v0.3
This commit is contained in:
		
							
								
								
									
										245
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										245
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -52,6 +52,24 @@ dependencies = [ | ||||
|  "windows-sys 0.60.2", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "autocfg" | ||||
| version = "1.5.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" | ||||
|  | ||||
| [[package]] | ||||
| name = "base16ct" | ||||
| version = "0.3.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d8b59d472eab27ade8d770dcb11da7201c11234bef9f82ce7aa517be028d462b" | ||||
|  | ||||
| [[package]] | ||||
| name = "base64ct" | ||||
| version = "1.8.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" | ||||
|  | ||||
| [[package]] | ||||
| name = "bytes" | ||||
| version = "1.10.1" | ||||
| @@ -76,6 +94,17 @@ version = "1.0.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" | ||||
|  | ||||
| [[package]] | ||||
| name = "chacha20" | ||||
| version = "0.10.0-rc.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9bd162f2b8af3e0639d83f28a637e4e55657b7a74508dba5a9bf4da523d5c9e9" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "cpufeatures", | ||||
|  "rand_core", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "clap" | ||||
| version = "4.5.49" | ||||
| @@ -122,6 +151,75 @@ version = "1.0.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" | ||||
|  | ||||
| [[package]] | ||||
| name = "const-oid" | ||||
| version = "0.10.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e" | ||||
|  | ||||
| [[package]] | ||||
| name = "cpufeatures" | ||||
| version = "0.2.17" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "crypto-bigint" | ||||
| version = "0.7.0-rc.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7f4b0fda9462026d53a3ef37c5ec283639ee8494a1a5401109c0e2a3fb4d490c" | ||||
| dependencies = [ | ||||
|  "num-traits", | ||||
|  "rand_core", | ||||
|  "serdect", | ||||
|  "subtle", | ||||
|  "zeroize", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "crypto-common" | ||||
| version = "0.2.0-rc.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "6a8235645834fbc6832939736ce2f2d08192652269e11010a6240f61b908a1c6" | ||||
| dependencies = [ | ||||
|  "hybrid-array", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "crypto-primes" | ||||
| version = "0.7.0-pre.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "25f2523fbb68811c8710829417ad488086720a6349e337c38d12fa81e09e50bf" | ||||
| dependencies = [ | ||||
|  "crypto-bigint", | ||||
|  "libm", | ||||
|  "rand_core", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "der" | ||||
| version = "0.8.0-rc.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e9d8dd2f26c86b27a2a8ea2767ec7f9df7a89516e4794e54ac01ee618dda3aa4" | ||||
| dependencies = [ | ||||
|  "const-oid", | ||||
|  "pem-rfc7468", | ||||
|  "zeroize", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "digest" | ||||
| version = "0.11.0-rc.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "dac89f8a64533a9b0eaa73a68e424db0fb1fd6271c74cc0125336a05f090568d" | ||||
| dependencies = [ | ||||
|  "const-oid", | ||||
|  "crypto-common", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "find-msvc-tools" | ||||
| version = "0.1.4" | ||||
| @@ -146,6 +244,15 @@ version = "0.5.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" | ||||
|  | ||||
| [[package]] | ||||
| name = "hybrid-array" | ||||
| version = "0.4.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" | ||||
| dependencies = [ | ||||
|  "typenum", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "is_terminal_polyfill" | ||||
| version = "1.70.1" | ||||
| @@ -174,6 +281,12 @@ version = "0.2.177" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" | ||||
|  | ||||
| [[package]] | ||||
| name = "libm" | ||||
| version = "0.2.15" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" | ||||
|  | ||||
| [[package]] | ||||
| name = "md5" | ||||
| version = "0.8.0" | ||||
| @@ -186,18 +299,56 @@ version = "2.7.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" | ||||
|  | ||||
| [[package]] | ||||
| name = "num-traits" | ||||
| version = "0.2.19" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" | ||||
| dependencies = [ | ||||
|  "autocfg", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "once_cell_polyfill" | ||||
| version = "1.70.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" | ||||
|  | ||||
| [[package]] | ||||
| name = "pem-rfc7468" | ||||
| version = "1.0.0-rc.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a8e58fab693c712c0d4e88f8eb3087b6521d060bcaf76aeb20cb192d809115ba" | ||||
| dependencies = [ | ||||
|  "base64ct", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "pin-project-lite" | ||||
| version = "0.2.16" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" | ||||
|  | ||||
| [[package]] | ||||
| name = "pkcs1" | ||||
| version = "0.8.0-rc.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" | ||||
| dependencies = [ | ||||
|  "der", | ||||
|  "spki", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "pkcs8" | ||||
| version = "0.11.0-rc.7" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "93eac55f10aceed84769df670ea4a32d2ffad7399400d41ee1c13b1cd8e1b478" | ||||
| dependencies = [ | ||||
|  "der", | ||||
|  "spki", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "pkg-config" | ||||
| version = "0.3.32" | ||||
| @@ -228,6 +379,44 @@ version = "5.3.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" | ||||
|  | ||||
| [[package]] | ||||
| name = "rand" | ||||
| version = "0.10.0-rc.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5ec474812b9de55111b29da8a1559f1718ef3dc20fa36f031f1b5d9e3836ad6c" | ||||
| dependencies = [ | ||||
|  "chacha20", | ||||
|  "rand_core", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "rand_core" | ||||
| version = "0.9.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" | ||||
| dependencies = [ | ||||
|  "getrandom", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "rsa" | ||||
| version = "0.10.0-rc.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "bf8955ab399f6426998fde6b76ae27233cce950705e758a6c17afd2f6d0e5d52" | ||||
| dependencies = [ | ||||
|  "const-oid", | ||||
|  "crypto-bigint", | ||||
|  "crypto-primes", | ||||
|  "digest", | ||||
|  "pkcs1", | ||||
|  "pkcs8", | ||||
|  "rand_core", | ||||
|  "signature", | ||||
|  "spki", | ||||
|  "subtle", | ||||
|  "zeroize", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "ryu" | ||||
| version = "1.0.20" | ||||
| @@ -286,12 +475,42 @@ dependencies = [ | ||||
|  "serde_core", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "serdect" | ||||
| version = "0.4.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d3ef0e35b322ddfaecbc60f34ab448e157e48531288ee49fafbb053696b8ffe2" | ||||
| dependencies = [ | ||||
|  "base16ct", | ||||
|  "serde", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "shlex" | ||||
| version = "1.3.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" | ||||
|  | ||||
| [[package]] | ||||
| name = "signature" | ||||
| version = "3.0.0-rc.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "fc280a6ff65c79fbd6622f64d7127f32b85563bca8c53cd2e9141d6744a9056d" | ||||
| dependencies = [ | ||||
|  "digest", | ||||
|  "rand_core", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "spki" | ||||
| version = "0.8.0-rc.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" | ||||
| dependencies = [ | ||||
|  "base64ct", | ||||
|  "der", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "strsim" | ||||
| version = "0.11.1" | ||||
| @@ -299,10 +518,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" | ||||
|  | ||||
| [[package]] | ||||
| name = "syn" | ||||
| version = "2.0.106" | ||||
| name = "subtle" | ||||
| version = "2.6.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" | ||||
| checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" | ||||
|  | ||||
| [[package]] | ||||
| name = "syn" | ||||
| version = "2.0.107" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
| @@ -331,6 +556,12 @@ dependencies = [ | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "typenum" | ||||
| version = "1.19.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" | ||||
|  | ||||
| [[package]] | ||||
| name = "unicode-ident" | ||||
| version = "1.0.19" | ||||
| @@ -466,12 +697,20 @@ version = "0.46.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" | ||||
|  | ||||
| [[package]] | ||||
| name = "zeroize" | ||||
| version = "1.8.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" | ||||
|  | ||||
| [[package]] | ||||
| name = "zsdiff_all" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "clap", | ||||
|  "md5", | ||||
|  "rand", | ||||
|  "rsa", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
|  "tokio", | ||||
|   | ||||
| @@ -3,6 +3,9 @@ name = "zsdiff_all" | ||||
| version = "0.1.0" | ||||
| edition = "2024" | ||||
|  | ||||
| [profile.dev] | ||||
| opt-level = 3 | ||||
|  | ||||
| [[bin]] | ||||
| name = "zsdiff" | ||||
| path = "src/zsdiff.rs" | ||||
| @@ -19,3 +22,5 @@ serde = { version = "1.0", features = ["derive"] } | ||||
| serde_json = "1.0" | ||||
| walkdir = "2.5" | ||||
| clap = { version = "4.5", features = ["derive"] } | ||||
| rsa = { version = "0.10.0-rc.9", features = ["std"] } | ||||
| rand = { version = "0.10.0-rc.0", features = ["thread_rng", "std"] } | ||||
|   | ||||
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
								
							| @@ -27,4 +27,19 @@ Options: | ||||
|   -d, --dest-dir <DEST_DIR>   | ||||
|   -h, --hash-check            | ||||
|   --help                    Print help | ||||
| ``` | ||||
|  | ||||
| ``` | ||||
| metadata version: uint16 (2 bytes) | ||||
| metadata size: uint32 (4 bytes) | ||||
| n = 6 | ||||
| metadata content: bytes (n;m bytes) | ||||
| m = n+6 | ||||
| (content -> rsa -> zstd (bytes) -> bytes) (m;o) | ||||
| content entry: { | ||||
|   size: uint32 (4 bytes) | ||||
|   i = 4 | ||||
|   j = i + size | ||||
|   content: (i;j bytes) | ||||
| } | ||||
| ``` | ||||
							
								
								
									
										187
									
								
								src/utils.rs
									
									
									
									
									
								
							
							
						
						
									
										187
									
								
								src/utils.rs
									
									
									
									
									
								
							| @@ -1,12 +1,23 @@ | ||||
| use crate::utils; | ||||
| use md5; | ||||
| use rsa::pkcs1::{DecodeRsaPrivateKey, EncodeRsaPrivateKey, EncodeRsaPublicKey}; | ||||
| use rsa::pkcs8::LineEnding; | ||||
| use rsa::traits::PublicKeyParts; | ||||
| use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use std::collections::HashMap; | ||||
| use std::{fs, io}; | ||||
| use std::io; | ||||
| use tokio::fs; | ||||
| use tokio::io::AsyncWriteExt; | ||||
| use zstd::{Decoder, Encoder}; | ||||
|  | ||||
| const METADATA_VERSION: u16 = 1; | ||||
| const SUPPORTED_VERSION: [u16; 1] = [1]; | ||||
|  | ||||
| pub fn is_metadata_supported(version: u16) -> bool { | ||||
|     SUPPORTED_VERSION.contains(&version) | ||||
| } | ||||
|  | ||||
| pub struct Zsdiff { | ||||
|     pub content: HashMap<String, Vec<u8>>, | ||||
|     pub metadata: Metadata, | ||||
| @@ -31,8 +42,27 @@ impl Zsdiff { | ||||
|         index += meta_size; | ||||
|         println!(">>> File count: {}", metadata.file_count); | ||||
|  | ||||
|         let data = _data; | ||||
|         let mut data = _data[index..].to_vec(); | ||||
|  | ||||
|         if metadata.encrypted { | ||||
|             println!(">>> Decrypting file"); | ||||
|             let key_content = fs::read("./private.pem").await?; | ||||
|             let private_key = RsaPrivateKey::from_pkcs1_pem( | ||||
|                 String::from_utf8(key_content) | ||||
|                     .expect("Can't load key") | ||||
|                     .as_str(), | ||||
|             ) | ||||
|             .expect("Can't load private key"); | ||||
|             let encrypter = Encrypter::from_private_key(private_key); | ||||
|             data = encrypter.decrypt(data).await; | ||||
|             println!(">>> Decrypting done"); | ||||
|         } | ||||
|         println!(">>> Decompressing file"); | ||||
|         let compressor = Compressor::new(); | ||||
|         data = compressor.decompress(data).await?; | ||||
|         println!(">>> Decompressing done"); | ||||
|  | ||||
|         index = 0; | ||||
|         let mut content = HashMap::new(); | ||||
|         while index < data.len() { | ||||
|             let filename_size = | ||||
| @@ -52,12 +82,11 @@ impl Zsdiff { | ||||
|         Ok(Zsdiff { content, metadata }) | ||||
|     } | ||||
|  | ||||
|     pub async fn to_vec(&self) -> Vec<u8> { | ||||
|     pub async fn to_vec(&self, compress_level: i32, encrypt: bool) -> Vec<u8> { | ||||
|         let mut meta_bytes: Vec<u8> = Vec::new(); | ||||
|  | ||||
|         meta_bytes.extend(METADATA_VERSION.to_be_bytes()); | ||||
|         meta_bytes.extend(METADATA_VERSION.to_be_bytes()); // u16 | ||||
|         let meta = serde_json::to_vec(&self.metadata).unwrap(); | ||||
|         meta_bytes.extend((meta.len() as u32).to_be_bytes()); | ||||
|         meta_bytes.extend((meta.len() as u32).to_be_bytes()); // u32 | ||||
|         meta_bytes.extend(meta); | ||||
|  | ||||
|         let mut parts: Vec<u8> = Vec::new(); | ||||
| @@ -71,9 +100,43 @@ impl Zsdiff { | ||||
|             parts.extend(part) | ||||
|         } | ||||
|  | ||||
|         let size_before = parts.len(); | ||||
|         parts = self.compress(parts, compress_level).await; | ||||
|         if encrypt { | ||||
|             parts = self.encrypt(parts).await; | ||||
|         } | ||||
|  | ||||
|         let size_after = parts.len(); | ||||
|         println!(">>> Size before: {:.1?}KB", size_before / 1024); | ||||
|         println!(">>> Size after: {:.1?}KB", size_after / 1024); | ||||
|         println!( | ||||
|             ">>> Compress ratio: {:.2?}%", | ||||
|             size_after as f64 / size_before as f64 * 100.0 | ||||
|         ); | ||||
|  | ||||
|         let out = vec![meta_bytes, parts].concat(); | ||||
|         out | ||||
|     } | ||||
|  | ||||
|     async fn compress(&self, data: Vec<u8>, level: i32) -> Vec<u8> { | ||||
|         let compressor = Compressor::new(); | ||||
|         println!(">>> Compressing"); | ||||
|         let _data = compressor | ||||
|             .compress(data, level) | ||||
|             .await | ||||
|             .expect("Can't compress data"); | ||||
|         println!(">>> Compressing done"); | ||||
|         _data | ||||
|     } | ||||
|  | ||||
|     async fn encrypt(&self, data: Vec<u8>) -> Vec<u8> { | ||||
|         println!(">>> Encrypting"); | ||||
|         let encrypter = Encrypter::new_pair(); | ||||
|         let _data = encrypter.encrypt(data).await.expect("Can't encrypt data"); | ||||
|         encrypter.export().await.expect("Can't export keys"); | ||||
|         println!(">>> Encrypting done"); | ||||
|         _data | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| @@ -84,6 +147,7 @@ pub struct Metadata { | ||||
|     pub remove_folders: Vec<String>, | ||||
|     pub file_count: u32, | ||||
|     pub compress_level: i32, | ||||
|     pub encrypted: bool, | ||||
| } | ||||
|  | ||||
| pub async fn get_hash(data: Vec<u8>) -> String { | ||||
| @@ -91,15 +155,108 @@ pub async fn get_hash(data: Vec<u8>) -> String { | ||||
|     format!("{:x}", hash) | ||||
| } | ||||
|  | ||||
| pub async fn compress(input: Vec<u8>, output: &fs::File, level: i32) { | ||||
|     let mut encoder = Encoder::new(output, level).unwrap(); | ||||
|     io::copy(&mut input.as_slice(), &mut encoder).unwrap(); | ||||
|     encoder.finish().unwrap(); | ||||
| struct Compressor {} | ||||
| impl Compressor { | ||||
|     pub(crate) fn new() -> Self { | ||||
|         Compressor {} | ||||
|     } | ||||
|  | ||||
|     pub async fn compress(&self, input: Vec<u8>, level: i32) -> Result<Vec<u8>, io::Error> { | ||||
|         let buf = Vec::new(); | ||||
|         let mut encoder = Encoder::new(buf, level)?; | ||||
|         io::copy(&mut &input[..], &mut encoder)?; | ||||
|         encoder.finish() | ||||
|     } | ||||
|  | ||||
|     pub async fn decompress(&self, input: Vec<u8>) -> Result<Vec<u8>, io::Error> { | ||||
|         let mut decoder = Decoder::new(&input[..])?; | ||||
|         let mut buf = Vec::new(); | ||||
|         io::copy(&mut decoder, &mut buf)?; | ||||
|         Ok(buf) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub async fn decompress(input: Vec<u8>) -> Result<Vec<u8>, io::Error> { | ||||
|     let mut decoder = Decoder::new(&input[..])?; | ||||
|     let mut buf = Vec::new(); | ||||
|     io::copy(&mut decoder, &mut buf)?; | ||||
|     Ok(buf) | ||||
| pub struct Encrypter { | ||||
|     private_key: RsaPrivateKey, | ||||
|     public_key: RsaPublicKey, | ||||
|     key_size: usize, | ||||
| } | ||||
| impl Encrypter { | ||||
|     pub fn new_pair() -> Self { | ||||
|         let mut rng = rand::rng(); | ||||
|         let bits = 2048; | ||||
|         let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); | ||||
|         let public_key = RsaPublicKey::from(&private_key); | ||||
|         Self { | ||||
|             private_key, | ||||
|             public_key, | ||||
|             key_size: bits, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn from_private_key(private_key: RsaPrivateKey) -> Self { | ||||
|         let public_key = RsaPublicKey::from(&private_key); | ||||
|         Self { | ||||
|             key_size: public_key.size(), | ||||
|             public_key, | ||||
|             private_key, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn new(private_key: RsaPrivateKey, public_key: RsaPublicKey) -> Self { | ||||
|         Self { | ||||
|             key_size: public_key.size(), | ||||
|             private_key, | ||||
|             public_key, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub async fn encrypt(&self, data: Vec<u8>) -> Result<Vec<u8>, io::Error> { | ||||
|         let mut rng = rand::rng(); | ||||
|         let mut out = Vec::new(); | ||||
|         let segment_size = self.public_key.size() - 11; | ||||
|  | ||||
|         for seg in data.chunks(segment_size) { | ||||
|             let segment = self | ||||
|                 .public_key | ||||
|                 .encrypt(&mut rng, Pkcs1v15Encrypt, seg) | ||||
|                 .unwrap(); | ||||
|             out.extend(segment) | ||||
|         } | ||||
|         Ok(out) | ||||
|     } | ||||
|  | ||||
|     pub async fn decrypt(&self, data: Vec<u8>) -> Vec<u8> { | ||||
|         let mut out = Vec::new(); | ||||
|         let segment_size = self.public_key.size(); | ||||
|         for seg in data.chunks(segment_size) { | ||||
|             let segment = self | ||||
|                 .private_key | ||||
|                 .decrypt(Pkcs1v15Encrypt, seg) | ||||
|                 .expect("failed to decrypt"); | ||||
|             out.extend(segment) | ||||
|         } | ||||
|         out | ||||
|     } | ||||
|  | ||||
|     pub async fn export(&self) -> Result<(), io::Error> { | ||||
|         let private_bytes = self | ||||
|             .private_key | ||||
|             .to_pkcs1_pem(LineEnding::CRLF) | ||||
|             .expect("failed to pem private key"); | ||||
|         let public_bytes = self | ||||
|             .public_key | ||||
|             .to_pkcs1_pem(LineEnding::CRLF) | ||||
|             .expect("failed to pem public key"); | ||||
|  | ||||
|         fs::File::create("private.pem") | ||||
|             .await? | ||||
|             .write_all(private_bytes.as_bytes()) | ||||
|             .await?; | ||||
|         fs::File::create("public.pem") | ||||
|             .await? | ||||
|             .write_all(public_bytes.as_bytes()) | ||||
|             .await?; | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -48,6 +48,7 @@ async fn compare_hashes( | ||||
|     old: HashMap<String, FileInfo>, | ||||
|     new: HashMap<String, FileInfo>, | ||||
|     compress_level: i32, | ||||
|     encrypted: bool, | ||||
| ) -> Zsdiff { | ||||
|     let mut diff_files: HashMap<String, Vec<u8>> = HashMap::new(); | ||||
|     let mut remove_files: Vec<String> = vec![]; | ||||
| @@ -89,6 +90,7 @@ async fn compare_hashes( | ||||
|             remove_folders, | ||||
|             compress_level, | ||||
|             file_count: diff_files.len() as u32, | ||||
|             encrypted, | ||||
|         }, | ||||
|     } | ||||
| } | ||||
| @@ -98,29 +100,24 @@ pub async fn zsdiff( | ||||
|     old: String, | ||||
|     new: String, | ||||
|     level: i32, | ||||
|     encrypt: bool, | ||||
| ) -> Result<(), io::Error> { | ||||
|     let now = time::Instant::now(); | ||||
|     let output_filename = &format!("{}.zdiff", filename); | ||||
|     let old_hashes = walk_dir(old).await; | ||||
|     let new_hashes = walk_dir(new).await; | ||||
|     let compare_hashes = compare_hashes(old_hashes, new_hashes, level).await; | ||||
|     let parts = compare_hashes.to_vec().await; | ||||
|     let size_before = parts.len(); | ||||
|     let now = time::Instant::now(); | ||||
|     utils::compress(parts, &fs::File::create(output_filename)?, level).await; | ||||
|     let output_data = fs::read(output_filename)?; | ||||
|     let size_after = output_data.len(); | ||||
|     let hash = get_hash(output_data).await; | ||||
|     let diff = compare_hashes(old_hashes, new_hashes, level, encrypt).await; | ||||
|  | ||||
|     let mut file = fs::File::create(output_filename)?; | ||||
|     let data = diff.to_vec(level, encrypt).await; | ||||
|     file.write_all(&data[..])?; | ||||
|  | ||||
|     let hash = get_hash(data).await; | ||||
|     let output_hash = format!("{}  {}", hash.clone(), output_filename); | ||||
|     fs::File::create(format!("{}.md5", output_filename))?.write_all(output_hash.as_bytes())?; | ||||
|     let elapsed = now.elapsed(); | ||||
|     println!("Zsdiff hash: {}", hash); | ||||
|     println!("Size before: {:.1?}KB", size_before / 1024); | ||||
|     println!("Size after: {:.1?}KB", size_after / 1024); | ||||
|     println!( | ||||
|         "Compress ratio: {:.2?}%", | ||||
|         size_after as f64 / size_before as f64 * 100.0 | ||||
|     ); | ||||
|     print!("Time elapsed: {:.2?}", elapsed); | ||||
|     println!(">>> Zsdiff hash: {}", hash); | ||||
|     println!("Time elapsed: {:.2?}", elapsed); | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| @@ -134,10 +131,19 @@ struct Args { | ||||
|     old: String, | ||||
|     #[arg(short, long)] | ||||
|     new: String, | ||||
|     #[arg(short, long)] | ||||
|     encrypt: bool, | ||||
| } | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() -> io::Result<()> { | ||||
|     let args = Args::parse(); | ||||
|     zsdiff(args.filename, args.old, args.new, args.compress_level).await | ||||
|     zsdiff( | ||||
|         args.filename, | ||||
|         args.old, | ||||
|         args.new, | ||||
|         args.compress_level, | ||||
|         args.encrypt, | ||||
|     ) | ||||
|     .await | ||||
| } | ||||
|   | ||||
							
								
								
									
										125
									
								
								src/zspatch.rs
									
									
									
									
									
								
							
							
						
						
									
										125
									
								
								src/zspatch.rs
									
									
									
									
									
								
							| @@ -1,6 +1,7 @@ | ||||
| mod utils; | ||||
|  | ||||
| use clap::Parser; | ||||
| use crate::utils::Metadata; | ||||
| use clap::{Arg, ArgAction, ArgMatches, Command, Parser}; | ||||
| use std::path::{Path, PathBuf}; | ||||
| use std::{io, time}; | ||||
| use tokio::fs; | ||||
| @@ -21,10 +22,28 @@ async fn create_tmp_dir(dir_name: String) -> Result<String, io::Error> { | ||||
|     }) | ||||
| } | ||||
|  | ||||
| async fn load_metadata(filename: String) -> Result<Metadata, io::Error> { | ||||
|     let filepath = format!("{}.zdiff", filename); | ||||
|     let data = fs::read(&filepath).await?; | ||||
|  | ||||
|     let meta_version = u16::from_be_bytes(data[..2].try_into().unwrap()); | ||||
|     if !utils::is_metadata_supported(meta_version) { | ||||
|         return Err(io::Error::new( | ||||
|             io::ErrorKind::InvalidData, | ||||
|             "Metadata version not supported", | ||||
|         )); | ||||
|     } | ||||
|     let meta_size = u32::from_be_bytes(data[2..6].try_into().unwrap()) as usize; | ||||
|     let meta_data = data[6..meta_size + 6].to_vec(); | ||||
|     let metadata = serde_json::from_slice(&meta_data[..]) | ||||
|         .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; | ||||
|  | ||||
|     Ok(metadata) | ||||
| } | ||||
|  | ||||
| async fn load_file(filename: String) -> Result<Zsdiff, io::Error> { | ||||
|     let full_filename = format!("{}.zdiff", filename); | ||||
|     let compressed_data = fs::read(&full_filename).await?; | ||||
|     let data = utils::decompress(compressed_data).await?; | ||||
|     let data = fs::read(&full_filename).await?; | ||||
|     let zsdiff = Zsdiff::from_vec(data).await?; | ||||
|     println!( | ||||
|         ">>> Metadata files to remove: {}", | ||||
| @@ -77,15 +96,11 @@ async fn check_hash(filename: String) -> Result<(), io::Error> { | ||||
| } | ||||
|  | ||||
| async fn zspatch(filename: String, dest_dir: String) -> Result<(), io::Error> { | ||||
|     let now = time::Instant::now(); | ||||
|     let cloned = filename.clone(); | ||||
|     let diff = load_file(cloned).await.ok().unwrap(); | ||||
|     let tmp_dir_name = extract_files(&diff, filename).await?; | ||||
|     let now = time::Instant::now(); | ||||
|  | ||||
|     fs::File::create("metadata.json") | ||||
|         .await? | ||||
|         .write_all(serde_json::to_vec(&diff.metadata).unwrap().as_slice()) | ||||
|         .await?; | ||||
|     let files_to_copy: Vec<String> = diff.content.keys().cloned().collect(); | ||||
|     for (_, name) in files_to_copy.iter().enumerate() { | ||||
|         let from_path = Path::new(&tmp_dir_name).join(name); | ||||
| @@ -156,7 +171,7 @@ async fn zspatch(filename: String, dest_dir: String) -> Result<(), io::Error> { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // fs::remove_dir_all(tmp_dir_name).await?; | ||||
|     fs::remove_dir_all(tmp_dir_name).await?; | ||||
|     println!(">>> Patching done! <<<"); | ||||
|     println!(">>> Elapsed time: {:.2?}", now.elapsed()); | ||||
|     Ok(()) | ||||
| @@ -176,19 +191,85 @@ struct Args { | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() -> io::Result<()> { | ||||
|     let args = Args::parse(); | ||||
|     let m = Command::new("ZsPatch") | ||||
|         .author("ScuroNeko") | ||||
|         .version("0.3.0") | ||||
|         .about("Explains in brief what the program does") | ||||
|         .subcommand_required(true) | ||||
|         .arg_required_else_help(true) | ||||
|         .after_help("") | ||||
|         .subcommand( | ||||
|             Command::new("metadata") | ||||
|                 .short_flag('m') | ||||
|                 .long_flag("metadata") | ||||
|                 .arg( | ||||
|                     Arg::new("filename") | ||||
|                         .short('f') | ||||
|                         .long("filename") | ||||
|                         .required(true) | ||||
|                         .action(ArgAction::Set), | ||||
|                 ), | ||||
|         ) | ||||
|         .subcommand( | ||||
|             Command::new("patch") | ||||
|                 .short_flag('p') | ||||
|                 .arg( | ||||
|                     Arg::new("filename") | ||||
|                         .short('f') | ||||
|                         .required(true) | ||||
|                         .action(ArgAction::Set), | ||||
|                 ) | ||||
|                 .arg( | ||||
|                     Arg::new("dest") | ||||
|                         .short('d') | ||||
|                         .required(true) | ||||
|                         .action(ArgAction::Set), | ||||
|                 ) | ||||
|                 .arg( | ||||
|                     Arg::new("hash_check") | ||||
|                         .long("hash_check") | ||||
|                         .required(false) | ||||
|                         .action(ArgAction::SetTrue), | ||||
|                 ), | ||||
|         ) | ||||
|         .get_matches(); | ||||
|  | ||||
|     let filename = args.filename.clone(); | ||||
|     let dest_dir = args.dest_dir.clone(); | ||||
|     match m.subcommand() { | ||||
|         Some(("metadata", meta_matches)) => { | ||||
|             let filename: &String = meta_matches.get_one("filename").unwrap(); | ||||
|             let metadata = load_metadata(filename.clone()).await?; | ||||
|             println!(">>> Compress level: {}", metadata.compress_level); | ||||
|             println!( | ||||
|                 ">>> Encrypted?: {}", | ||||
|                 if metadata.encrypted { "Yes" } else { "No" } | ||||
|             ); | ||||
|             return Ok(()); | ||||
|         } | ||||
|         Some(("patch", patch_matches)) => { | ||||
|             let filename: &String = patch_matches.get_one("filename").unwrap(); | ||||
|             let dest_dir: &String = patch_matches.get_one("dest").unwrap(); | ||||
|             if patch_matches.get_flag("hash_check") { | ||||
|                 check_hash(filename.clone()).await.ok(); | ||||
|             } | ||||
|             zspatch(filename.clone(), dest_dir.clone()).await?; | ||||
|         } | ||||
|         _ => unreachable!("Subcommand is required"), | ||||
|     } | ||||
|     Ok(()) | ||||
|  | ||||
|     if args.check_hash { | ||||
|         check_hash(args.filename.clone()).await.ok(); | ||||
|     } | ||||
|     if args.metadata { | ||||
|         let diff = load_file(filename).await?; | ||||
|         let metadata = diff.metadata; | ||||
|         println!(">>> Compress level: {}", metadata.compress_level); | ||||
|         return Ok(()); | ||||
|     } | ||||
|     zspatch(filename, dest_dir).await | ||||
|     // let args = Args::parse(); | ||||
|     // | ||||
|     // let filename = args.filename.clone(); | ||||
|     // let dest_dir = args.dest_dir.clone(); | ||||
|     // | ||||
|     // if args.check_hash { | ||||
|     //     check_hash(args.filename.clone()).await.ok(); | ||||
|     // } | ||||
|     // if args.metadata { | ||||
|     //     let diff = load_file(filename).await?; | ||||
|     //     let metadata = diff.metadata; | ||||
|     //     println!(">>> Compress level: {}", metadata.compress_level); | ||||
|     //     return Ok(()); | ||||
|     // } | ||||
|     // zspatch(filename, dest_dir).await | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user