aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
authorReiner Herrmann <reiner@reiner-h.de>2019-03-02 18:29:45 +0100
committerReiner Herrmann <reiner@reiner-h.de>2019-03-02 18:29:45 +0100
commit65bf28fbfcd91f430a8f3d1dbf669d7c398aac50 (patch)
tree204da243d4289ec57449f80facee167922fd104c /src/lib.rs
parent644b518e10f17e6360af088794c6fb710c7e7ce4 (diff)
Move main tftp module into lib to be used as crate
Otherwise compiler throws warnings about unused symbols, if not every public function is used in every module importing it.
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs304
1 files changed, 304 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..b1ff232
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2019 Reiner Herrmann <reiner@reiner-h.de>
+ * License: GPL-3+
+ */
+
+use std::net::UdpSocket;
+use std::fs::File;
+use std::collections::HashMap;
+use std::path::{Path,PathBuf};
+use std::io;
+use std::io::prelude::*;
+use std::time::Duration;
+
+pub struct TftpOptions {
+ blksize: usize,
+ timeout: u8,
+}
+
+pub struct Tftp {
+ options: TftpOptions,
+}
+
+fn default_options() -> TftpOptions {
+ TftpOptions {
+ blksize: 512,
+ timeout: 3,
+ }
+}
+
+impl Tftp {
+
+ pub fn new() -> Tftp {
+ Tftp{
+ options: default_options(),
+ }
+ }
+
+ fn get_tftp_str(&self, buf: &[u8]) -> Option<(String, usize)> {
+ let mut iter = buf.iter();
+
+ let len = match iter.position(|&x| x == 0) {
+ Some(l) => l,
+ None => return None,
+ };
+ let val = match String::from_utf8(buf[0 .. len].to_vec()) {
+ Ok(v) => v,
+ Err(_) => return None,
+ };
+
+ return Some((val, len));
+ }
+
+ fn wait_for_ack(&self, sock: &UdpSocket, expected_block: u16) -> Result<bool, io::Error> {
+ let mut buf = [0; 4];
+ match sock.recv(&mut buf) {
+ Ok(_) => (),
+ Err(ref error) if [io::ErrorKind::WouldBlock, io::ErrorKind::TimedOut].contains(&error.kind()) => {
+ return Ok(false);
+ }
+ Err(err) => return Err(err),
+ };
+
+ let opcode = u16::from_be_bytes([buf[0], buf[1]]);
+ let block_nr = u16::from_be_bytes([buf[2], buf[3]]);
+
+ if opcode == 4 && block_nr == expected_block {
+ return Ok(true)
+ }
+
+ Ok(false)
+ }
+
+ fn ack_options(&self, sock: &UdpSocket, options: &HashMap<String, String>, ackwait: bool) -> Result<(), io::Error> {
+ if options.is_empty() {
+ if !ackwait {
+ /* it's a WRQ, send normal ack to start transfer */
+ self.send_ack(&sock, 0)?;
+ }
+ return Ok(())
+ }
+
+ let mut buf = Vec::with_capacity(512);
+ buf.extend([0x00, 0x06].iter()); // opcode
+
+ for (key, val) in options {
+ buf.extend(key.bytes());
+ buf.push(0x00);
+ buf.extend(val.bytes());
+ buf.push(0x00);
+ }
+
+ for _ in 1..5 {
+ sock.send(&buf)?;
+ if !ackwait {
+ return Ok(());
+ }
+ match self.wait_for_ack(&sock, 0) {
+ Ok(true) => return Ok(()),
+ Ok(false) => continue,
+ Err(e) => return Err(e),
+ };
+ }
+
+ Err(io::Error::new(io::ErrorKind::TimedOut, "ack timeout"))
+ }
+
+ pub fn init_tftp_options(&mut self, sock: &UdpSocket, options: &mut HashMap<String, String>, ackwait: bool) -> Result<(), io::Error> {
+ self.options = default_options();
+
+ options.retain(|key, val| {
+ let val = val.to_lowercase();
+ match key.to_lowercase().as_str() {
+ "blksize" => {
+ match val.parse() {
+ Ok(b) if b >= 8 && b <= 65464 => {
+ self.options.blksize = b;
+ true
+ }
+ _ => false
+ }
+ }
+ "timeout" => {
+ match val.parse() {
+ Ok(t) if t >= 1 => {
+ self.options.timeout = t;
+ true
+ }
+ _ => false
+ }
+ }
+ _ => false
+ }
+ });
+
+ sock.set_read_timeout(Some(Duration::from_secs(self.options.timeout as u64)))?;
+
+ self.ack_options(&sock, &options, ackwait)?;
+
+ return Ok(());
+ }
+
+ fn parse_options(&self, buf: &[u8]) -> HashMap<String, String> {
+ let mut options = HashMap::new();
+
+ let mut pos = 0;
+ loop {
+ let (key, len) = match self.get_tftp_str(&buf[pos ..]) {
+ Some(args) => args,
+ None => break,
+ };
+ pos += len + 1;
+
+ let (val, len) = match self.get_tftp_str(&buf[pos ..]) {
+ Some(args) => args,
+ None => break,
+ };
+ pos += len + 1;
+
+ options.insert(key, val);
+ }
+
+ return options;
+ }
+
+ pub fn parse_file_mode_options(&self, buf: &[u8]) -> Result<(PathBuf, String, HashMap<String, String>), io::Error> {
+ let dataerr = io::Error::new(io::ErrorKind::InvalidData, "invalid data received");
+
+ let mut pos = 0;
+ let (filename, len) = match self.get_tftp_str(&buf[pos ..]) {
+ Some(args) => args,
+ None => return Err(dataerr),
+ };
+ pos += len + 1;
+
+ let filename = Path::new(&filename);
+
+ let (mode, len) = match self.get_tftp_str(&buf[pos ..]) {
+ Some((m,l)) => (m.to_lowercase(), l),
+ None => return Err(dataerr),
+ };
+ pos += len + 1;
+
+ let options = self.parse_options(&buf[pos ..]);
+
+ Ok((filename.to_path_buf(), mode, options))
+ }
+
+ pub fn send_error(&self, socket: &UdpSocket, code: u16, msg: &str) -> Result<(), io::Error> {
+ let mut buf = vec![0x00, 0x05]; // opcode
+ buf.extend(code.to_be_bytes().iter());
+ buf.extend(msg.as_bytes());
+
+ socket.send(&buf)?;
+ Ok(())
+ }
+
+ pub fn send_ack(&self, sock: &UdpSocket, block_nr: u16) -> Result<(), io::Error> {
+ let mut buf = vec![0x00, 0x04]; // opcode
+ buf.extend(block_nr.to_be_bytes().iter());
+
+ sock.send(&buf)?;
+
+ Ok(())
+ }
+
+ pub fn send_file(&self, socket: &UdpSocket, file: &mut File) -> Result<(), io::Error> {
+ let mut block_nr: u16 = 1;
+
+ loop {
+ let mut filebuf = vec![0; self.options.blksize];
+ let len = match file.read(&mut filebuf) {
+ Ok(n) => n,
+ Err(ref error) if error.kind() == io::ErrorKind::Interrupted => continue, /* retry */
+ Err(err) => {
+ self.send_error(&socket, 0, "File reading error")?;
+ return Err(err);
+ }
+ };
+
+ let mut sendbuf = Vec::with_capacity(4 + len);
+ sendbuf.extend([0x00, 0x03].iter()); // opcode
+ sendbuf.extend(block_nr.to_be_bytes().iter());
+ sendbuf.extend(filebuf[0..len].iter());
+
+ let mut acked = false;
+ for _ in 1..5 {
+ /* try a couple of times to send data, in case of timeouts
+ or re-ack of previous data */
+ socket.send(&sendbuf)?;
+ match self.wait_for_ack(&socket, block_nr) {
+ Ok(true) => {
+ acked = true;
+ break;
+ },
+ Ok(false) => continue,
+ Err(e) => return Err(e),
+ };
+ }
+ if !acked {
+ return Err(io::Error::new(io::ErrorKind::TimedOut, "ack timeout"))
+ }
+
+ if len < self.options.blksize {
+ /* this was the last block */
+ break;
+ }
+
+ /* increment with rollover on overflow */
+ block_nr = block_nr.wrapping_add(1);
+ }
+ Ok(())
+ }
+
+ pub fn recv_file(&self, sock: &UdpSocket, file: &mut File) -> Result<(), io::Error> {
+ let mut block_nr: u16 = 1;
+
+ loop {
+ let mut buf = vec![0; 4 + self.options.blksize + 1]; // +1 for later size check
+ let mut len = 0;
+
+ for _ in 1..5 {
+ len = match sock.recv(&mut buf) {
+ Ok(n) => n,
+ Err(ref error) if [io::ErrorKind::WouldBlock, io::ErrorKind::TimedOut].contains(&error.kind()) => {
+ /* re-ack previous and try to recv again */
+ self.send_ack(&sock, block_nr - 1)?;
+ continue;
+ }
+ Err(err) => return Err(err),
+ };
+ break;
+ }
+ if len < 4 || len > 4 + self.options.blksize {
+ /* max size: 2 + 2 + blksize */
+ return Err(io::Error::new(io::ErrorKind::InvalidInput, "unexpected size"));
+ }
+
+ let _opcode = match u16::from_be_bytes([buf[0], buf[1]]) {
+ 3 /* DATA */ => (),
+ _ => return Err(io::Error::new(io::ErrorKind::Other, "unexpected opcode")),
+ };
+ if u16::from_be_bytes([buf[2], buf[3]]) != block_nr {
+ /* already received or packets were missed, re-acknowledge */
+ self.send_ack(&sock, block_nr - 1)?;
+ continue;
+ }
+
+ let databuf = &buf[4..len];
+ file.write_all(databuf)?;
+
+ self.send_ack(&sock, block_nr)?;
+ block_nr = block_nr.wrapping_add(1);
+
+ if len < 4 + self.options.blksize {
+ break;
+ }
+ }
+
+ file.flush()?;
+
+ Ok(())
+ }
+
+}