Hello all.
I'm working on a TFTP
server implementation, for a school project.
I thought it would be relatively simple, however I am now stuck trying to create struct
s for all the different packet types. Not because it is so difficult, rather because working with raw packets is so frustrating. I looked, and many on this forum were recommending this crate called bytes
, and while it certainly feels like it should make things easier, it isn't, for whatever reason.
use bytes::{Buf, BufMut, Bytes, BytesMut};
use super::{modes, opcode, packet_errors, tftp_options};
/// Read Request packet:
pub struct RRQ {
filename: String,
mode: &'static [u8],
blksize: Option<usize>,
timeout: Option<usize>,
tsize: Option<usize>,
windowsize: Option<u16>,
}
impl RRQ {
/// Recieves the packet currently being constructed, and appends an option to it:
fn put_opt(packet: &mut BytesMut, opt_type: &[u8], opt_val: impl ToString) {
packet.put(opt_type);
packet.put_u8(0);
packet.put(opt_val.to_string().as_bytes());
packet.put_u8(0);
}
}
impl Into<Bytes> for RRQ {
fn into(self) -> Bytes {
// This will store the bytes while we build the packet, and will be made immutable later on:
let mut packet_bytes: BytesMut = BytesMut::new();
// First, insert the opcode:
packet_bytes.put_u16(opcode::RRQ);
// Then add the file name, followed by a null byte:
packet_bytes.put(self.filename.as_bytes());
packet_bytes.put_u8(0);
// Then the mode, followed by a null byte:
packet_bytes.put(self.mode);
packet_bytes.put_u8(0);
// Options:
// This is the implementation for the Options Extension:
// For the blocksize extension:
if let Some(blksize) = self.blksize {
Self::put_opt(&mut packet_bytes, tftp_options::BLKSIZE, blksize);
}
// For the Timeout extension:
if let Some(timeout) = self.timeout {
Self::put_opt(&mut packet_bytes, tftp_options::TIMEOUT, timeout);
}
// For the Transfer Size extension:
if let Some(tsize) = self.tsize {
Self::put_opt(&mut packet_bytes, tftp_options::TSIZE, tsize);
}
// For the Window Size extension:
if let Some(windowsize) = self.windowsize {
Self::put_opt(&mut packet_bytes, tftp_options::WINDOWSIZE, windowsize);
}
// Finally, put it into the bytearray that we'll send over UDP:
return packet_bytes.freeze();
}
}
impl TryFrom<Bytes> for RRQ {
type Error = packet_errors::DeserializingError;
fn try_from(mut value: Bytes) -> Result<Self, Self::Error> {
// 4 bytes is the minimum length.
if value.len() < 4 {
return Err(Self::Error::MalformedPacket);
}
// Is it the right kind of packet?
if value.get_u16() != opcode::RRQ {
return Err(Self::Error::BadOpcode);
}
// TODO: get filename, mode, and options.
Err(Self::Error::BadOpcode)
}
}
Note that my objective here is to make it so that I can simply send this struct
over a tokio
socket, and receive directly into it. Can I have some guidance?
I'm not new to programming, just really, really new to Rust.
Thanks in advance!