Initial commit

This commit is contained in:
einisto 2022-07-10 22:16:50 +03:00
commit 02f7d74267
9 changed files with 622 additions and 0 deletions

21
.github/workflows/ci.yml vendored Normal file
View File

@ -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

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
/target
data/**
output/**
!output/.placeholder
!data/.placeholder

308
Cargo.lock generated Normal file
View File

@ -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"

15
Cargo.toml Normal file
View File

@ -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"] }

21
LICENSE Normal file
View File

@ -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.

0
data/.placeholder Normal file
View File

0
output/.placeholder Normal file
View File

127
src/bin/client.rs Normal file
View File

@ -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<dyn std::error::Error>> {
// 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::<usize>()
.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::<Vec<&str>>();
let filesize = split[0].trim().parse::<u64>().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(())
}

123
src/bin/server.rs Normal file
View File

@ -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<dyn std::error::Error>> {
// 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::<Vec<&str>>();
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)
}