From 2aff61927bfec2ccc57661a955eca50729bb015e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Altu=C4=9F=20Bakan?= Date: Mon, 1 Jul 2024 22:17:29 +0200 Subject: [PATCH] Cleanup --- .github/workflows/unit.yml | 6 +-- README.md | 4 +- src/client.rs | 16 +++++--- src/client_config.rs | 55 +++++++++++--------------- src/client_main.rs | 4 +- src/config.rs | 19 ++++----- tests/integration_test.rs | 81 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 132 insertions(+), 53 deletions(-) diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index be0b511..2d9c81b 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -17,9 +17,9 @@ jobs: - uses: actions/checkout@v3 - name: Build run: cargo build --verbose - - name: Build + - name: Build client run: cargo build --features client --verbose - - name: Run tests + - name: Test run: cargo test --verbose - - name: test client + - name: Test client run: cargo test --features client --verbose diff --git a/README.md b/README.md index 5860600..ee4d6b3 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,15 @@ tftpc --help ``` To connect the client to a tftp server running on IP address `127.0.0.1`, read-only, on port `1234` and download a file named `example.file` + ```bash tftpc example.file -i 0.0.0.0 -p 1234 -d ``` To connect the client to a tftp server running on IP address `127.0.0.1`, read-only, on port `1234` and upload a file named `example.file` + ```bash -tftpc ./example.file -i 0.0.0.0 -p 1234 -u +tftpc example.file -i 0.0.0.0 -p 1234 -u ``` ## License diff --git a/src/client.rs b/src/client.rs index 4d0a385..bc7387d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -51,7 +51,7 @@ impl Client { timeout: config.timeout, mode: config.mode, filename: config.filename.clone(), - save_path: config.save_directory.clone(), + save_path: config.receive_directory.clone(), }) } @@ -73,14 +73,20 @@ impl Client { } else { UdpSocket::bind((Ipv6Addr::UNSPECIFIED, 0))? }; - let file = self.filename.clone(); + let file_name = self + .filename + .file_name() + .ok_or("Invalid filename")? + .to_str() + .ok_or("Filename is not valid UTF-8")? + .to_owned(); let size = File::open(self.filename.clone())?.metadata()?.len() as usize; Socket::send_to( &socket, &Packet::Wrq { - filename: file.into_os_string().into_string().unwrap(), + filename: file_name, mode: "octet".into(), options: vec![ TransferOption { @@ -98,7 +104,7 @@ impl Client { TransferOption { option: OptionType::TransferSize, value: size, - } + }, ], }, &self.remote_address, @@ -174,7 +180,7 @@ impl Client { TransferOption { option: OptionType::TransferSize, value: 0, - } + }, ], }, &self.remote_address, diff --git a/src/client_config.rs b/src/client_config.rs index 9aac467..ee1b186 100644 --- a/src/client_config.rs +++ b/src/client_config.rs @@ -23,9 +23,9 @@ use std::time::Duration; /// ``` #[derive(Debug)] pub struct ClientConfig { - /// Local IP address of the TFTP Server. (default: 127.0.0.1) + /// Local IP address of the TFTP Client. (default: 127.0.0.1) pub remote_ip_address: IpAddr, - /// Local Port number of the TFTP Server. (default: 69) + /// Local Port number of the TFTP Client. (default: 69) pub port: u16, /// Blocksize to use during transfer. (default: 512) pub blocksize: usize, @@ -35,8 +35,8 @@ pub struct ClientConfig { pub timeout: Duration, /// Upload or Download a file. (default: Download) pub mode: Mode, - /// Directory where to save downloaded files. (default: Current Working Directory) - pub save_directory: PathBuf, + /// Download directory of the TFTP Client. (default: current working directory) + pub receive_directory: PathBuf, /// File to Upload or Download. pub filename: PathBuf, } @@ -50,7 +50,7 @@ impl Default for ClientConfig { windowsize: DEFAULT_WINDOWSIZE, timeout: DEFAULT_TIMEOUT, mode: Mode::Download, - save_directory: Default::default(), + receive_directory: Default::default(), filename: Default::default(), } } @@ -100,14 +100,14 @@ impl ClientConfig { return Err("Missing timeout after flag".into()); } } - "-sd" | "--save-directory" => { + "-rd" | "--receive-directory" => { if let Some(dir_str) = args.next() { if !Path::new(&dir_str).exists() { return Err(format!("{dir_str} does not exist").into()); } - config.save_directory = dir_str.into(); + config.receive_directory = dir_str.into(); } else { - return Err("Missing save directory after flag".into()); + return Err("Missing receive directory after flag".into()); } } "-u" | "--upload" => { @@ -120,15 +120,17 @@ impl ClientConfig { println!("TFTP Client\n"); println!("Usage: tftpd client [OPTIONS]\n"); println!("Options:"); - println!(" -i, --ip-address \tIp address of the server (default: 127.0.0.1)"); - println!(" -p, --port \t\tPort of the server (default: 69)"); - println!(" -b, --blocksize \tSets the blocksize (default: 512)"); - println!(" -w, --windowsize \tSets the windowsize (default: 1)"); - println!(" -t, --timeout \tSets the timeout in seconds (default: 5)"); - println!(" -u, --upload\t\t\tSets the client to upload mode, Ignores all previous download flags"); - println!(" -d, --download\t\tSet the client to download mode, Invalidates all previous upload flags"); - println!(" -sd, --save-directory \tSet the directory to save files when in Download Mode (default: the directory setting)"); - println!(" -h, --help\t\t\tPrint help information"); + println!(" -i, --ip-address \t\tIp address of the server (default: 127.0.0.1)"); + println!(" -p, --port \t\t\tPort of the server (default: 69)"); + println!(" -b, --blocksize \t\tSets the blocksize (default: 512)"); + println!(" -w, --windowsize \t\tSets the windowsize (default: 1)"); + println!( + " -t, --timeout \t\tSets the timeout in seconds (default: 5)" + ); + println!(" -u, --upload\t\t\t\tSets the client to upload mode, Ignores all previous download flags"); + println!(" -d, --download\t\t\tSet the client to download mode, Invalidates all previous upload flags"); + println!(" -rd, --receive-directory \tSet the directory to receive files when in Download mode (default: current working directory)"); + println!(" -h, --help\t\t\t\tPrint help information"); process::exit(0); } file_name => { @@ -166,7 +168,7 @@ mod tests { "-w", "2", "-t", - "4" + "4", ] .iter() .map(|s| s.to_string()), @@ -175,7 +177,7 @@ mod tests { assert_eq!(config.remote_ip_address, Ipv4Addr::new(0, 0, 0, 0)); assert_eq!(config.port, 1234); - assert_eq!(config.save_directory, PathBuf::from("/")); + assert_eq!(config.receive_directory, PathBuf::from("/")); assert_eq!(config.filename, PathBuf::from("test.file")); assert_eq!(config.windowsize, 2); assert_eq!(config.blocksize, 1024); @@ -183,21 +185,12 @@ mod tests { assert_eq!(config.timeout, Duration::from_secs(4)); } - #[test] fn parses_partial_config() { let config = ClientConfig::new( - [ - "client", - "test.file", - "-d", - "-b", - "2048", - "-p", - "2000", - ] - .iter() - .map(|s| s.to_string()), + ["client", "test.file", "-d", "-b", "2048", "-p", "2000"] + .iter() + .map(|s| s.to_string()), ) .unwrap(); diff --git a/src/client_main.rs b/src/client_main.rs index 36c9151..a540c80 100644 --- a/src/client_main.rs +++ b/src/client_main.rs @@ -4,7 +4,7 @@ use tftpd::{Client, ClientConfig, Mode}; fn main() { client(env::args()).unwrap_or_else(|err| { - eprintln!("{err}"); + eprintln!("{err}"); }) } @@ -27,7 +27,7 @@ fn client>(args: T) -> Result<(), Box> { ); } else { println!( - "Starting TFTP Client, downloading {} to {}", + "Starting TFTP Client, downloading {} from {}", config.filename.display(), SocketAddr::new(config.remote_ip_address, config.port), ); diff --git a/src/config.rs b/src/config.rs index 167cdca..39b4459 100644 --- a/src/config.rs +++ b/src/config.rs @@ -118,23 +118,20 @@ impl Config { } "-h" | "--help" => { println!("TFTP Server Daemon\n"); - #[cfg(feature = "client")] - println!("Usage: tftpd server [OPTIONS]\n"); - #[cfg(not(feature = "client"))] println!("Usage: tftpd [OPTIONS]\n"); println!("Options:"); - println!(" -i, --ip-address \tSet the ip address of the server (default: 127.0.0.1)"); + println!(" -i, --ip-address \t\tSet the ip address of the server (default: 127.0.0.1)"); println!( - " -p, --port \t\tSet the listening port of the server (default: 69)" + " -p, --port \t\t\tSet the listening port of the server (default: 69)" ); - println!(" -d, --directory \tSet the serving directory (default: current working directory)"); + println!(" -d, --directory \t\tSet the serving directory (default: current working directory)"); println!(" -rd, --receive-directory \tSet the directory to receive files to (default: the directory setting)"); println!(" -sd, --send-directory \tSet the directory to send files from (default: the directory setting)"); - println!(" -s, --single-port\t\tUse a single port for both sending and receiving (default: false)"); - println!(" -r, --read-only\t\tRefuse all write requests, making the server read-only (default: false)"); - println!(" --duplicate-packets \tDuplicate all packets sent from the server (default: 0)"); - println!(" --overwrite\t\t\tOverwrite existing files (default: false)"); - println!(" -h, --help\t\t\tPrint help information"); + println!(" -s, --single-port\t\t\tUse a single port for both sending and receiving (default: false)"); + println!(" -r, --read-only\t\t\tRefuse all write requests, making the server read-only (default: false)"); + println!(" --duplicate-packets \t\tDuplicate all packets sent from the server (default: 0)"); + println!(" --overwrite\t\t\t\tOverwrite existing files (default: false)"); + println!(" -h, --help\t\t\t\tPrint help information"); process::exit(0); } "--duplicate-packets" => { diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 95f3097..fe2723e 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -2,6 +2,8 @@ use std::fs::{create_dir_all, remove_dir_all}; use std::process::{Child, Command, ExitStatus}; +use std::thread; +use std::time::Duration; const SERVER_DIR: &str = "target/integration/server"; const CLIENT_DIR: &str = "target/integration/client"; @@ -41,6 +43,7 @@ fn test_send() { initialize(format!("{SERVER_DIR}/{file_name}").as_str()); let _server = CommandRunner::new("target/debug/tftpd", &["-p", port, "-d", SERVER_DIR]); + thread::sleep(Duration::from_secs(1)); let mut client = CommandRunner::new( "atftp", &[ @@ -65,6 +68,7 @@ fn test_receive() { initialize(format!("{CLIENT_DIR}/{file_name}").as_str()); let _server = CommandRunner::new("target/debug/tftpd", &["-p", port, "-d", SERVER_DIR]); + thread::sleep(Duration::from_secs(1)); let mut client = CommandRunner::new( "atftp", &[ @@ -89,6 +93,7 @@ fn test_send_dir() { initialize(format!("{SERVER_DIR}/{file_name}").as_str()); let _server = CommandRunner::new("target/debug/tftpd", &["-p", port, "-sd", SERVER_DIR]); + thread::sleep(Duration::from_secs(1)); let mut client = CommandRunner::new( "atftp", &[ @@ -104,6 +109,8 @@ fn test_send_dir() { let status = client.wait(); assert!(status.success()); + + check_files(file_name); } #[test] @@ -113,6 +120,7 @@ fn test_receive_dir() { initialize(format!("{CLIENT_DIR}/{file_name}").as_str()); let _server = CommandRunner::new("target/debug/tftpd", &["-p", port, "-rd", SERVER_DIR]); + thread::sleep(Duration::from_secs(1)); let mut client = CommandRunner::new( "atftp", &[ @@ -128,6 +136,8 @@ fn test_receive_dir() { let status = client.wait(); assert!(status.success()); + + check_files(file_name); } #[test] @@ -140,6 +150,7 @@ fn test_send_ipv6() { "target/debug/tftpd", &["-i", "::1", "-p", port, "-d", SERVER_DIR], ); + thread::sleep(Duration::from_secs(1)); let mut client = CommandRunner::new( "atftp", &[ @@ -155,6 +166,8 @@ fn test_send_ipv6() { let status = client.wait(); assert!(status.success()); + + check_files(file_name); } #[test] @@ -167,6 +180,7 @@ fn test_receive_ipv6() { "target/debug/tftpd", &["-i", "::1", "-p", port, "-d", SERVER_DIR], ); + thread::sleep(Duration::from_secs(1)); let mut client = CommandRunner::new( "atftp", &[ @@ -182,6 +196,8 @@ fn test_receive_ipv6() { let status = client.wait(); assert!(status.success()); + + check_files(file_name); } #[test] @@ -191,6 +207,7 @@ fn test_send_single_port_options() { initialize(format!("{SERVER_DIR}/{file_name}").as_str()); let _server = CommandRunner::new("target/debug/tftpd", &["-p", port, "-d", SERVER_DIR, "-s"]); + thread::sleep(Duration::from_secs(1)); let mut client = CommandRunner::new( "atftp", &[ @@ -208,6 +225,60 @@ fn test_send_single_port_options() { let status = client.wait(); assert!(status.success()); + + check_files(file_name); +} + +#[test] +fn test_client_send() { + let file_name = "client_send"; + let port = "6980"; + initialize(format!("{CLIENT_DIR}/{file_name}").as_str()); + + let _server = CommandRunner::new("target/debug/tftpd", &["-p", port, "-d", SERVER_DIR]); + thread::sleep(Duration::from_secs(1)); + + let mut client = CommandRunner::new( + "target/debug/tftpc", + &[ + format!("{CLIENT_DIR}/{file_name}").as_str(), + "-p", + port, + "-u", + ], + ); + + let status = client.wait(); + assert!(status.success()); + + check_files(file_name); +} + +#[test] +fn test_client_receive() { + let file_name = "client_receive"; + let port = "6981"; + initialize(format!("{SERVER_DIR}/{file_name}").as_str()); + + let _server = CommandRunner::new("target/debug/tftpd", &["-p", port, "-d", SERVER_DIR]); + thread::sleep(Duration::from_secs(1)); + + let mut client = CommandRunner::new( + "target/debug/tftpc", + &[ + file_name, + "-p", + port, + "-d", + "-rd", + format!("{CLIENT_DIR}").as_str(), + ], + ); + + let status = client.wait(); + assert!(status.success()); + + check_files(file_name); } fn initialize(file_name: &str) { @@ -235,3 +306,13 @@ fn create_file(file_name: &str) { .wait() .expect("error waiting for test file creation"); } + +fn check_files(file_name: &str) { + let server_file = format!("{SERVER_DIR}/{file_name}"); + let client_file = format!("{CLIENT_DIR}/{file_name}"); + + let server_content = std::fs::read(server_file).expect("error reading server file"); + let client_content = std::fs::read(client_file).expect("error reading client file"); + + assert_eq!(server_content, client_content); +}