Finish implementation
This commit is contained in:
parent
c345a5b531
commit
0df85e156d
7 changed files with 404 additions and 180 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -4,4 +4,4 @@ version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tftpd"
|
name = "tftpd"
|
||||||
version = "0.0.0"
|
version = "0.1.0"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "tftpd"
|
name = "tftpd"
|
||||||
version = "0.0.0"
|
version = "0.1.0"
|
||||||
authors = ["Altuğ Bakan <mail@alt.ug>"]
|
authors = ["Altuğ Bakan <mail@alt.ug>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "TFTP Server Daemon implemented in Rust"
|
description = "TFTP Server Daemon implemented in Rust"
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@ use tftpd::{Config, Server};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let config = Config::new(env::args()).unwrap_or_else(|err| {
|
let config = Config::new(env::args()).unwrap_or_else(|err| {
|
||||||
eprintln!("Problem parsing arguments: {}", err);
|
eprintln!("Problem parsing arguments: {err}");
|
||||||
process::exit(1)
|
process::exit(1)
|
||||||
});
|
});
|
||||||
|
|
||||||
let server = Server::new(&config).unwrap_or_else(|err| {
|
let server = Server::new(&config).unwrap_or_else(|err| {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"Problem creating server on {}:{}: {}",
|
"Problem creating server on {}:{}: {err}",
|
||||||
config.ip_address, config.port, err
|
config.ip_address, config.port
|
||||||
);
|
);
|
||||||
process::exit(1)
|
process::exit(1)
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,28 @@ use crate::packet::{ErrorCode, Opcode, Packet, TransferOption};
|
||||||
|
|
||||||
pub struct Message;
|
pub struct Message;
|
||||||
|
|
||||||
|
const MAX_REQUEST_PACKET_SIZE: usize = 512;
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
pub fn send_data(socket: &UdpSocket, data: &[u8]) -> Result<(), Box<dyn Error>> {
|
pub fn send_data(
|
||||||
let buf = [&Opcode::Data.as_bytes()[..], data].concat();
|
socket: &UdpSocket,
|
||||||
|
block_number: u16,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
let buf = [
|
||||||
|
&Opcode::Data.as_bytes()[..],
|
||||||
|
&block_number.to_be_bytes(),
|
||||||
|
data,
|
||||||
|
]
|
||||||
|
.concat();
|
||||||
|
|
||||||
socket.send(&buf)?;
|
socket.send(&buf)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_ack(socket: &UdpSocket, block: u16) -> Result<(), Box<dyn Error>> {
|
pub fn send_ack(socket: &UdpSocket, block_number: u16) -> Result<(), Box<dyn Error>> {
|
||||||
let buf = [Opcode::Ack.as_bytes(), block.to_be_bytes()].concat();
|
let buf = [Opcode::Ack.as_bytes(), block_number.to_be_bytes()].concat();
|
||||||
|
|
||||||
socket.send(&buf)?;
|
socket.send(&buf)?;
|
||||||
|
|
||||||
|
|
@ -29,16 +40,21 @@ impl Message {
|
||||||
code: ErrorCode,
|
code: ErrorCode,
|
||||||
msg: &str,
|
msg: &str,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
socket.send(&get_error_buf(code, msg))?;
|
socket.send(&build_error_buf(code, msg))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_error_to(socket: &UdpSocket, to: &SocketAddr, code: ErrorCode, msg: &str) {
|
pub fn send_error_to<'a>(
|
||||||
eprintln!("{msg}");
|
socket: &UdpSocket,
|
||||||
if socket.send_to(&get_error_buf(code, msg), to).is_err() {
|
to: &SocketAddr,
|
||||||
|
code: ErrorCode,
|
||||||
|
msg: &'a str,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
if socket.send_to(&build_error_buf(code, msg), to).is_err() {
|
||||||
eprintln!("could not send an error message");
|
eprintln!("could not send an error message");
|
||||||
}
|
}
|
||||||
|
Err(msg.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_oack(
|
pub fn send_oack(
|
||||||
|
|
@ -56,19 +72,32 @@ impl Message {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn receive_ack(socket: &UdpSocket) -> Result<u16, Box<dyn Error>> {
|
pub fn recv(socket: &UdpSocket) -> Result<Packet, Box<dyn Error>> {
|
||||||
let mut buf = [0; 4];
|
let mut buf = [0; MAX_REQUEST_PACKET_SIZE];
|
||||||
socket.recv(&mut buf)?;
|
let number_of_bytes = socket.recv(&mut buf)?;
|
||||||
|
let packet = Packet::deserialize(&buf[..number_of_bytes])?;
|
||||||
|
|
||||||
if let Ok(Packet::Ack(block)) = Packet::deserialize(&buf) {
|
Ok(packet)
|
||||||
Ok(block)
|
}
|
||||||
} else {
|
|
||||||
Err("invalid ack".into())
|
pub fn recv_data(socket: &UdpSocket, size: usize) -> Result<Packet, Box<dyn Error>> {
|
||||||
}
|
let mut buf = vec![0; size + 4];
|
||||||
|
let number_of_bytes = socket.recv(&mut buf)?;
|
||||||
|
let packet = Packet::deserialize(&buf[..number_of_bytes])?;
|
||||||
|
|
||||||
|
Ok(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recv_from(socket: &UdpSocket) -> Result<(Packet, SocketAddr), Box<dyn Error>> {
|
||||||
|
let mut buf = [0; MAX_REQUEST_PACKET_SIZE];
|
||||||
|
let (number_of_bytes, from) = socket.recv_from(&mut buf)?;
|
||||||
|
let packet = Packet::deserialize(&buf[..number_of_bytes])?;
|
||||||
|
|
||||||
|
Ok((packet, from))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_error_buf(code: ErrorCode, msg: &str) -> Vec<u8> {
|
fn build_error_buf(code: ErrorCode, msg: &str) -> Vec<u8> {
|
||||||
[
|
[
|
||||||
&Opcode::Error.as_bytes()[..],
|
&Opcode::Error.as_bytes()[..],
|
||||||
&code.as_bytes()[..],
|
&code.as_bytes()[..],
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::Convert;
|
use crate::Convert;
|
||||||
use std::error::Error;
|
use std::{error::Error, fmt};
|
||||||
|
|
||||||
pub enum Packet<'a> {
|
pub enum Packet {
|
||||||
Rrq {
|
Rrq {
|
||||||
filename: String,
|
filename: String,
|
||||||
mode: String,
|
mode: String,
|
||||||
|
|
@ -14,7 +14,7 @@ pub enum Packet<'a> {
|
||||||
},
|
},
|
||||||
Data {
|
Data {
|
||||||
block_num: u16,
|
block_num: u16,
|
||||||
data: &'a [u8],
|
data: Vec<u8>,
|
||||||
},
|
},
|
||||||
Ack(u16),
|
Ack(u16),
|
||||||
Error {
|
Error {
|
||||||
|
|
@ -23,9 +23,9 @@ pub enum Packet<'a> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Packet<'a> {
|
impl Packet {
|
||||||
pub fn deserialize(buf: &'a [u8]) -> Result<Packet, Box<dyn Error>> {
|
pub fn deserialize(buf: &[u8]) -> Result<Packet, Box<dyn Error>> {
|
||||||
let opcode = Opcode::from_u16(Convert::to_u16(&buf[0..1])?)?;
|
let opcode = Opcode::from_u16(Convert::to_u16(&buf[0..=1])?)?;
|
||||||
|
|
||||||
match opcode {
|
match opcode {
|
||||||
Opcode::Rrq | Opcode::Wrq => parse_rq(buf, opcode),
|
Opcode::Rrq | Opcode::Wrq => parse_rq(buf, opcode),
|
||||||
|
|
@ -66,7 +66,7 @@ impl Opcode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct TransferOption {
|
pub struct TransferOption {
|
||||||
pub option: OptionType,
|
pub option: OptionType,
|
||||||
pub value: usize,
|
pub value: usize,
|
||||||
|
|
@ -84,7 +84,7 @@ impl TransferOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum OptionType {
|
pub enum OptionType {
|
||||||
BlockSize,
|
BlockSize,
|
||||||
TransferSize,
|
TransferSize,
|
||||||
|
|
@ -100,10 +100,6 @@ impl OptionType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_bytes(&self) -> &[u8] {
|
|
||||||
self.as_str().as_bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_str(value: &str) -> Result<Self, &'static str> {
|
fn from_str(value: &str) -> Result<Self, &'static str> {
|
||||||
match value {
|
match value {
|
||||||
"blksize" => Ok(OptionType::BlockSize),
|
"blksize" => Ok(OptionType::BlockSize),
|
||||||
|
|
@ -147,6 +143,21 @@ impl ErrorCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ErrorCode {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
ErrorCode::NotDefined => write!(f, "Not Defined"),
|
||||||
|
ErrorCode::FileNotFound => write!(f, "File Not Found"),
|
||||||
|
ErrorCode::AccessViolation => write!(f, "Access Violation"),
|
||||||
|
ErrorCode::DiskFull => write!(f, "Disk Full"),
|
||||||
|
ErrorCode::IllegalOperation => write!(f, "Illegal Operation"),
|
||||||
|
ErrorCode::UnknownId => write!(f, "Unknown ID"),
|
||||||
|
ErrorCode::FileExists => write!(f, "File Exists"),
|
||||||
|
ErrorCode::NoSuchUser => write!(f, "No Such User"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_rq(buf: &[u8], opcode: Opcode) -> Result<Packet, Box<dyn Error>> {
|
fn parse_rq(buf: &[u8], opcode: Opcode) -> Result<Packet, Box<dyn Error>> {
|
||||||
let mut options = vec![];
|
let mut options = vec![];
|
||||||
let filename: String;
|
let filename: String;
|
||||||
|
|
@ -188,7 +199,7 @@ fn parse_rq(buf: &[u8], opcode: Opcode) -> Result<Packet, Box<dyn Error>> {
|
||||||
fn parse_data(buf: &[u8]) -> Result<Packet, Box<dyn Error>> {
|
fn parse_data(buf: &[u8]) -> Result<Packet, Box<dyn Error>> {
|
||||||
Ok(Packet::Data {
|
Ok(Packet::Data {
|
||||||
block_num: Convert::to_u16(&buf[2..])?,
|
block_num: Convert::to_u16(&buf[2..])?,
|
||||||
data: &buf[4..],
|
data: buf[4..].to_vec(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,11 +250,11 @@ mod tests {
|
||||||
&[0x00],
|
&[0x00],
|
||||||
&"octet".as_bytes(),
|
&"octet".as_bytes(),
|
||||||
&[0x00],
|
&[0x00],
|
||||||
&OptionType::TransferSize.as_bytes(),
|
&OptionType::TransferSize.as_str().as_bytes(),
|
||||||
&[0x00],
|
&[0x00],
|
||||||
&"0".as_bytes(),
|
&"0".as_bytes(),
|
||||||
&[0x00],
|
&[0x00],
|
||||||
&OptionType::Timeout.as_bytes(),
|
&OptionType::Timeout.as_str().as_bytes(),
|
||||||
&[0x00],
|
&[0x00],
|
||||||
&"5".as_bytes(),
|
&"5".as_bytes(),
|
||||||
&[0x00],
|
&[0x00],
|
||||||
|
|
@ -311,11 +322,11 @@ mod tests {
|
||||||
&[0x00],
|
&[0x00],
|
||||||
&"octet".as_bytes(),
|
&"octet".as_bytes(),
|
||||||
&[0x00],
|
&[0x00],
|
||||||
&OptionType::TransferSize.as_bytes(),
|
&OptionType::TransferSize.as_str().as_bytes(),
|
||||||
&[0x00],
|
&[0x00],
|
||||||
&"12341234".as_bytes(),
|
&"12341234".as_bytes(),
|
||||||
&[0x00],
|
&[0x00],
|
||||||
&OptionType::BlockSize.as_bytes(),
|
&OptionType::BlockSize.as_str().as_bytes(),
|
||||||
&[0x00],
|
&[0x00],
|
||||||
&"1024".as_bytes(),
|
&"1024".as_bytes(),
|
||||||
&[0x00],
|
&[0x00],
|
||||||
|
|
|
||||||
209
src/server.rs
209
src/server.rs
|
|
@ -2,9 +2,7 @@ use crate::packet::{ErrorCode, Packet, TransferOption};
|
||||||
use crate::{Config, Message, Worker};
|
use crate::{Config, Message, Worker};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::net::{SocketAddr, UdpSocket};
|
use std::net::{SocketAddr, UdpSocket};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
const MAX_REQUEST_PACKET_SIZE: usize = 512;
|
|
||||||
|
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
socket: UdpSocket,
|
socket: UdpSocket,
|
||||||
|
|
@ -25,63 +23,86 @@ impl Server {
|
||||||
|
|
||||||
pub fn listen(&self) {
|
pub fn listen(&self) {
|
||||||
loop {
|
loop {
|
||||||
let mut buf = [0; MAX_REQUEST_PACKET_SIZE];
|
if let Ok((packet, from)) = Message::recv_from(&self.socket) {
|
||||||
if let Ok((number_of_bytes, from)) = self.socket.recv_from(&mut buf) {
|
match packet {
|
||||||
if let Ok(packet) = Packet::deserialize(&buf[..number_of_bytes]) {
|
Packet::Rrq {
|
||||||
self.handle_packet(&packet, &from)
|
filename,
|
||||||
}
|
mut options,
|
||||||
|
..
|
||||||
|
} => match self.handle_rrq(filename.clone(), &mut options, &from) {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("Sending {filename} to {from}");
|
||||||
|
}
|
||||||
|
Err(err) => eprintln!("{err}"),
|
||||||
|
},
|
||||||
|
Packet::Wrq {
|
||||||
|
filename,
|
||||||
|
mut options,
|
||||||
|
..
|
||||||
|
} => match self.handle_wrq(filename.clone(), &mut options, &from) {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("Receiving {filename} from {from}");
|
||||||
|
}
|
||||||
|
Err(err) => eprintln!("{err}"),
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
Message::send_error_to(
|
||||||
|
&self.socket,
|
||||||
|
&from,
|
||||||
|
ErrorCode::IllegalOperation,
|
||||||
|
"invalid request",
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|err| eprintln!("{err}"));
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_packet(&self, packet: &Packet, from: &SocketAddr) {
|
fn handle_rrq(
|
||||||
match &packet {
|
&self,
|
||||||
Packet::Rrq {
|
filename: String,
|
||||||
filename, options, ..
|
options: &mut Vec<TransferOption>,
|
||||||
} => self.validate_rrq(filename, options, from),
|
to: &SocketAddr,
|
||||||
Packet::Wrq {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
filename, options, ..
|
match check_file_exists(&get_full_path(&filename, &self.directory), &self.directory) {
|
||||||
} => self.validate_wrq(filename, options, from),
|
|
||||||
_ => {
|
|
||||||
Message::send_error_to(
|
|
||||||
&self.socket,
|
|
||||||
from,
|
|
||||||
ErrorCode::IllegalOperation,
|
|
||||||
"invalid request",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_rrq(&self, filename: &String, options: &Vec<TransferOption>, to: &SocketAddr) {
|
|
||||||
match self.check_file_exists(&Path::new(&filename)) {
|
|
||||||
ErrorCode::FileNotFound => {
|
ErrorCode::FileNotFound => {
|
||||||
Message::send_error_to(
|
return Message::send_error_to(
|
||||||
&self.socket,
|
&self.socket,
|
||||||
to,
|
to,
|
||||||
ErrorCode::FileNotFound,
|
ErrorCode::FileNotFound,
|
||||||
"requested file does not exist",
|
"file does not exist",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ErrorCode::AccessViolation => {
|
ErrorCode::AccessViolation => {
|
||||||
Message::send_error_to(
|
return Message::send_error_to(
|
||||||
&self.socket,
|
&self.socket,
|
||||||
to,
|
to,
|
||||||
ErrorCode::AccessViolation,
|
ErrorCode::AccessViolation,
|
||||||
"requested file is not in the directory",
|
"file access violation",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ErrorCode::FileExists => self
|
ErrorCode::FileExists => Worker::send(
|
||||||
.handle_rrq(filename, options, to)
|
self.socket.local_addr().unwrap(),
|
||||||
.unwrap_or_else(|err| eprintln!("could not handle read request: {err}")),
|
*to,
|
||||||
|
filename,
|
||||||
|
options.to_vec(),
|
||||||
|
),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_wrq(&self, filename: &String, options: &Vec<TransferOption>, to: &SocketAddr) {
|
fn handle_wrq(
|
||||||
match self.check_file_exists(&Path::new(&filename)) {
|
&self,
|
||||||
|
filename: String,
|
||||||
|
options: &mut Vec<TransferOption>,
|
||||||
|
to: &SocketAddr,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
match check_file_exists(&get_full_path(&filename, &self.directory), &self.directory) {
|
||||||
ErrorCode::FileExists => {
|
ErrorCode::FileExists => {
|
||||||
Message::send_error_to(
|
return Message::send_error_to(
|
||||||
&self.socket,
|
&self.socket,
|
||||||
to,
|
to,
|
||||||
ErrorCode::FileExists,
|
ErrorCode::FileExists,
|
||||||
|
|
@ -89,53 +110,85 @@ impl Server {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ErrorCode::AccessViolation => {
|
ErrorCode::AccessViolation => {
|
||||||
Message::send_error_to(
|
return Message::send_error_to(
|
||||||
&self.socket,
|
&self.socket,
|
||||||
to,
|
to,
|
||||||
ErrorCode::AccessViolation,
|
ErrorCode::AccessViolation,
|
||||||
"requested file is not in the directory",
|
"file access violation",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ErrorCode::FileNotFound => self
|
ErrorCode::FileNotFound => Worker::receive(
|
||||||
.handle_wrq(filename, options, to)
|
self.socket.local_addr().unwrap(),
|
||||||
.unwrap_or_else(|err| eprintln!("could not handle write request: {err}")),
|
*to,
|
||||||
|
filename,
|
||||||
|
options.to_vec(),
|
||||||
|
),
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_rrq(
|
|
||||||
&self,
|
|
||||||
filename: &String,
|
|
||||||
options: &Vec<TransferOption>,
|
|
||||||
to: &SocketAddr,
|
|
||||||
) -> Result<(), Box<dyn Error>> {
|
|
||||||
let mut worker = Worker::new(&self.socket.local_addr().unwrap(), to)?;
|
|
||||||
worker.send_file(Path::new(&filename), options)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fn handle_wrq(
|
|
||||||
&self,
|
fn check_file_exists(file: &PathBuf, directory: &PathBuf) -> ErrorCode {
|
||||||
filename: &String,
|
if !validate_file_path(file, directory) {
|
||||||
options: &Vec<TransferOption>,
|
return ErrorCode::AccessViolation;
|
||||||
to: &SocketAddr,
|
}
|
||||||
) -> Result<(), Box<dyn Error>> {
|
|
||||||
let mut worker = Worker::new(&self.socket.local_addr().unwrap(), to)?;
|
if !file.exists() {
|
||||||
worker.receive_file(Path::new(&filename), options)?;
|
return ErrorCode::FileNotFound;
|
||||||
|
}
|
||||||
Ok(())
|
|
||||||
}
|
ErrorCode::FileExists
|
||||||
|
}
|
||||||
fn check_file_exists(&self, file: &Path) -> ErrorCode {
|
|
||||||
if !file.ancestors().any(|a| a == &self.directory) {
|
fn validate_file_path(file: &PathBuf, directory: &PathBuf) -> bool {
|
||||||
return ErrorCode::AccessViolation;
|
!file.to_str().unwrap().contains("..") && file.ancestors().any(|a| a == directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !file.exists() {
|
fn get_full_path(filename: &str, directory: &PathBuf) -> PathBuf {
|
||||||
return ErrorCode::FileNotFound;
|
let mut file = directory.clone();
|
||||||
}
|
file.push(PathBuf::from(filename));
|
||||||
|
file
|
||||||
ErrorCode::FileExists
|
}
|
||||||
}
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gets_full_path() {
|
||||||
|
assert_eq!(
|
||||||
|
get_full_path("test.txt", &PathBuf::from("/dir/test")),
|
||||||
|
PathBuf::from("/dir/test/test.txt")
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_full_path("some_dir/test.txt", &PathBuf::from("/dir/test")),
|
||||||
|
PathBuf::from("/dir/test/some_dir/test.txt")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validates_file_path() {
|
||||||
|
assert!(validate_file_path(
|
||||||
|
&PathBuf::from("/dir/test/file"),
|
||||||
|
&PathBuf::from("/dir/test")
|
||||||
|
));
|
||||||
|
|
||||||
|
assert!(!validate_file_path(
|
||||||
|
&PathBuf::from("/system/data.txt"),
|
||||||
|
&PathBuf::from("/dir/test")
|
||||||
|
));
|
||||||
|
|
||||||
|
assert!(!validate_file_path(
|
||||||
|
&PathBuf::from("~/some_data.txt"),
|
||||||
|
&PathBuf::from("/dir/test")
|
||||||
|
));
|
||||||
|
|
||||||
|
assert!(!validate_file_path(
|
||||||
|
&PathBuf::from("/dir/test/../file"),
|
||||||
|
&PathBuf::from("/dir/test")
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
257
src/worker.rs
257
src/worker.rs
|
|
@ -1,111 +1,242 @@
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::Read,
|
io::{Read, Write},
|
||||||
net::{SocketAddr, UdpSocket},
|
net::{SocketAddr, UdpSocket},
|
||||||
path::Path,
|
path::Path,
|
||||||
|
thread,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
packet::{OptionType, TransferOption},
|
packet::{ErrorCode, OptionType, Packet, TransferOption},
|
||||||
Message,
|
Message,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Worker {
|
pub struct Worker;
|
||||||
socket: UdpSocket,
|
|
||||||
|
pub struct WorkerOptions {
|
||||||
blk_size: usize,
|
blk_size: usize,
|
||||||
t_size: usize,
|
t_size: usize,
|
||||||
timeout: usize,
|
timeout: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
enum WorkType {
|
||||||
|
Receive,
|
||||||
|
Send(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_RETRIES: u32 = 6;
|
const MAX_RETRIES: u32 = 6;
|
||||||
|
const DEFAULT_TIMEOUT_SECS: u64 = 5;
|
||||||
|
const DEFAULT_BLOCK_SIZE: usize = 512;
|
||||||
|
|
||||||
impl Worker {
|
impl Worker {
|
||||||
pub fn new(addr: &SocketAddr, remote: &SocketAddr) -> Result<Worker, Box<dyn Error>> {
|
pub fn send(
|
||||||
let socket = UdpSocket::bind(SocketAddr::from((addr.ip(), 0)))?;
|
addr: SocketAddr,
|
||||||
socket.connect(remote)?;
|
remote: SocketAddr,
|
||||||
Ok(Worker {
|
filename: String,
|
||||||
socket,
|
mut options: Vec<TransferOption>,
|
||||||
blk_size: 512,
|
) {
|
||||||
t_size: 0,
|
thread::spawn(move || {
|
||||||
timeout: 5,
|
let mut handle_send = || -> Result<(), Box<dyn Error>> {
|
||||||
})
|
let socket = setup_socket(&addr, &remote)?;
|
||||||
|
let work_type = WorkType::Send(Path::new(&filename).metadata().unwrap().len());
|
||||||
|
let worker_options = parse_options(&mut options, &work_type)?;
|
||||||
|
send_oack(&socket, &options, &work_type)?;
|
||||||
|
send_file(&socket, &worker_options, &filename, &mut options)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = handle_send() {
|
||||||
|
eprintln!("{err}");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_file(
|
pub fn receive(
|
||||||
&mut self,
|
addr: SocketAddr,
|
||||||
file: &Path,
|
remote: SocketAddr,
|
||||||
options: &Vec<TransferOption>,
|
filename: String,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
mut options: Vec<TransferOption>,
|
||||||
let mut file = File::open(file).unwrap();
|
) {
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut handle_receive = || -> Result<(), Box<dyn Error>> {
|
||||||
|
let socket = setup_socket(&addr, &remote)?;
|
||||||
|
let work_type = WorkType::Receive;
|
||||||
|
let worker_options = parse_options(&mut options, &work_type)?;
|
||||||
|
send_oack(&socket, &options, &work_type)?;
|
||||||
|
receive_file(&socket, &worker_options, &filename, &mut options)?;
|
||||||
|
|
||||||
self.parse_options(options, Some(&file));
|
Ok(())
|
||||||
Message::send_oack(&self.socket, options)?;
|
};
|
||||||
|
|
||||||
self.socket
|
if let Err(err) = handle_receive() {
|
||||||
.set_write_timeout(Some(Duration::from_secs(self.timeout as u64)))?;
|
eprintln!("{err}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_file(
|
||||||
|
socket: &UdpSocket,
|
||||||
|
worker_options: &WorkerOptions,
|
||||||
|
filename: &String,
|
||||||
|
options: &mut Vec<TransferOption>,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut file = File::open(filename).unwrap();
|
||||||
|
|
||||||
|
parse_options(options, &WorkType::Send(file.metadata().unwrap().len()))?;
|
||||||
|
|
||||||
|
let mut block_number = 1;
|
||||||
|
loop {
|
||||||
|
let mut chunk = vec![0; worker_options.blk_size];
|
||||||
|
let size = file.read(&mut chunk)?;
|
||||||
|
|
||||||
let mut retry_cnt = 0;
|
let mut retry_cnt = 0;
|
||||||
loop {
|
loop {
|
||||||
let mut chunk = Vec::with_capacity(self.blk_size);
|
Message::send_data(socket, block_number, &chunk[..size])?;
|
||||||
let size = file
|
|
||||||
.by_ref()
|
|
||||||
.take(self.blk_size as u64)
|
|
||||||
.read_to_end(&mut chunk)?;
|
|
||||||
|
|
||||||
loop {
|
match Message::recv(socket) {
|
||||||
if Message::send_data(&self.socket, &chunk).is_err() {
|
Ok(Packet::Ack(received_block_number)) => {
|
||||||
return Err(format!("failed to send data").into());
|
if received_block_number == block_number {
|
||||||
|
block_number = block_number.wrapping_add(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Ok(Packet::Error { code, msg }) => {
|
||||||
if let Ok(block) = Message::receive_ack(&self.socket) {
|
return Err(format!("received error code {code}, with message {msg}").into());
|
||||||
todo!("handle block number");
|
}
|
||||||
} else {
|
_ => {
|
||||||
retry_cnt += 1;
|
retry_cnt += 1;
|
||||||
if retry_cnt == MAX_RETRIES {
|
if retry_cnt == MAX_RETRIES {
|
||||||
return Err(format!("transfer timed out after {MAX_RETRIES} tries").into());
|
return Err(format!("transfer timed out after {MAX_RETRIES} tries").into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if size < self.blk_size {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
if size < worker_options.blk_size {
|
||||||
|
break;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn receive_file(
|
println!("Sent {filename} to {}", socket.peer_addr().unwrap());
|
||||||
&mut self,
|
Ok(())
|
||||||
file: &Path,
|
}
|
||||||
options: &Vec<TransferOption>,
|
|
||||||
) -> Result<(), Box<dyn Error>> {
|
|
||||||
let mut file = File::open(file).unwrap();
|
|
||||||
|
|
||||||
self.parse_options(options, Some(&file));
|
fn receive_file(
|
||||||
Message::send_oack(&self.socket, options)?;
|
socket: &UdpSocket,
|
||||||
|
worker_options: &WorkerOptions,
|
||||||
|
filename: &String,
|
||||||
|
options: &mut Vec<TransferOption>,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut file = File::create(filename).unwrap();
|
||||||
|
|
||||||
todo!("file receiving");
|
parse_options(options, &WorkType::Receive)?;
|
||||||
|
|
||||||
Ok(())
|
let mut block_number: u16 = 0;
|
||||||
}
|
loop {
|
||||||
|
let size;
|
||||||
|
|
||||||
fn parse_options(&mut self, options: &Vec<TransferOption>, file: Option<&File>) {
|
let mut retry_cnt = 0;
|
||||||
for option in options {
|
loop {
|
||||||
let TransferOption { option, value } = option;
|
match Message::recv_data(socket, worker_options.blk_size) {
|
||||||
|
Ok(Packet::Data {
|
||||||
match option {
|
block_num: received_block_number,
|
||||||
OptionType::BlockSize => self.blk_size = *value,
|
data,
|
||||||
OptionType::TransferSize => {
|
}) => {
|
||||||
self.t_size = match file {
|
if received_block_number == block_number.wrapping_add(1) {
|
||||||
Some(file) => file.metadata().unwrap().len() as usize,
|
block_number = received_block_number;
|
||||||
None => *value,
|
file.write(&data)?;
|
||||||
|
size = data.len();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OptionType::Timeout => self.timeout = *value,
|
Ok(Packet::Error { code, msg }) => {
|
||||||
|
return Err(format!("received error code {code}: {msg}").into());
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
retry_cnt += 1;
|
||||||
|
if retry_cnt == MAX_RETRIES {
|
||||||
|
return Err(
|
||||||
|
format!("transfer timed out after {MAX_RETRIES} tries: {err}").into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Message::send_ack(socket, block_number)?;
|
||||||
|
if size < worker_options.blk_size {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Received {filename} from {}", socket.peer_addr().unwrap());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_socket(addr: &SocketAddr, remote: &SocketAddr) -> Result<UdpSocket, Box<dyn Error>> {
|
||||||
|
let socket = UdpSocket::bind(SocketAddr::from((addr.ip(), 0)))?;
|
||||||
|
socket.connect(remote)?;
|
||||||
|
socket.set_read_timeout(Some(Duration::from_secs(DEFAULT_TIMEOUT_SECS)))?;
|
||||||
|
socket.set_write_timeout(Some(Duration::from_secs(DEFAULT_TIMEOUT_SECS)))?;
|
||||||
|
Ok(socket)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_options(
|
||||||
|
options: &mut Vec<TransferOption>,
|
||||||
|
work_type: &WorkType,
|
||||||
|
) -> Result<WorkerOptions, Box<dyn Error>> {
|
||||||
|
let mut worker_options = WorkerOptions {
|
||||||
|
blk_size: DEFAULT_BLOCK_SIZE,
|
||||||
|
t_size: 0,
|
||||||
|
timeout: DEFAULT_TIMEOUT_SECS,
|
||||||
|
};
|
||||||
|
|
||||||
|
for option in &mut *options {
|
||||||
|
let TransferOption { option, value } = option;
|
||||||
|
|
||||||
|
match option {
|
||||||
|
OptionType::BlockSize => worker_options.blk_size = *value,
|
||||||
|
OptionType::TransferSize => match work_type {
|
||||||
|
WorkType::Send(size) => {
|
||||||
|
*value = *size as usize;
|
||||||
|
}
|
||||||
|
WorkType::Receive => {
|
||||||
|
worker_options.t_size = *value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OptionType::Timeout => {
|
||||||
|
if *value == 0 {
|
||||||
|
return Err("invalid timeout value".into());
|
||||||
|
}
|
||||||
|
worker_options.timeout = *value as u64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(worker_options)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_oack(
|
||||||
|
socket: &UdpSocket,
|
||||||
|
options: &Vec<TransferOption>,
|
||||||
|
work_type: &WorkType,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
if options.len() > 0 {
|
||||||
|
Message::send_oack(socket, options)?;
|
||||||
|
if let Packet::Ack(received_block_number) = Message::recv(socket)? {
|
||||||
|
if received_block_number != 0 {
|
||||||
|
Message::send_error(socket, ErrorCode::IllegalOperation, "invalid oack response")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if *work_type == WorkType::Receive {
|
||||||
|
Message::send_ack(socket, 0)?
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue