Add initial packet logic
This commit is contained in:
parent
713cadaaa0
commit
2eb4329201
6 changed files with 238 additions and 53 deletions
|
|
@ -1,8 +1,7 @@
|
|||
use std::error::Error;
|
||||
use std::net::{AddrParseError, Ipv4Addr};
|
||||
use std::num::ParseIntError;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{env, fmt, process};
|
||||
use std::{env, process};
|
||||
|
||||
pub struct Config {
|
||||
pub ip_address: Ipv4Addr,
|
||||
|
|
@ -11,7 +10,7 @@ pub struct 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
|
||||
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)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
|
|
|||
22
src/convert.rs
Normal file
22
src/convert.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1,8 @@
|
|||
pub mod config;
|
||||
mod config;
|
||||
mod convert;
|
||||
mod packet;
|
||||
mod server;
|
||||
|
||||
pub use config::Config;
|
||||
pub use convert::Convert;
|
||||
pub use server::Server;
|
||||
|
|
|
|||
12
src/main.rs
12
src/main.rs
|
|
@ -1,5 +1,5 @@
|
|||
use std::{env, process};
|
||||
use tftpd::Config;
|
||||
use tftpd::{Config, Server};
|
||||
|
||||
fn main() {
|
||||
let config = Config::new(env::args()).unwrap_or_else(|err| {
|
||||
|
|
@ -7,8 +7,18 @@ fn main() {
|
|||
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!(
|
||||
"Running TFTP Server on {}:{}",
|
||||
config.ip_address, config.port
|
||||
);
|
||||
|
||||
server.listen();
|
||||
}
|
||||
|
|
|
|||
141
src/packet.rs
Normal file
141
src/packet.rs
Normal 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
54
src/server.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue