diff --git a/Cargo.lock b/Cargo.lock index fe67557..cdc3e20 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,9 +31,9 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" +checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" dependencies = [ "aead", "aes", @@ -43,6 +43,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "0.3.1" @@ -128,9 +137,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bitflags" @@ -140,9 +149,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" -version = "0.9.0" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] @@ -189,9 +198,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.2.5" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a1f23fa97e1d1641371b51f35535cb26959b8e27ab50d167a8b996b5bada819" +checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc" dependencies = [ "clap_builder", "clap_derive", @@ -200,9 +209,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.5" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdc5d93c358224b4d6867ef1356d740de2303e9892edc06c5340daeccd96bab" +checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990" dependencies = [ "anstream", "anstyle", @@ -213,9 +222,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.2.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +checksum = "191d9573962933b4027f932c600cd252ce27a8ad5979418fe78e43c07996f27b" dependencies = [ "heck", "proc-macro2", @@ -225,9 +234,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "colorchoice" @@ -240,8 +249,10 @@ name = "contego" version = "0.3.0" dependencies = [ "aes-gcm", - "base64 0.21.0", + "base64 0.21.2", "clap", + "env_logger", + "log", "ntest", "rand", "sha256", @@ -296,7 +307,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" dependencies = [ "byteorder", - "digest", + "digest 0.9.0", "rand_core 0.5.1", "subtle", "zeroize", @@ -311,6 +322,29 @@ dependencies = [ "generic-array", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + [[package]] name = "errno" version = "0.3.1" @@ -432,6 +466,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "idna" version = "0.3.0" @@ -553,26 +593,19 @@ dependencies = [ [[package]] name = "ntest" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e865500b46e35210765d62d549178c520badc018b2a71a827c29b305d680d1fb" +checksum = "da8ec6d2b73d45307e926f5af46809768581044384637af6b3f3fe7c3c88f512" dependencies = [ - "ntest_proc_macro_helper", "ntest_test_cases", "ntest_timeout", ] -[[package]] -name = "ntest_proc_macro_helper" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0e328d267a679d683b55222b3d06c2fb7358220857945bfc4e65a6b531e9994" - [[package]] name = "ntest_test_cases" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f7caf063242bb66721e74515dc01a915901063fa1f994bee7a2b9136f13370e" +checksum = "be7d33be719c6f4d09e64e27c1ef4e73485dc4cc1f4d22201f89860a7fe22e22" dependencies = [ "proc-macro2", "quote", @@ -581,11 +614,10 @@ dependencies = [ [[package]] name = "ntest_timeout" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca6eaadc7c104fb2eb0c6d14782b9e33775aaf5584c3bcb0f87c89e3e6d6c07" +checksum = "066b468120587a402f0b47d8f80035c921f6a46f8209efd0632a89a16f5188a4" dependencies = [ - "ntest_proc_macro_helper", "proc-macro-crate", "proc-macro2", "quote", @@ -754,6 +786,23 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" + [[package]] name = "ring" version = "0.16.20" @@ -813,22 +862,20 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.9" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ - "block-buffer", "cfg-if", "cpufeatures", - "digest", - "opaque-debug", + "digest 0.10.7", ] [[package]] name = "sha256" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328169f167261957e83d82be47f9e36629e257c62308129033d7f7e7c173d180" +checksum = "5f9f8b5de2bac3a4ae28e9b611072a8e326d9b26c8189c0972d4c321fa684f1f" dependencies = [ "hex", "sha2", @@ -899,6 +946,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -916,9 +972,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.27.0" +version = "1.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" dependencies = [ "autocfg", "bytes", @@ -930,14 +986,14 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", @@ -1178,6 +1234,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 670f357..cdb424b 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,23 @@ [package] name = "contego" -description = "Dynamic CLI tool for secure file transfer" +description = "CLI tool for file transfer" version = "0.3.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tokio = { version = "1.20.4", features = ["full"] } +tokio = { version = "1.28.1", features = ["full"] } rand = "0.7.0" x25519-dalek = "1.2.0" -aes-gcm = "0.10.1" -base64 = "0.21.0" -sha256 = "1.1.2" +aes-gcm = "0.10.2" +base64 = "0.21.2" +sha256 = "1.1.3" ureq = "2.6.2" -clap = { version = "4.2.5", features = ["derive"] } +clap = { version = "4.3.0", features = ["derive"] } +log = "0.4.17" +env_logger = "0.10.0" [dev-dependencies] tokio-test = "0.4.2" -ntest = "0.8.1" +ntest = "0.9.0" diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/client.rs b/src/client.rs index 8a93e5f..e4506e3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,5 +1,6 @@ use std::{error::Error, net::SocketAddr, path::PathBuf}; +use log::{debug, error, info}; use tokio::{io::AsyncWriteExt, net::TcpStream}; use crate::{ @@ -21,19 +22,31 @@ impl Client { } pub async fn connection(&self) -> Result<(), Box> { + info!("Trying to connect to the server at {}", self.addr); + let mut socket = TcpStream::connect(self.addr).await?; + + debug!("Connected to the TCP socket at {}", self.addr); + let mut handler = SocketHandler::new(&mut socket); let crypto = Crypto::new(&mut handler, true).await?; handler.set_crypto(crypto); + info!("Encrypted connection to {} established", self.addr); + if !self.authorize(&mut handler).await? { - // log: invalid access key '' + error!( + "Authorization failed due to an invalid access key '{}'", + self.key + ); return Ok(()); } let metadata = self.metadata(&mut handler).await?; self.requests(&mut handler, metadata).await?; + debug!("Connection sequence done, shutting down"); + Ok(()) } @@ -41,6 +54,8 @@ impl Client { &self, handler: &mut SocketHandler<'_>, ) -> Result> { + debug!("Starting authorization"); + let msg = self.key.as_bytes().to_vec(); handler.send(&msg).await?; @@ -52,6 +67,8 @@ impl Client { return Ok(false); } + debug!("Authorization successfully done"); + Ok(true) } @@ -59,10 +76,14 @@ impl Client { &self, handler: &mut SocketHandler<'_>, ) -> Result, Box> { + debug!("Starting to receive metadata"); + let buf = handler.recv().await?; let amt = String::from_utf8(buf.clone())?.parse::()?; handler.send(&buf).await?; // confirmation + debug!("Confirmed metadata amount ({})", amt); + let mut metadata = Vec::new(); while metadata.len() < amt { @@ -74,6 +95,8 @@ impl Client { let size = split[1].trim().parse::()?; let hash = split[2].trim().to_string(); + debug!("Metadata of file '{}' received successfully", name); + let info = FileInfo::new(name, size, hash); metadata.push(info); @@ -87,12 +110,14 @@ impl Client { handler: &mut SocketHandler<'_>, metadata: Vec, ) -> Result<(), Box> { + info!("Starting to send requests"); + for file in metadata { let (mut handle, path) = new_file(self.output.clone(), &file.name).await?; let msg = file.hash.as_bytes().to_vec(); handler.send(&msg).await?; - // log: downloading file to + info!("Requesting file '{}'", file.hash); let mut remaining = file.size; @@ -101,6 +126,8 @@ impl Client { handle.write_all(&buf).await?; handle.flush().await?; remaining -= buf.len() as u64; + + debug!("File '{}': {} bytes remaining", file.hash, remaining); } let check_hash = crypto::try_hash(&path)?; @@ -109,9 +136,13 @@ impl Client { if check_hash != file.hash { return Err("Unsuccessful file transfer, hashes don't match".into()); - } // else: log that the transfer was successful + } + + info!("File '{}' successfully transferred", file.hash); } + info!("All requests successfully done"); + Ok(()) } } diff --git a/src/crypto.rs b/src/crypto.rs index 3de7917..3754ea4 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -5,6 +5,7 @@ use aes_gcm::{ aes::Aes256, Aes256Gcm, AesGcm, KeyInit, Nonce, }; +use log::debug; use rand::{rngs::OsRng, RngCore}; use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret}; @@ -35,10 +36,14 @@ impl Crypto { handler: &mut SocketHandler<'_>, go_first: bool, ) -> Result> { + debug!("Starting ECDH key exchange"); + let buf: Vec; let own_sec = EphemeralSecret::new(OsRng); let own_pbk = PublicKey::from(&own_sec); - let msg = own_pbk.as_bytes().to_vec(); + let mut msg = own_pbk.as_bytes().to_vec(); + + msg.push(b':'); // manual delimiter if go_first { handler.send_raw(&msg).await?; @@ -48,20 +53,29 @@ impl Crypto { handler.send_raw(&msg).await?; } + debug!("Calculating PPK from the shared secret"); + let slice: [u8; DH_PBK_SIZE] = buf[..DH_PBK_SIZE].try_into()?; let recv_pbk = PublicKey::from(slice); + let pvk = own_sec.diffie_hellman(&recv_pbk); - Ok(own_sec.diffie_hellman(&recv_pbk)) + debug!("PPK successfully generated"); + + Ok(pvk) } - fn nonce(&self) -> Nonce { + fn nonce(&mut self) -> Nonce { + debug!("Generating new unique nonce (AEAD)"); + let mut nonce = Nonce::default(); self.rng.fill_bytes(&mut nonce); nonce } - pub async fn encrypt(&self, data: &[u8]) -> Result, Box> { + pub async fn encrypt(&mut self, data: &[u8]) -> Result, Box> { + debug!("Encrypting {} bytes payload", data.len()); + let nonce = self.nonce(); let encrypted = match self.cipher.encrypt(&nonce, data.as_ref()) { Ok(data) => data, @@ -75,6 +89,8 @@ impl Crypto { } pub async fn decrypt(&self, data: &[u8]) -> Result, Box> { + debug!("Decrypting {} bytes payload", data.len()); + let (nonce_bytes, data) = data.split_at(AES_NONCE_SIZE); let nonce = Nonce::from_slice(nonce_bytes); let decrypted = match self.cipher.decrypt(nonce, data.as_ref()) { @@ -87,9 +103,9 @@ impl Crypto { } pub fn try_hash(path: &Path) -> Result> { + debug!("Calculating SHA hash"); + let hash = sha256::try_digest(path)?; Ok(hash) } - -// TODO: unit test if deemed necessary diff --git a/src/lib.rs b/src/lib.rs index 266c5b4..4e16913 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -pub mod cli; pub mod client; pub mod crypto; pub mod parser; diff --git a/src/main.rs b/src/main.rs index 0da85a6..5b0e21e 100755 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use std::{error::Error, net::SocketAddr, path::PathBuf}; use clap::{command, ArgGroup, Parser, Subcommand}; -use contego::parsers::{addr_parser, dirpath_parser, filepath_parser}; +use contego::parser::{addr_parser, dirpath_parser, filepath_parser}; #[derive(Debug, Parser)] #[command(about, version)] @@ -54,19 +54,21 @@ enum Commands { #[tokio::main] async fn main() -> Result<(), Box> { - let cli = Cli::parse(); + // TODO: init logger with default level set to 'info' - match cli.command { - Commands::Host { - port, - ipv6, - infile, - files, - chunksize, - local, - } => {} - Commands::Connect { addr, out, key } => {} - }; + let _cli = Cli::parse(); + + //match cli.command { + // Commands::Host { + // port, + // ipv6, + // infile, + // files, + // chunksize, + // local, + // } => {} + // Commands::Connect { addr, out, key } => {} + //}; Ok(()) } diff --git a/src/server.rs b/src/server.rs index b5842b4..636092f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, error::Error, net::SocketAddr, path::PathBuf, sync::Arc}; +use log::{debug, error, info}; use tokio::{ fs::File, io::AsyncReadExt, @@ -48,30 +49,40 @@ impl Server { async fn listen(self: Arc) -> Result<(), Box> { let listener = TcpListener::bind(self.addr).await?; + info!("Listening on {} - Access key: {}", self.addr, self.key); + loop { let this_self = self.clone(); let (mut socket, addr) = listener.accept().await?; - // log: new client connected: + info!("New client connected: {}", addr); - match tokio::spawn(async move { this_self.connection(&mut socket).await }).await { - Ok(_) => {} - Err(e) => eprintln!("Error during connection ({}): {}", addr, e), + match tokio::spawn(async move { this_self.connection(&mut socket, &addr).await }).await + { + Ok(_) => info!("Client disconnected: {}", addr), + Err(e) => error!("Fatal error in connection {}: {}", addr, e), }; } } - async fn connection(&self, socket: &mut TcpStream) -> Result<(), Box> { + async fn connection( + &self, + socket: &mut TcpStream, + addr: &SocketAddr, + ) -> Result<(), Box> { let mut handler = SocketHandler::new(socket); - let crypto = Crypto::new(&mut handler, true).await?; + let crypto = Crypto::new(&mut handler, false).await?; handler.set_crypto(crypto); - if !self.authorize(&mut handler).await? { + debug!("({}): Connection established", addr); + + if !self.authorize(&mut handler, addr).await? { + info!("({}): Invalid access key", addr); return Ok(()); } - self.metadata(&mut handler).await?; - self.requests(&mut handler).await?; + self.metadata(&mut handler, addr).await?; + self.requests(&mut handler, addr).await?; Ok(()) } @@ -79,7 +90,10 @@ impl Server { async fn authorize( &self, handler: &mut SocketHandler<'_>, + addr: &SocketAddr, ) -> Result> { + debug!("({}): Starting authorization", addr); + let buf = handler.recv().await?; let key = String::from_utf8(buf)?; @@ -96,13 +110,18 @@ impl Server { handler.send(&res_msg).await?; + debug!("({}): Authorization finished", addr); + Ok(is_valid) } async fn metadata( &self, handler: &mut SocketHandler<'_>, + addr: &SocketAddr, ) -> Result<(), Box> { + debug!("({}): Starting to send metadata", addr); + let amt = self.metadata.len(); let msg = amt.to_string().as_bytes().to_vec(); @@ -115,11 +134,15 @@ impl Server { return Err("Broken message sequence during metadata exchange".into()); } + debug!("({}): Metadata amount confirmed successfully", addr); + for file in &self.metadata { let msg = format!("{}:{}:{}", file.name, file.size, file.hash) .as_bytes() .to_vec(); handler.send(&msg).await?; + + debug!("({}): Sent metadata of file '{}'", addr, file.hash); } Ok(()) @@ -128,7 +151,10 @@ impl Server { async fn requests( &self, handler: &mut SocketHandler<'_>, + addr: &SocketAddr, ) -> Result<(), Box> { + debug!("({}): Waiting for file requests", addr); + loop { let buf = handler.recv().await?; let hash = String::from_utf8(buf)?; @@ -138,14 +164,20 @@ impl Server { break; } + debug!("({}): Received request for file '{}'", addr, hash); + let mut file = File::open(self.index[hash].clone()).await?; let mut remaining = file.metadata().await?.len(); let mut sendbuf = vec![0u8; self.chunksize]; + debug!("({}): Sending bytes of '{}'", addr, hash); + while remaining != 0 { let n = file.read(&mut sendbuf).await?; - handler.send(&sendbuf[..n].to_vec()).await?; + handler.send(&sendbuf[..n]).await?; remaining -= n as u64; + + debug!("({}): {} bytes remaining", addr, remaining); } let buf = handler.recv().await?; @@ -155,6 +187,8 @@ impl Server { if confirmation != hash { return Err("Unsuccessful file transfer, hashes don't match".into()); } + + debug!("({}): File '{}' successfully transferred", addr, hash); } Ok(()) diff --git a/src/sockets.rs b/src/sockets.rs index f98490f..e92649c 100644 --- a/src/sockets.rs +++ b/src/sockets.rs @@ -1,6 +1,7 @@ use std::error::Error; use base64::{engine::general_purpose, Engine}; +use log::debug; use tokio::{ io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}, net::{ @@ -20,8 +21,8 @@ pub struct SocketHandler<'a> { impl<'a> SocketHandler<'a> { pub fn new(socket: &'a mut TcpStream) -> Self { let (reader, writer) = socket.split(); - let mut reader = BufReader::new(reader); - let mut writer = BufWriter::new(writer); + let reader = BufReader::new(reader); + let writer = BufWriter::new(writer); Self { writer, @@ -30,19 +31,26 @@ impl<'a> SocketHandler<'a> { } } - pub fn set_crypto(&self, crypto: Crypto) { + pub fn set_crypto(&mut self, crypto: Crypto) { // setting up AES cipher requires DH key exchange in plaintext, // meaning crypto can't be initialized at the same time as the socket handler + debug!("Cryptography module initialized to the connection"); self.crypto = Some(crypto); } pub async fn send(&mut self, data: &[u8]) -> Result<(), Box> { - let data = match &self.crypto { + let data = match &mut self.crypto { Some(c) => c.encrypt(data).await?, - None => data.to_vec(), + None => data.to_vec(), // syntactic sugar, never actually called }; - self.send_raw(&data).await?; + let mut encoded = general_purpose::STANDARD_NO_PAD + .encode(data) + .as_bytes() + .to_vec(); + encoded.push(b':'); + + self.send_raw(&encoded).await?; Ok(()) } @@ -51,6 +59,8 @@ impl<'a> SocketHandler<'a> { self.writer.write_all(data).await?; self.writer.flush().await?; + debug!("Sent {} bytes to the socket", data.len()); + Ok(()) } @@ -75,6 +85,8 @@ impl<'a> SocketHandler<'a> { return Err("Received 0 bytes from the socket".into()); } + debug!("Received {} bytes from the socket", buf.len()); + Ok(buf) } } diff --git a/src/util.rs b/src/util.rs index 75c6ef0..fd1ecc6 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, error::Error, fs, net::SocketAddr, path::PathBuf}; +use log::{debug, info}; use tokio::{fs::File, io::BufWriter}; use crate::crypto; @@ -16,6 +17,8 @@ pub enum Ip { impl Ip { pub fn fetch(self, port: u16) -> Result> { + debug!("Fetching IP information"); + let addr = match self { Ip::V4 => PUBLIC_IPV4, Ip::V6 => PUBLIC_IPV6, @@ -28,6 +31,8 @@ impl Ip { let res = format!("{}:{}", ureq::get(addr).call()?.into_string()?.trim(), port); let addr = res.parse::()?; + debug!("IP: {}", res); + Ok(addr) } } @@ -49,6 +54,8 @@ fn filepaths( infile: Option, files: Option>, ) -> Result, Box> { + info!("Collecting filepaths"); + let mut filepaths = Vec::new(); if let Some(infile) = infile { @@ -56,20 +63,22 @@ fn filepaths( for path in paths.lines() { filepaths.push(PathBuf::from(path)); } - } - - if let Some(files) = files { + } else if let Some(files) = files { for file in files { filepaths.push(file); } } + debug!("Filepaths collection finished (total: {})", filepaths.len()); + Ok(filepaths) } pub async fn metadata( files: &Vec, -) -> Result<(Vec<(String, u64, String)>, HashMap), Box> { +) -> Result<(Vec, HashMap), Box> { + info!("Collecting metadata"); + let mut metadata = Vec::new(); let mut index = HashMap::new(); @@ -81,11 +90,19 @@ pub async fn metadata( let hash = crypto::try_hash(path)?; if size > 0 { - metadata.push((name, size, hash.clone())); + debug!("Collecting '{}' metadata", name); + + let info = FileInfo::new(name, size, hash.clone()); + metadata.push(info); index.insert(hash, path.clone()); } } + debug!( + "Metadata collection successfully done (total: {})", + metadata.len() + ); + Ok((metadata, index)) } @@ -93,6 +110,8 @@ pub async fn new_file( mut path: PathBuf, name: &str, ) -> Result<(BufWriter, PathBuf), Box> { + debug!("New file handle for '{}'", name); + path.push(name); let handle = File::create(&path).await?; diff --git a/tests/sockets_integration.rs b/tests/sockets_integration.rs index a612c3d..d799bcc 100644 --- a/tests/sockets_integration.rs +++ b/tests/sockets_integration.rs @@ -1,76 +1,81 @@ -use contego::{common::Message, connector::Connector, crypto, listener::Listener}; -use ntest::timeout; -use rand::{distributions::Alphanumeric, thread_rng, Rng}; use std::{ fs::{self, File}, io::{BufWriter, Write}, - net::SocketAddr, path::PathBuf, str::FromStr, - thread, }; -use tokio::sync::mpsc; -use tokio_test::block_on; -#[test] +use contego::{ + client::Client, + server::Server, + util::{metadata, Ip}, +}; +use env_logger::Env; +use log::debug; +use ntest::timeout; +use rand::{distributions::Alphanumeric, thread_rng, Rng}; +use tokio::{fs::read_to_string, sync::mpsc}; + +#[tokio::test] #[timeout(2000)] -/// Tests communication between UI and individual handlers by mocking signals. -fn filesync_signals() { - let (testdata, paths) = write_testfiles(); +/// Ensures backend communications integrity & the ability to handle individual requests. +async fn sockets_integration() { + env_logger::Builder::from_env(Env::default().default_filter_or("debug")) + .is_test(true) + .try_init() + .unwrap(); + //env_logger::builder().is_test(true).try_init().unwrap(); - let output_path = PathBuf::from("./tests/output/"); - let addr = SocketAddr::from(([127, 0, 0, 1], 9191)); - let key = crypto::keygen(); + debug!("Initializing and starting the test"); + + let (testdata, paths) = testdata(); + let (metadata, index) = metadata(&paths).await.unwrap(); + + let addr = Ip::Local.fetch(8080).unwrap(); + let outdir = PathBuf::from("./tests/output/"); + let key = String::from("testkey"); let c_key = key.clone(); - let (kstx, srx) = mpsc::channel::(10); - let (stx, mut lsrx) = mpsc::channel::(10); - let (lctx, crx) = mpsc::channel::(10); - let (ctx, mut lcrx) = mpsc::channel::(10); + let (tx, rx) = mpsc::channel::<()>(1); - let server_handle = thread::spawn(move || { - let listener = Listener::new(addr, key, 8192usize).unwrap(); - block_on(listener.start(stx, srx, paths)).unwrap(); + let server_handle = tokio::spawn(async move { + debug!("Initializing the asynchronous server task"); + let server = Server::new(addr, key, 8192, metadata, index); + debug!("Starting to listen to incoming connections"); + server.start(rx).await.unwrap(); }); - let server_channel_handle = thread::spawn(move || { - block_on(lsrx.recv()).unwrap(); // ClientConnect - block_on(lsrx.recv()).unwrap(); // ConnectionReady - block_on(lsrx.recv()).unwrap(); // ClientDisconnect - block_on(kstx.send(Message::Shutdown)).unwrap(); + let client_handle = tokio::spawn(async move { + debug!("Initializing the asynchronous client task"); + let client = Client::new(addr, c_key, outdir); + debug!("Connecting to the server"); + client.connection().await.unwrap(); }); - let client_handle = thread::spawn(move || { - let output_path = output_path.clone(); - let connector = Connector::new(addr, c_key, output_path); - block_on(connector.connect(ctx, crx)).unwrap() - }); + client_handle.await.unwrap(); + tx.send(()).await.unwrap(); + server_handle.await.unwrap(); - let client_channel_handle = thread::spawn(move || { - let metadata = block_on(lcrx.recv()).unwrap(); // Metadata(HashMap) - - if let Message::Metadata(inner) = metadata { - assert_eq!(inner.len(), 3); - for (filename, _) in inner { - block_on(lctx.send(Message::ClientReq(filename))).unwrap(); - } - } - - block_on(lctx.send(Message::Shutdown)).unwrap(); - }); - - client_handle.join().unwrap(); - client_channel_handle.join().unwrap(); - server_handle.join().unwrap(); - server_channel_handle.join().unwrap(); + debug!("Checking for file integrity"); for file in testdata { + let path = String::from("./tests/output/") + file.0; + let recv_content = read_to_string(path).await.unwrap(); + + assert_eq!( + recv_content, file.1, + "Output '{}' doesn't match the input '{}'", + recv_content, file.1 + ); + fs::remove_file(String::from("./tests/output/") + file.0).unwrap(); fs::remove_file(String::from("./tests/data/") + file.0).unwrap(); + + debug!("File '{}' checked and removed successfully", file.0); } } -fn write_testfiles() -> (Vec<(&'static str, String)>, Vec) { +fn testdata() -> (Vec<(&'static str, String)>, Vec) { let mut paths = Vec::new(); let testdata = vec![ ("1.txt", generate_data()),