commit 02f7d742675c89385bd0752a1127d19a05581f00 Author: einisto <79069176+einisto@users.noreply.github.com> Date: Sun Jul 10 22:16:50 2022 +0300 Initial commit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..df6d0de --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: Cargo Build & Test + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build_and_test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..647fa9f --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/target + +data/** +output/** + +!output/.placeholder +!data/.placeholder \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f63cf28 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,308 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac8b1a9b2518dc799a2271eff1688707eb315f0d4697aa6b0871369ca4c4da55" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-socket-test" +version = "0.1.0" +dependencies = [ + "tokio", +] + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c631564 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "tokio-socket-test" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "server" + +[[bin]] +name = "client" + +[dependencies] +tokio = { version = "1.19.2", features = ["full"] } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7bb08cb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 einisto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/data/.placeholder b/data/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/output/.placeholder b/output/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/src/bin/client.rs b/src/bin/client.rs new file mode 100644 index 0000000..ef86662 --- /dev/null +++ b/src/bin/client.rs @@ -0,0 +1,127 @@ +use std::time::Duration; +use tokio::{ + fs::File, + io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}, + net::TcpStream, + time::sleep, +}; + +// TODO: Remove panics/unwraps & add proper error handling + +const BUFFERSIZE: usize = 8192; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // TODO: Clap + let addr = "127.0.0.1:8080"; + let mut stream = TcpStream::connect(addr).await?; + println!("[+] Connecting to {}", addr); + + let (reader, writer) = stream.split(); + let mut reader = BufReader::new(reader); + let mut writer = BufWriter::new(writer); + + let mut buf = Vec::new(); + + loop { + let bytes_read = reader.read_buf(&mut buf).await.unwrap(); + if bytes_read == 0 { + println!("[-] No more bytes received, disconnecting from the server..."); + break; + } + + // Receive file amount + let file_amount = String::from_utf8(buf.clone()) + .unwrap() + .parse::() + .unwrap(); + println!("[+] Total of {} files available", file_amount); + buf.clear(); + + // ACK file amount + writer.write_all(b"ACK").await.unwrap(); + writer.flush().await.unwrap(); + + // Receive file metadata + println!("[+] Receiving file metadata"); + let mut metadata = Vec::<(String, u64)>::new(); + while metadata.len() < file_amount { + reader.read_until(b'\n', &mut buf).await.unwrap(); + let msg = String::from_utf8(buf.clone()).unwrap(); + buf.clear(); + + // Parse 'filesize:filename' + let split = msg.split(":").collect::>(); + let filesize = split[0].trim().parse::().unwrap(); + let filename = split[1].trim().to_string(); + + metadata.push((filename, filesize)); + } + println!("[INFO] Metadata: {:?}", metadata); + + // Send request for each file by filename + println!("[+] Requesting files individually"); // TODO: Choose files based on input + for file in &metadata { + println!("[INFO] Current request: [{:?}]", file); + writer.write_all(file.0.as_bytes()).await.unwrap(); + writer.flush().await.unwrap(); + + // Create file locally (./output/) + let output_path = String::from("./output/") + file.0.as_str(); + + let output_file = File::create(output_path.clone()).await.unwrap(); + println!("[+] New file: {}", output_path); + let mut file_buf = BufWriter::new(output_file); + + // Receive the file itself + let mut remaining_data = file.1; + let mut buf = [0u8; BUFFERSIZE]; + + while remaining_data != 0 { + if remaining_data >= BUFFERSIZE as u64 { + let read_result = reader.read(&mut buf); + + match read_result.await { + Ok(0) => { + println!("[-] Waiting for data to become available..."); + sleep(Duration::from_secs(5)).await; + continue; + } + Ok(n) => { + file_buf.write_all(&mut buf).await.unwrap(); + file_buf.flush().await.unwrap(); + remaining_data = remaining_data - n as u64; + } + _ => {} + } + } else { + let read_result = reader.read(&mut buf); + + match read_result.await { + Ok(_) => { + let mut buf_slice = &buf[0..(remaining_data as usize)]; + file_buf.write_all(&mut buf_slice).await.unwrap(); + file_buf.flush().await.unwrap(); + remaining_data = 0; + } + _ => {} + } + } + } + + // ACK file + writer.write_all(b"ACK").await.unwrap(); + writer.flush().await.unwrap(); + println!( + "[+] Successfully wrote {} bytes to {}\n", + file.1, output_path + ); + } + + println!("[+] All finished, disconnecting..."); + writer.write_all(b"FIN").await.unwrap(); + writer.flush().await.unwrap(); + } + + Ok(()) +} diff --git a/src/bin/server.rs b/src/bin/server.rs new file mode 100644 index 0000000..c5434a8 --- /dev/null +++ b/src/bin/server.rs @@ -0,0 +1,123 @@ +use std::fs::read_dir; +use tokio::{ + self, + fs::File, + io::{AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}, + net::TcpListener, +}; + +// TODO: Remove panics/unwraps & add proper error handling + +const BUFFERSIZE: usize = 8192; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // TODO: Clap + let addr = "127.0.0.1:8080"; + let listener = TcpListener::bind(addr).await?; + println!("[+] Listening on {}", addr); + + loop { + let (mut socket, addr) = listener.accept().await?; + println!("[+] New client: {}", addr); + + tokio::spawn(async move { + let (reader, writer) = socket.split(); + let mut reader = BufReader::new(reader); + let mut writer = BufWriter::new(writer); + + let mut vec_buf = Vec::new(); + + let (metadata_list, file_amount) = get_metadata().await; + + // Send file amount + writer + .write_all(file_amount.to_string().as_bytes()) + .await + .unwrap(); + writer.flush().await.unwrap(); + + // Read ACK + let _bytes_read = reader.read_buf(&mut vec_buf).await.unwrap(); + if String::from_utf8(vec_buf.clone()).unwrap() != "ACK" { + panic!("ACK not received (amount)"); + } else { + vec_buf.clear(); + } + + // Send file metadata + for file in &metadata_list { + // Newline as delimiter between instances + let msg = format!("{}:{}\n", file.1, file.0); + writer.write_all(msg.as_bytes()).await.unwrap(); + writer.flush().await.unwrap(); + } + + // Handle file request(s) + println!("[+] Ready to serve files"); + loop { + let bytes_read = reader.read_buf(&mut vec_buf).await.unwrap(); + + if bytes_read == 0 { + println!("File request never received"); + break; + } else { + let msg = String::from_utf8(vec_buf.clone()).unwrap(); + vec_buf.clear(); + + if msg == "FIN" { + println!("[+] FIN received, terminating connection..."); + break; + } + + let input_path = String::from("./data/") + msg.as_str(); + + println!("[+] File requested: {}", input_path); + let mut file = File::open(input_path.clone()).await.unwrap(); + let mut remaining_data = file.metadata().await.unwrap().len(); + let mut filebuf = [0u8; BUFFERSIZE]; + + while remaining_data != 0 { + let read_result = file.read(&mut filebuf); + match read_result.await { + Ok(n) => { + writer.write_all(&filebuf).await.unwrap(); + writer.flush().await.unwrap(); + remaining_data = remaining_data - n as u64; + } + _ => {} + } + } + } + + // Read ACK + let _bytes_read = reader.read_buf(&mut vec_buf).await.unwrap(); + if String::from_utf8(vec_buf.clone()).unwrap() != "ACK" { + panic!("ACK not received (amount)"); + } else { + println!("[+] File transfer successfully done\n"); + vec_buf.clear(); + } + } + }); + } +} + +async fn get_metadata() -> (Vec<(String, u64)>, usize) { + let mut metadata = Vec::<(String, u64)>::new(); + let paths = read_dir("./data").unwrap(); + + for filename in paths { + let filepath = filename.unwrap().path().display().to_string(); // ???? + let split = filepath.split("/").collect::>(); + let filename = split[split.len() - 1].to_string(); + let file = File::open(filepath).await.unwrap(); + let filesize = file.metadata().await.unwrap().len(); + + metadata.push((filename, filesize)); + } + + let amount = metadata.len(); + + (metadata, amount) +}