Add initial packet logic

This commit is contained in:
altugbakan 2023-03-06 22:21:31 +03:00
parent 713cadaaa0
commit 2eb4329201
6 changed files with 238 additions and 53 deletions

View file

@ -1,8 +1,7 @@
use std::error::Error; use std::error::Error;
use std::net::{AddrParseError, Ipv4Addr}; use std::net::Ipv4Addr;
use std::num::ParseIntError;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{env, fmt, process}; use std::{env, process};
pub struct Config { pub struct Config {
pub ip_address: Ipv4Addr, pub ip_address: Ipv4Addr,
@ -11,7 +10,7 @@ pub struct Config {
} }
impl Config { impl Config {
pub fn new<T>(mut args: T) -> Result<Config, ConfigError> pub fn new<T>(mut args: T) -> Result<Config, Box<dyn Error>>
where where
T: Iterator<Item = String>, T: Iterator<Item = String>,
{ {
@ -69,53 +68,6 @@ impl Config {
} }
} }
#[derive(Debug)]
pub struct ConfigError {
description: String,
}
impl Error for ConfigError {
fn description(&self) -> &str {
self.description.as_str()
}
}
impl fmt::Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description)
}
}
impl From<AddrParseError> for ConfigError {
fn from(value: AddrParseError) -> Self {
ConfigError {
description: value.to_string(),
}
}
}
impl From<ParseIntError> for ConfigError {
fn from(value: ParseIntError) -> Self {
ConfigError {
description: value.to_string(),
}
}
}
impl From<String> for ConfigError {
fn from(value: String) -> Self {
ConfigError { description: value }
}
}
impl From<&str> for ConfigError {
fn from(value: &str) -> Self {
ConfigError {
description: value.to_string(),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::str::FromStr; use std::str::FromStr;

22
src/convert.rs Normal file
View file

@ -0,0 +1,22 @@
use std::error::Error;
pub struct Convert;
impl Convert {
pub fn to_u16(buf: &[u8]) -> Result<u16, &'static str> {
if buf.len() < 2 {
Err("error when converting to u16")
} else {
Ok(((buf[0] as u16) << 8) + buf[1] as u16)
}
}
pub fn to_string(buf: &[u8]) -> Result<(String, usize), Box<dyn Error>> {
let zero_index = match buf[2..].iter().position(|&b| b == 0x00) {
Some(index) => index,
None => return Err("invalid string".into()),
};
Ok((String::from_utf8(buf[2..zero_index].to_vec())?, zero_index))
}
}

View file

@ -1,2 +1,8 @@
pub mod config; mod config;
mod convert;
mod packet;
mod server;
pub use config::Config; pub use config::Config;
pub use convert::Convert;
pub use server::Server;

View file

@ -1,5 +1,5 @@
use std::{env, process}; use std::{env, process};
use tftpd::Config; 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| {
@ -7,8 +7,18 @@ fn main() {
process::exit(1) process::exit(1)
}); });
let server = Server::new(&config).unwrap_or_else(|err| {
eprintln!(
"Problem creating server on {}:{}: {}",
config.ip_address, config.port, err
);
process::exit(1)
});
println!( println!(
"Running TFTP Server on {}:{}", "Running TFTP Server on {}:{}",
config.ip_address, config.port config.ip_address, config.port
); );
server.listen();
} }

141
src/packet.rs Normal file
View file

@ -0,0 +1,141 @@
use crate::Convert;
use std::error::Error;
pub enum Packet<'a> {
Rrq {
filename: String,
mode: String,
options: Vec<Option>,
},
Wrq {
filename: String,
mode: String,
options: Vec<Option>,
},
Data {
block_num: u16,
data: &'a [u8],
},
Ack(u16),
Error {
code: ErrorCode,
msg: String,
},
}
impl<'a> Packet<'a> {
pub fn deserialize(buf: &'a [u8]) -> Result<Packet, Box<dyn Error>> {
let opcode = Opcode::from_u16(Convert::to_u16(&buf[0..1])?)?;
match opcode {
Opcode::Rrq | Opcode::Wrq => parse_rq(buf, opcode),
Opcode::Data => parse_data(buf),
Opcode::Ack => parse_ack(buf),
Opcode::Error => parse_error(buf),
}
}
}
pub enum Opcode {
Rrq = 0x0001,
Wrq = 0x0002,
Data = 0x0003,
Ack = 0x0004,
Error = 0x0005,
}
impl Opcode {
pub fn from_u16(val: u16) -> Result<Opcode, &'static str> {
match val {
0x0001 => Ok(Opcode::Rrq),
0x0002 => Ok(Opcode::Wrq),
0x0003 => Ok(Opcode::Data),
0x0004 => Ok(Opcode::Ack),
0x0005 => Ok(Opcode::Error),
_ => Err("invalid opcode"),
}
}
}
pub struct Option {
option: String,
value: String,
}
#[repr(u16)]
#[derive(PartialEq)]
pub enum ErrorCode {
NotDefined = 0,
FileNotFound = 1,
AccessViolation = 2,
DiskFull = 3,
IllegalOperation = 4,
UnknownId = 5,
FileExists = 6,
NoSuchUser = 7,
}
impl ErrorCode {
pub fn from_u16(code: u16) -> Result<ErrorCode, &'static str> {
match code {
0 => Ok(ErrorCode::NotDefined),
1 => Ok(ErrorCode::FileNotFound),
2 => Ok(ErrorCode::AccessViolation),
3 => Ok(ErrorCode::DiskFull),
4 => Ok(ErrorCode::IllegalOperation),
5 => Ok(ErrorCode::UnknownId),
6 => Ok(ErrorCode::FileExists),
7 => Ok(ErrorCode::NoSuchUser),
_ => Err("invalid error code"),
}
}
}
fn parse_rq(buf: &[u8], opcode: Opcode) -> Result<Packet, Box<dyn Error>> {
let mut options = vec![];
let filename: String;
let mode: String;
let mut zero_index: usize;
(filename, zero_index) = Convert::to_string(&buf[2..])?;
(mode, zero_index) = Convert::to_string(&buf[zero_index + 1..])?;
let mut value: String;
let mut option;
while zero_index < buf.len() - 1 {
(option, zero_index) = Convert::to_string(&buf[zero_index + 1..])?;
(value, zero_index) = Convert::to_string(&buf[zero_index + 1..])?;
options.push(Option { option, value });
}
match opcode {
Opcode::Rrq => Ok(Packet::Rrq {
filename,
mode,
options,
}),
Opcode::Wrq => Ok(Packet::Wrq {
filename,
mode,
options,
}),
_ => Err("non request opcode".into()),
}
}
fn parse_data(buf: &[u8]) -> Result<Packet, Box<dyn Error>> {
Ok(Packet::Data {
block_num: Convert::to_u16(&buf[2..])?,
data: &buf[2..],
})
}
fn parse_ack(buf: &[u8]) -> Result<Packet, Box<dyn Error>> {
Ok(Packet::Ack(Convert::to_u16(&buf[2..])?))
}
fn parse_error(buf: &[u8]) -> Result<Packet, Box<dyn Error>> {
let code = ErrorCode::from_u16(Convert::to_u16(&buf[2..])?)?;
let (msg, _) = Convert::to_string(&buf[4..])?;
Ok(Packet::Error { code, msg })
}

54
src/server.rs Normal file
View file

@ -0,0 +1,54 @@
use crate::packet::{ErrorCode, Opcode, Packet};
use crate::Config;
use std::error::Error;
use std::net::{SocketAddr, UdpSocket};
const MAX_REQUEST_PACKET_SIZE: usize = 512;
pub struct Server {
socket: UdpSocket,
}
impl Server {
pub fn new(config: &Config) -> Result<Server, Box<dyn Error>> {
let socket = UdpSocket::bind(SocketAddr::from((config.ip_address, config.port)))?;
let server = Server { socket };
Ok(server)
}
pub fn listen(&self) {
loop {
let mut buf = [0; MAX_REQUEST_PACKET_SIZE];
if let Ok((number_of_bytes, from)) = self.socket.recv_from(&mut buf) {
if let Ok(packet) = Packet::deserialize(&buf[..number_of_bytes]) {
match packet {
Packet::Rrq {
filename,
mode,
options,
} => todo!(),
Packet::Wrq {
filename,
mode,
options,
} => todo!(),
_ => self.send_error_msg(from),
}
};
}
}
}
fn send_error_msg(&self, to: SocketAddr) {
let buf = [
0x00,
Opcode::Error as u8,
0x00,
ErrorCode::IllegalOperation as u8,
0x00,
];
self.socket.send_to(&buf, to);
}
}