Add docstrings
This commit is contained in:
parent
43d2e85a00
commit
bfb959bca8
7 changed files with 268 additions and 23 deletions
|
|
@ -3,13 +3,33 @@ use std::net::Ipv4Addr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::{env, process};
|
use std::{env, process};
|
||||||
|
|
||||||
|
/// Configuration `struct` used for parsing TFTP options from user
|
||||||
|
/// input.
|
||||||
|
///
|
||||||
|
/// This `struct` is meant to be created by [`Config::new()`]. See its
|
||||||
|
/// documentation for more.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// // Create TFTP configuration from user arguments.
|
||||||
|
/// use std::env;
|
||||||
|
/// use tftpd::Config;
|
||||||
|
///
|
||||||
|
/// let config = Config::new(env::args()).unwrap();
|
||||||
|
/// ```
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
/// Local IP address of the TFTP Server. (default: 127.0.0.1)
|
||||||
pub ip_address: Ipv4Addr,
|
pub ip_address: Ipv4Addr,
|
||||||
|
/// Local Port number of the TFTP Server. (default: 69)
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
|
/// Default directory of the TFTP Server. (default: current working directory)
|
||||||
pub directory: PathBuf,
|
pub directory: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
/// Creates a new configuration by parsing the supplied arguments. It is
|
||||||
|
/// intended for use with [`env::args()`].
|
||||||
pub fn new<T>(mut args: T) -> Result<Config, Box<dyn Error>>
|
pub fn new<T>(mut args: T) -> Result<Config, Box<dyn Error>>
|
||||||
where
|
where
|
||||||
T: Iterator<Item = String>,
|
T: Iterator<Item = String>,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,22 @@
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
|
/// Allows conversions between byte arrays and other types.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use tftpd::Convert;
|
||||||
|
///
|
||||||
|
/// assert_eq!(Convert::to_u16(&[0x01, 0x02]).unwrap(), 0x0102);
|
||||||
|
///
|
||||||
|
/// let (result, index) = Convert::to_string(b"hello world\0", 0).unwrap();
|
||||||
|
/// assert_eq!(result, "hello world");
|
||||||
|
/// assert_eq!(index, 11);
|
||||||
|
/// ```
|
||||||
pub struct Convert;
|
pub struct Convert;
|
||||||
|
|
||||||
impl Convert {
|
impl Convert {
|
||||||
|
/// Converts a [`u8`] slice to a [`u16`].
|
||||||
pub fn to_u16(buf: &[u8]) -> Result<u16, &'static str> {
|
pub fn to_u16(buf: &[u8]) -> Result<u16, &'static str> {
|
||||||
if buf.len() < 2 {
|
if buf.len() < 2 {
|
||||||
Err("Error when converting to u16")
|
Err("Error when converting to u16")
|
||||||
|
|
@ -11,6 +25,8 @@ impl Convert {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts a zero terminated [`u8`] slice to a [`String`], and returns the
|
||||||
|
/// size of the [`String`]. Useful for TFTP packet conversions.
|
||||||
pub fn to_string(buf: &[u8], start: usize) -> Result<(String, usize), Box<dyn Error>> {
|
pub fn to_string(buf: &[u8], start: usize) -> Result<(String, usize), Box<dyn Error>> {
|
||||||
match buf[start..].iter().position(|&b| b == 0x00) {
|
match buf[start..].iter().position(|&b| b == 0x00) {
|
||||||
Some(index) => Ok((
|
Some(index) => Ok((
|
||||||
|
|
|
||||||
21
src/lib.rs
21
src/lib.rs
|
|
@ -1,3 +1,19 @@
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
//! Multithreaded TFTP daemon implemented in pure Rust without dependencies.
|
||||||
|
//!
|
||||||
|
//! This server implements [RFC 1350](https://www.rfc-editor.org/rfc/rfc1350), The TFTP Protocol (Revision 2).
|
||||||
|
//! It also supports the following [RFC 2347](https://www.rfc-editor.org/rfc/rfc2347) TFTP Option Extensions:
|
||||||
|
//!
|
||||||
|
//! - [RFC 2348](https://www.rfc-editor.org/rfc/rfc2348) Blocksize Option
|
||||||
|
//! - [RFC 2349](https://www.rfc-editor.org/rfc/rfc2349) Timeout Interval Option
|
||||||
|
//! - [RFC 2349](https://www.rfc-editor.org/rfc/rfc2349) Transfer Size Option
|
||||||
|
//!
|
||||||
|
//! # Security
|
||||||
|
//!
|
||||||
|
//! Since TFTP servers do not offer any type of login or access control mechanisms, this server only allows
|
||||||
|
//! transfer and receiving inside a chosen folder, and disallows external file access.
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod convert;
|
mod convert;
|
||||||
mod message;
|
mod message;
|
||||||
|
|
@ -8,5 +24,10 @@ mod worker;
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
pub use convert::Convert;
|
pub use convert::Convert;
|
||||||
pub use message::Message;
|
pub use message::Message;
|
||||||
|
pub use packet::ErrorCode;
|
||||||
|
pub use packet::Opcode;
|
||||||
|
pub use packet::OptionType;
|
||||||
|
pub use packet::Packet;
|
||||||
|
pub use packet::TransferOption;
|
||||||
pub use server::Server;
|
pub use server::Server;
|
||||||
pub use worker::Worker;
|
pub use worker::Worker;
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,33 @@ use std::{
|
||||||
net::{SocketAddr, UdpSocket},
|
net::{SocketAddr, UdpSocket},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::packet::{ErrorCode, Packet, TransferOption};
|
use crate::{ErrorCode, Packet, TransferOption};
|
||||||
|
|
||||||
|
/// Message `struct` is used for easy message transmission of common TFTP
|
||||||
|
/// message types.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use std::{net::{SocketAddr, UdpSocket}, str::FromStr};
|
||||||
|
/// use tftpd::{Message, ErrorCode};
|
||||||
|
///
|
||||||
|
/// // Send a FileNotFound error.
|
||||||
|
/// Message::send_error_to(
|
||||||
|
/// &UdpSocket::bind(SocketAddr::from_str("127.0.0.1:69").unwrap()).unwrap(),
|
||||||
|
/// &SocketAddr::from_str("127.0.0.1:1234").unwrap(),
|
||||||
|
/// ErrorCode::FileNotFound,
|
||||||
|
/// "file does not exist".to_string(),
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
pub struct Message;
|
pub struct Message;
|
||||||
|
|
||||||
const MAX_REQUEST_PACKET_SIZE: usize = 512;
|
const MAX_REQUEST_PACKET_SIZE: usize = 512;
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
|
/// Sends a data packet to the socket's connected remote. See
|
||||||
|
/// [`UdpSocket`] for more information about connected
|
||||||
|
/// sockets.
|
||||||
pub fn send_data(
|
pub fn send_data(
|
||||||
socket: &UdpSocket,
|
socket: &UdpSocket,
|
||||||
block_num: u16,
|
block_num: u16,
|
||||||
|
|
@ -20,12 +40,18 @@ impl Message {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends an acknowledgement packet to the socket's connected remote. See
|
||||||
|
/// [`UdpSocket`] for more information about connected
|
||||||
|
/// sockets.
|
||||||
pub fn send_ack(socket: &UdpSocket, block_number: u16) -> Result<(), Box<dyn Error>> {
|
pub fn send_ack(socket: &UdpSocket, block_number: u16) -> Result<(), Box<dyn Error>> {
|
||||||
socket.send(&Packet::Ack(block_number).serialize()?)?;
|
socket.send(&Packet::Ack(block_number).serialize()?)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends an error packet to the socket's connected remote. See
|
||||||
|
/// [`UdpSocket`] for more information about connected
|
||||||
|
/// sockets.
|
||||||
pub fn send_error(
|
pub fn send_error(
|
||||||
socket: &UdpSocket,
|
socket: &UdpSocket,
|
||||||
code: ErrorCode,
|
code: ErrorCode,
|
||||||
|
|
@ -36,6 +62,7 @@ impl Message {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends an error packet to the supplied [`SocketAddr`].
|
||||||
pub fn send_error_to<'a>(
|
pub fn send_error_to<'a>(
|
||||||
socket: &UdpSocket,
|
socket: &UdpSocket,
|
||||||
to: &SocketAddr,
|
to: &SocketAddr,
|
||||||
|
|
@ -58,15 +85,20 @@ impl Message {
|
||||||
Err(msg.into())
|
Err(msg.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends an option acknowledgement packet to the socket's connected remote.
|
||||||
|
/// See [`UdpSocket`] for more information about connected sockets.
|
||||||
pub fn send_oack(
|
pub fn send_oack(
|
||||||
socket: &UdpSocket,
|
socket: &UdpSocket,
|
||||||
options: Vec<TransferOption>,
|
options: Vec<TransferOption>,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
socket.send(&Packet::Oack { options }.serialize()?)?;
|
socket.send(&Packet::Oack(options).serialize()?)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Receives a packet from the socket's connected remote, and returns the
|
||||||
|
/// parsed [`Packet`]. This function cannot handle large data packets due to
|
||||||
|
/// the limited buffer size. For handling data packets, see [`Message::recv_data()`].
|
||||||
pub fn recv(socket: &UdpSocket) -> Result<Packet, Box<dyn Error>> {
|
pub fn recv(socket: &UdpSocket) -> Result<Packet, Box<dyn Error>> {
|
||||||
let mut buf = [0; MAX_REQUEST_PACKET_SIZE];
|
let mut buf = [0; MAX_REQUEST_PACKET_SIZE];
|
||||||
let number_of_bytes = socket.recv(&mut buf)?;
|
let number_of_bytes = socket.recv(&mut buf)?;
|
||||||
|
|
@ -75,14 +107,10 @@ impl Message {
|
||||||
Ok(packet)
|
Ok(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recv_data(socket: &UdpSocket, size: usize) -> Result<Packet, Box<dyn Error>> {
|
/// Receives a packet from any incoming remote request, and returns the
|
||||||
let mut buf = vec![0; size + 4];
|
/// parsed [`Packet`] and the requesting [`SocketAddr`]. This function cannot handle
|
||||||
let number_of_bytes = socket.recv(&mut buf)?;
|
/// large data packets due to the limited buffer size, so it is intended for
|
||||||
let packet = Packet::deserialize(&buf[..number_of_bytes])?;
|
/// only accepting incoming requests.
|
||||||
|
|
||||||
Ok(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recv_from(socket: &UdpSocket) -> Result<(Packet, SocketAddr), Box<dyn Error>> {
|
pub fn recv_from(socket: &UdpSocket) -> Result<(Packet, SocketAddr), Box<dyn Error>> {
|
||||||
let mut buf = [0; MAX_REQUEST_PACKET_SIZE];
|
let mut buf = [0; MAX_REQUEST_PACKET_SIZE];
|
||||||
let (number_of_bytes, from) = socket.recv_from(&mut buf)?;
|
let (number_of_bytes, from) = socket.recv_from(&mut buf)?;
|
||||||
|
|
@ -90,4 +118,15 @@ impl Message {
|
||||||
|
|
||||||
Ok((packet, from))
|
Ok((packet, from))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Receives a data packet from the socket's connected remote, and returns the
|
||||||
|
/// parsed [`Packet`]. The received packet can actually be of any type, however,
|
||||||
|
/// this function also allows supplying the buffer size for an incoming request.
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
125
src/packet.rs
125
src/packet.rs
|
|
@ -1,32 +1,62 @@
|
||||||
use crate::Convert;
|
use crate::Convert;
|
||||||
use std::{error::Error, fmt};
|
use std::{error::Error, fmt};
|
||||||
|
|
||||||
|
/// Packet `enum` represents the valid TFTP packet types.
|
||||||
|
///
|
||||||
|
/// This `enum` has function implementaions for serializing [`Packet`]s into
|
||||||
|
/// [`Vec<u8>`]s and deserializing [`u8`] slices to [`Packet`]s.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust
|
||||||
|
/// use tftpd::Packet;
|
||||||
|
///
|
||||||
|
/// let packet = Packet::Data { block_num: 15, data: vec![0x01, 0x02, 0x03] };
|
||||||
|
///
|
||||||
|
/// assert_eq!(packet.serialize().unwrap(), vec![0x00, 0x03, 0x00, 0x0F, 0x01, 0x02, 0x03]);
|
||||||
|
/// assert_eq!(Packet::deserialize(&[0x00, 0x03, 0x00, 0x0F, 0x01, 0x02, 0x03]).unwrap(), packet);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Packet {
|
pub enum Packet {
|
||||||
|
/// Read Request `struct`
|
||||||
Rrq {
|
Rrq {
|
||||||
|
/// Name of the requested file
|
||||||
filename: String,
|
filename: String,
|
||||||
|
/// Transfer mode
|
||||||
mode: String,
|
mode: String,
|
||||||
|
/// Transfer options
|
||||||
options: Vec<TransferOption>,
|
options: Vec<TransferOption>,
|
||||||
},
|
},
|
||||||
|
/// Write Request `struct`
|
||||||
Wrq {
|
Wrq {
|
||||||
|
/// Name of the requested file
|
||||||
filename: String,
|
filename: String,
|
||||||
|
/// Transfer mode
|
||||||
mode: String,
|
mode: String,
|
||||||
|
/// Transfer options
|
||||||
options: Vec<TransferOption>,
|
options: Vec<TransferOption>,
|
||||||
},
|
},
|
||||||
|
/// Data `struct`
|
||||||
Data {
|
Data {
|
||||||
|
/// Block number
|
||||||
block_num: u16,
|
block_num: u16,
|
||||||
|
/// Data
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
},
|
},
|
||||||
|
/// Acknowledgement `tuple` with block number
|
||||||
Ack(u16),
|
Ack(u16),
|
||||||
|
/// Error `struct`
|
||||||
Error {
|
Error {
|
||||||
|
/// Error code
|
||||||
code: ErrorCode,
|
code: ErrorCode,
|
||||||
|
/// Error message
|
||||||
msg: String,
|
msg: String,
|
||||||
},
|
},
|
||||||
Oack {
|
/// Option acknowledgement `tuple` with transfer options
|
||||||
options: Vec<TransferOption>,
|
Oack(Vec<TransferOption>),
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Packet {
|
impl Packet {
|
||||||
|
/// Deserializes a [`u8`] slice into a [`Packet`].
|
||||||
pub fn deserialize(buf: &[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])?)?;
|
||||||
|
|
||||||
|
|
@ -39,29 +69,50 @@ impl Packet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Serializes a [`Packet`] into a [`Vec<u8>`].
|
||||||
pub fn serialize(&self) -> Result<Vec<u8>, Box<dyn Error>> {
|
pub fn serialize(&self) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
match self {
|
match self {
|
||||||
Packet::Data { block_num, data } => Ok(serialize_data(block_num, data)),
|
Packet::Data { block_num, data } => Ok(serialize_data(block_num, data)),
|
||||||
Packet::Ack(block_num) => Ok(serialize_ack(block_num)),
|
Packet::Ack(block_num) => Ok(serialize_ack(block_num)),
|
||||||
Packet::Error { code, msg } => Ok(serialize_error(code, msg)),
|
Packet::Error { code, msg } => Ok(serialize_error(code, msg)),
|
||||||
Packet::Oack { options } => Ok(serialize_oack(options)),
|
Packet::Oack(options) => Ok(serialize_oack(options)),
|
||||||
_ => Err("Invalid packet".into()),
|
_ => Err("Invalid packet".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opcode `enum` represents the opcodes used in the TFTP definition.
|
||||||
|
///
|
||||||
|
/// This `enum` has function implementations for converting [`u16`]s to
|
||||||
|
/// [`Opcode`]s and [`Opcode`]s to [`u8`] arrays.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use tftpd::Opcode;
|
||||||
|
///
|
||||||
|
/// assert_eq!(Opcode::from_u16(3).unwrap(), Opcode::Data);
|
||||||
|
/// assert_eq!(Opcode::Ack.as_bytes(), [0x00, 0x04]);
|
||||||
|
/// ```
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Opcode {
|
pub enum Opcode {
|
||||||
|
/// Read request opcode
|
||||||
Rrq = 0x0001,
|
Rrq = 0x0001,
|
||||||
|
/// Write request opcode
|
||||||
Wrq = 0x0002,
|
Wrq = 0x0002,
|
||||||
|
/// Data opcode
|
||||||
Data = 0x0003,
|
Data = 0x0003,
|
||||||
|
/// Acknowledgement opcode
|
||||||
Ack = 0x0004,
|
Ack = 0x0004,
|
||||||
|
/// Error opcode
|
||||||
Error = 0x0005,
|
Error = 0x0005,
|
||||||
|
/// Option acknowledgement opcode
|
||||||
Oack = 0x0006,
|
Oack = 0x0006,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Opcode {
|
impl Opcode {
|
||||||
|
/// Converts a [`u16`] to an [`Opcode`].
|
||||||
pub fn from_u16(val: u16) -> Result<Opcode, &'static str> {
|
pub fn from_u16(val: u16) -> Result<Opcode, &'static str> {
|
||||||
match val {
|
match val {
|
||||||
0x0001 => Ok(Opcode::Rrq),
|
0x0001 => Ok(Opcode::Rrq),
|
||||||
|
|
@ -74,18 +125,37 @@ impl Opcode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts a [`u16`] to a [`u8`] array with 2 elements.
|
||||||
pub fn as_bytes(self) -> [u8; 2] {
|
pub fn as_bytes(self) -> [u8; 2] {
|
||||||
return (self as u16).to_be_bytes();
|
return (self as u16).to_be_bytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TransferOption `struct` represents the TFTP transfer options.
|
||||||
|
///
|
||||||
|
/// This `struct` has a function implementation for converting [`TransferOption`]s
|
||||||
|
/// to [`Vec<u8>`]s.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use tftpd::{TransferOption, OptionType};
|
||||||
|
///
|
||||||
|
/// assert_eq!(TransferOption { option: OptionType::BlockSize, value: 1432 }.as_bytes(), vec![
|
||||||
|
/// 0x62, 0x6C, 0x6B, 0x73, 0x69, 0x7A, 0x65, 0x00, 0x31, 0x34, 0x33, 0x32,
|
||||||
|
/// 0x00,
|
||||||
|
/// ]);
|
||||||
|
/// ```
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct TransferOption {
|
pub struct TransferOption {
|
||||||
|
/// Type of the option
|
||||||
pub option: OptionType,
|
pub option: OptionType,
|
||||||
|
/// Value of the option
|
||||||
pub value: usize,
|
pub value: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransferOption {
|
impl TransferOption {
|
||||||
|
/// Converts a [`TransferOption`] to a [`Vec<u8>`].
|
||||||
pub fn as_bytes(&self) -> Vec<u8> {
|
pub fn as_bytes(&self) -> Vec<u8> {
|
||||||
[
|
[
|
||||||
self.option.as_str().as_bytes(),
|
self.option.as_str().as_bytes(),
|
||||||
|
|
@ -97,15 +167,32 @@ impl TransferOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OptionType `enum` represents the TFTP option types
|
||||||
|
///
|
||||||
|
/// This `enum` has function implementations for conversion between
|
||||||
|
/// [`OptionType`]s and [`str`]s.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use tftpd::OptionType;
|
||||||
|
///
|
||||||
|
/// assert_eq!(OptionType::BlockSize, OptionType::from_str("blksize").unwrap());
|
||||||
|
/// assert_eq!("tsize", OptionType::TransferSize.as_str());
|
||||||
|
/// ```
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum OptionType {
|
pub enum OptionType {
|
||||||
|
/// Block Size option type
|
||||||
BlockSize,
|
BlockSize,
|
||||||
|
/// Transfer Size option type
|
||||||
TransferSize,
|
TransferSize,
|
||||||
|
/// Timeout option type
|
||||||
Timeout,
|
Timeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OptionType {
|
impl OptionType {
|
||||||
fn as_str(&self) -> &'static str {
|
/// Converts an [`OptionType`] to a [`str`].
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
OptionType::BlockSize => "blksize",
|
OptionType::BlockSize => "blksize",
|
||||||
OptionType::TransferSize => "tsize",
|
OptionType::TransferSize => "tsize",
|
||||||
|
|
@ -113,7 +200,8 @@ impl OptionType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_str(value: &str) -> Result<Self, &'static str> {
|
/// Converts a [`str`] to an [`OptionType`].
|
||||||
|
pub fn from_str(value: &str) -> Result<Self, &'static str> {
|
||||||
match value {
|
match value {
|
||||||
"blksize" => Ok(OptionType::BlockSize),
|
"blksize" => Ok(OptionType::BlockSize),
|
||||||
"tsize" => Ok(OptionType::TransferSize),
|
"tsize" => Ok(OptionType::TransferSize),
|
||||||
|
|
@ -123,20 +211,42 @@ impl OptionType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ErrorCode `enum` represents the error codes used in the TFTP definition.
|
||||||
|
///
|
||||||
|
/// This `enum` has function implementations for converting [`u16`]s to
|
||||||
|
/// [`ErrorCode`]s and [`ErrorCode`]s to [`u8`] arrays.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use tftpd::ErrorCode;
|
||||||
|
///
|
||||||
|
/// assert_eq!(ErrorCode::from_u16(3).unwrap(), ErrorCode::DiskFull);
|
||||||
|
/// assert_eq!(ErrorCode::FileExists.as_bytes(), [0x00, 0x06]);
|
||||||
|
/// ```
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
pub enum ErrorCode {
|
pub enum ErrorCode {
|
||||||
|
/// Not Defined error code
|
||||||
NotDefined = 0,
|
NotDefined = 0,
|
||||||
|
/// File not found error code
|
||||||
FileNotFound = 1,
|
FileNotFound = 1,
|
||||||
|
/// Access violation error code
|
||||||
AccessViolation = 2,
|
AccessViolation = 2,
|
||||||
|
/// Disk full error code
|
||||||
DiskFull = 3,
|
DiskFull = 3,
|
||||||
|
/// Illegal operation error code
|
||||||
IllegalOperation = 4,
|
IllegalOperation = 4,
|
||||||
|
/// Unknown ID error code
|
||||||
UnknownId = 5,
|
UnknownId = 5,
|
||||||
|
/// File exists error code
|
||||||
FileExists = 6,
|
FileExists = 6,
|
||||||
|
/// No such user error code
|
||||||
NoSuchUser = 7,
|
NoSuchUser = 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorCode {
|
impl ErrorCode {
|
||||||
|
/// Converts a [`u16`] to an [`ErrorCode`].
|
||||||
pub fn from_u16(code: u16) -> Result<ErrorCode, &'static str> {
|
pub fn from_u16(code: u16) -> Result<ErrorCode, &'static str> {
|
||||||
match code {
|
match code {
|
||||||
0 => Ok(ErrorCode::NotDefined),
|
0 => Ok(ErrorCode::NotDefined),
|
||||||
|
|
@ -151,6 +261,7 @@ impl ErrorCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts an [`ErrorCode`] to a [`u8`] array with 2 elements.
|
||||||
pub fn as_bytes(self) -> [u8; 2] {
|
pub fn as_bytes(self) -> [u8; 2] {
|
||||||
return (self as u16).to_be_bytes();
|
return (self as u16).to_be_bytes();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,31 @@
|
||||||
use crate::packet::{ErrorCode, Packet, TransferOption};
|
|
||||||
use crate::{Config, Message, Worker};
|
use crate::{Config, Message, Worker};
|
||||||
|
use crate::{ErrorCode, Packet, TransferOption};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::net::{SocketAddr, UdpSocket};
|
use std::net::{SocketAddr, UdpSocket};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Server `struct` is used for handling incoming TFTP requests.
|
||||||
|
///
|
||||||
|
/// This `struct` is meant to be created by [`Server::new()`]. See its
|
||||||
|
/// documentation for more.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// // Create the TFTP server.
|
||||||
|
/// use std::env;
|
||||||
|
/// use tftpd::{Config, Server};
|
||||||
|
///
|
||||||
|
/// let config = Config::new(env::args()).unwrap();
|
||||||
|
/// let server = Server::new(&config).unwrap();
|
||||||
|
/// ```
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
socket: UdpSocket,
|
socket: UdpSocket,
|
||||||
directory: PathBuf,
|
directory: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
|
/// Creates the TFTP Server with the supplied [`Config`].
|
||||||
pub fn new(config: &Config) -> Result<Server, Box<dyn Error>> {
|
pub fn new(config: &Config) -> Result<Server, Box<dyn Error>> {
|
||||||
let socket = UdpSocket::bind(SocketAddr::from((config.ip_address, config.port)))?;
|
let socket = UdpSocket::bind(SocketAddr::from((config.ip_address, config.port)))?;
|
||||||
|
|
||||||
|
|
@ -21,6 +37,7 @@ impl Server {
|
||||||
Ok(server)
|
Ok(server)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Starts listening for connections. Note that this function does not finish running until termination.
|
||||||
pub fn listen(&self) {
|
pub fn listen(&self) {
|
||||||
loop {
|
loop {
|
||||||
if let Ok((packet, from)) = Message::recv_from(&self.socket) {
|
if let Ok((packet, from)) = Message::recv_from(&self.socket) {
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,32 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{ErrorCode, Message, OptionType, Packet, TransferOption};
|
||||||
packet::{ErrorCode, OptionType, Packet, TransferOption},
|
|
||||||
Message,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
/// Worker `struct` is used for multithreaded file sending and receiving.
|
||||||
|
/// It creates a new socket using the Server's IP and a random port
|
||||||
|
/// requested from the OS to communicate with the requesting client.
|
||||||
|
///
|
||||||
|
/// See [`Worker::send()`] and [`Worker::receive()`] for more details.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use std::{net::SocketAddr, path::PathBuf, str::FromStr};
|
||||||
|
/// use tftpd::Worker;
|
||||||
|
///
|
||||||
|
/// // Send a file, responding to a read request.
|
||||||
|
/// Worker::send(
|
||||||
|
/// SocketAddr::from_str("127.0.0.1:1234").unwrap(),
|
||||||
|
/// SocketAddr::from_str("127.0.0.1:4321").unwrap(),
|
||||||
|
/// PathBuf::from_str("/home/rust/test.txt").unwrap(),
|
||||||
|
/// vec![]
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
pub struct Worker;
|
pub struct Worker;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct WorkerOptions {
|
struct WorkerOptions {
|
||||||
blk_size: usize,
|
blk_size: usize,
|
||||||
t_size: usize,
|
t_size: usize,
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
|
|
@ -33,6 +50,8 @@ const DEFAULT_TIMEOUT_SECS: u64 = 5;
|
||||||
const DEFAULT_BLOCK_SIZE: usize = 512;
|
const DEFAULT_BLOCK_SIZE: usize = 512;
|
||||||
|
|
||||||
impl Worker {
|
impl Worker {
|
||||||
|
/// Sends a file to the remote [`SocketAddr`] that has sent a read request using
|
||||||
|
/// a random port, asynchronously.
|
||||||
pub fn send(
|
pub fn send(
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
remote: SocketAddr,
|
remote: SocketAddr,
|
||||||
|
|
@ -56,6 +75,8 @@ impl Worker {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Receives a file from the remote [`SocketAddr`] that has sent a write request using
|
||||||
|
/// a random port, asynchronously.
|
||||||
pub fn receive(
|
pub fn receive(
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
remote: SocketAddr,
|
remote: SocketAddr,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue