ruck/src/client.rs

281 lines
8.8 KiB
Rust
Raw Normal View History

2022-02-10 19:52:28 +00:00
use crate::crypto::handshake;
2022-02-12 19:59:04 +00:00
use crate::file::{to_size_string, FileHandle, FileInfo};
2022-02-13 22:45:58 +00:00
use crate::message::{
EncryptedMessage, FileNegotiationPayload, FileTransferPayload, Message, MessageStream,
};
2022-02-06 19:04:36 +00:00
2022-02-12 19:59:04 +00:00
use aes_gcm::Aes256Gcm;
use anyhow::{anyhow, Result};
2022-02-12 01:50:14 +00:00
use blake2::{Blake2s256, Digest};
2022-02-12 19:59:04 +00:00
use bytes::{Bytes, BytesMut};
use futures::future::try_join_all;
2022-02-06 19:04:36 +00:00
use futures::prelude::*;
2022-02-13 22:45:58 +00:00
use futures::stream::FuturesUnordered;
use futures::StreamExt;
2022-02-15 04:01:30 +00:00
use std::collections::HashMap;
use std::ffi::OsStr;
2022-02-07 00:54:36 +00:00
use std::path::PathBuf;
2022-02-15 04:01:30 +00:00
use tokio::fs::File;
use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
2022-02-06 19:04:36 +00:00
use tokio::net::TcpStream;
2022-02-13 22:45:58 +00:00
use tokio::sync::mpsc;
2022-02-12 19:59:04 +00:00
use tokio_util::codec::{FramedRead, LinesCodec};
2022-02-06 19:04:36 +00:00
2022-02-12 01:50:14 +00:00
fn pass_to_bytes(password: &String) -> Bytes {
let mut hasher = Blake2s256::new();
hasher.update(password.as_bytes());
let res = hasher.finalize();
BytesMut::from(&res[..]).freeze()
}
2022-02-10 19:52:28 +00:00
pub async fn send(file_paths: &Vec<PathBuf>, password: &String) -> Result<()> {
2022-02-12 19:59:04 +00:00
// Fail early if there are problems generating file handles
let handles = get_file_handles(file_paths).await?;
2022-02-12 17:18:24 +00:00
2022-02-12 19:59:04 +00:00
// Establish connection to server
2022-02-10 19:52:28 +00:00
let socket = TcpStream::connect("127.0.0.1:8080").await?;
let mut stream = Message::to_stream(socket);
2022-02-12 01:50:14 +00:00
2022-02-12 19:59:04 +00:00
// Complete handshake, returning cipher used for encryption
let (stream, cipher) = handshake(
2022-02-10 19:52:28 +00:00
&mut stream,
Bytes::from(password.to_string()),
2022-02-12 01:50:14 +00:00
pass_to_bytes(password),
2022-02-10 19:52:28 +00:00
)
.await?;
2022-02-12 19:59:04 +00:00
// Complete file negotiation
let handles = negotiate_files_up(handles, stream, &cipher).await?;
// Upload negotiated files
2022-02-15 04:01:30 +00:00
upload_encrypted_files(stream, handles, &cipher).await?;
2022-02-12 19:59:04 +00:00
// Exit
Ok(())
2022-02-10 19:52:28 +00:00
}
pub async fn receive(password: &String) -> Result<()> {
let socket = TcpStream::connect("127.0.0.1:8080").await?;
let mut stream = Message::to_stream(socket);
2022-02-12 19:59:04 +00:00
let (stream, cipher) = handshake(
2022-02-10 19:52:28 +00:00
&mut stream,
Bytes::from(password.to_string()),
2022-02-12 01:50:14 +00:00
pass_to_bytes(password),
2022-02-10 19:52:28 +00:00
)
.await?;
2022-02-12 19:59:04 +00:00
let files = negotiate_files_down(stream, &cipher).await?;
2022-02-15 04:01:30 +00:00
download_files(files, stream, &cipher).await?;
2022-02-10 19:52:28 +00:00
return Ok(());
}
2022-02-12 19:59:04 +00:00
pub async fn get_file_handles(file_paths: &Vec<PathBuf>) -> Result<Vec<FileHandle>> {
let tasks = file_paths
.into_iter()
.map(|path| FileHandle::new(path.to_path_buf()));
let handles = try_join_all(tasks).await?;
Ok(handles)
}
pub async fn negotiate_files_up(
file_handles: Vec<FileHandle>,
stream: &mut MessageStream,
cipher: &Aes256Gcm,
) -> Result<Vec<FileHandle>> {
let files = file_handles.iter().map(|fh| fh.to_file_info()).collect();
let msg = EncryptedMessage::FileNegotiationMessage(FileNegotiationPayload { files });
let server_msg = msg.to_encrypted_message(cipher)?;
stream.send(server_msg).await?;
let reply_payload = match stream.next().await {
Some(Ok(msg)) => match msg {
Message::EncryptedMessage(response) => response,
_ => return Err(anyhow!("Expecting encrypted message back")),
},
_ => {
return Err(anyhow!("No response to negotiation message"));
}
};
let plaintext_reply = EncryptedMessage::from_encrypted_message(cipher, &reply_payload)?;
let requested_paths: Vec<PathBuf> = match plaintext_reply {
EncryptedMessage::FileNegotiationMessage(fnm) => {
fnm.files.into_iter().map(|f| f.path).collect()
}
_ => return Err(anyhow!("Expecting file negotiation message back")),
};
Ok(file_handles
.into_iter()
.filter(|fh| requested_paths.contains(&fh.path))
.collect())
}
2022-02-15 04:01:30 +00:00
pub async fn negotiate_files_down(
stream: &mut MessageStream,
cipher: &Aes256Gcm,
) -> Result<(Vec<FileInfo>)> {
2022-02-12 19:59:04 +00:00
let file_offer = match stream.next().await {
Some(Ok(msg)) => match msg {
Message::EncryptedMessage(response) => response,
_ => return Err(anyhow!("Expecting encrypted message back")),
},
_ => {
return Err(anyhow!("No response to negotiation message"));
}
};
let plaintext_offer = EncryptedMessage::from_encrypted_message(cipher, &file_offer)?;
let requested_infos: Vec<FileInfo> = match plaintext_offer {
EncryptedMessage::FileNegotiationMessage(fnm) => fnm.files,
_ => return Err(anyhow!("Expecting file negotiation message back")),
};
let mut stdin = FramedRead::new(io::stdin(), LinesCodec::new());
let mut files = vec![];
2022-02-15 04:01:30 +00:00
for file_info in requested_infos.into_iter() {
let mut reply = prompt_user_input(&mut stdin, &file_info).await;
2022-02-12 19:59:04 +00:00
while reply.is_none() {
2022-02-15 04:01:30 +00:00
reply = prompt_user_input(&mut stdin, &file_info).await;
2022-02-12 19:59:04 +00:00
}
match reply {
2022-02-15 04:01:30 +00:00
Some(true) => files.push(file_info),
2022-02-12 19:59:04 +00:00
_ => {}
}
}
2022-02-15 04:01:30 +00:00
let msg = EncryptedMessage::FileNegotiationMessage(FileNegotiationPayload {
files: files.clone(),
});
2022-02-12 19:59:04 +00:00
let server_msg = msg.to_encrypted_message(cipher)?;
stream.send(server_msg).await?;
2022-02-15 04:01:30 +00:00
Ok(files)
2022-02-12 19:59:04 +00:00
}
2022-02-10 19:52:28 +00:00
pub async fn upload_encrypted_files(
stream: &mut MessageStream,
2022-02-13 22:45:58 +00:00
handles: Vec<FileHandle>,
cipher: &Aes256Gcm,
2022-02-10 19:52:28 +00:00
) -> Result<()> {
2022-02-13 22:45:58 +00:00
let (tx, mut rx) = mpsc::unbounded_channel::<EncryptedMessage>();
for mut handle in handles {
let txc = tx.clone();
tokio::spawn(async move {
let _ = enqueue_file_chunks(&mut handle, txc).await;
});
}
loop {
tokio::select! {
Some(msg) = rx.recv() => {
println!("message received to client.rx {:?}", msg);
let x = msg.to_encrypted_message(cipher)?;
stream.send(x).await?
}
else => break,
}
}
Ok(())
}
const BUFFER_SIZE: usize = 1024 * 64;
pub async fn enqueue_file_chunks(
fh: &mut FileHandle,
tx: mpsc::UnboundedSender<EncryptedMessage>,
) -> Result<()> {
2022-02-15 04:01:30 +00:00
let mut chunk_num = 0;
let mut buf: BytesMut;
while {
buf = BytesMut::with_capacity(BUFFER_SIZE);
let n = fh.file.read_exact(&mut buf[..]).await?;
n == 0
} {
let chunk = buf.freeze();
let file_info = fh.to_file_info();
let ftp = EncryptedMessage::FileTransferMessage(FileTransferPayload {
chunk,
chunk_num,
file_info,
});
tx.send(ftp)?;
chunk_num += 1;
}
2022-02-13 22:45:58 +00:00
Ok(())
2022-02-06 19:04:36 +00:00
}
2022-02-12 19:59:04 +00:00
2022-02-15 04:01:30 +00:00
pub async fn download_files(
file_infos: Vec<FileInfo>,
stream: &mut MessageStream,
cipher: &Aes256Gcm,
) -> Result<()> {
// for each file_info
let info_handles: HashMap<_, _> = file_infos
.into_iter()
.map(|fi| {
let (tx, rx) = mpsc::unbounded_channel::<(u64, Bytes)>();
let path = fi.path.clone();
tokio::spawn(async move { download_file(fi, rx).await });
(path, tx)
})
.collect();
loop {
tokio::select! {
result = stream.next() => match result {
Some(Ok(Message::EncryptedMessage(payload))) => {
let ec = EncryptedMessage::from_encrypted_message(cipher, &payload)?;
match ec {
EncryptedMessage::FileTransferMessage(payload) => {
if let Some(tx) = info_handles.get(&payload.file_info.path) {
tx.send((payload.chunk_num, payload.chunk))?
};
},
_ => {println!("wrong msg")}
}
}
Some(Ok(_)) => {
println!("wrong msg");
}
Some(Err(e)) => {
println!("Error {:?}", e);
}
None => break,
}
}
}
Ok(())
}
pub async fn download_file(
file_info: FileInfo,
rx: mpsc::UnboundedReceiver<(u64, Bytes)>,
) -> Result<()> {
let mut rx = rx;
let filename = match file_info.path.file_name() {
Some(f) => f,
None => OsStr::new("random.txt"),
};
let mut file = File::open(filename).await?;
while let Some((chunk_num, chunk)) = rx.recv().await {
file.write_all(&chunk).await?;
}
Ok(())
}
2022-02-12 19:59:04 +00:00
pub async fn prompt_user_input(
stdin: &mut FramedRead<io::Stdin, LinesCodec>,
file_info: &FileInfo,
) -> Option<bool> {
let prompt_name = file_info.path.file_name().unwrap();
println!(
2022-02-15 04:01:30 +00:00
"Accept {:?}? ({:?}). (Y/n)",
2022-02-12 19:59:04 +00:00
prompt_name,
to_size_string(file_info.size)
);
match stdin.next().await {
Some(Ok(line)) => match line.as_str() {
"" | "Y" | "y" | "yes" | "Yes" | "YES" => Some(true),
"N" | "n" | "NO" | "no" | "No" => Some(false),
_ => {
println!("Invalid input. Please enter one of the following characters: [YyNn]");
return None;
}
},
_ => None,
}
}