feat: add TFTP server and client binaries
This commit is contained in:
145
src/bin/pfs-tftp-client.rs
Normal file
145
src/bin/pfs-tftp-client.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
use std::{net::SocketAddr, path::PathBuf, time::Duration};
|
||||
|
||||
use pfs_tftp_sync::{Client, ClientConfig, Mode};
|
||||
|
||||
fn main() {
|
||||
match run() {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
std::process::exit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
let mut args = std::env::args().skip(1);
|
||||
|
||||
let Some(cmd) = args.next() else {
|
||||
print_usage();
|
||||
return Err("missing command".to_string());
|
||||
};
|
||||
|
||||
if cmd == "--help" || cmd == "-h" {
|
||||
print_usage();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let server_str = next_value(&mut args, "<server:port>")?;
|
||||
let server = parse_socket_addr(&server_str)?;
|
||||
|
||||
let mut cfg = ClientConfig::default();
|
||||
let mut mode = Mode::Octet;
|
||||
|
||||
let (remote, local, is_get) = match cmd.as_str() {
|
||||
"get" => {
|
||||
let remote = next_value(&mut args, "<remote_filename>")?;
|
||||
let local = next_value(&mut args, "<local_path>")?;
|
||||
(remote, local, true)
|
||||
}
|
||||
"put" => {
|
||||
let local = next_value(&mut args, "<local_path>")?;
|
||||
let remote = next_value(&mut args, "<remote_filename>")?;
|
||||
(remote, local, false)
|
||||
}
|
||||
_ => {
|
||||
print_usage();
|
||||
return Err(format!("unknown command: {cmd}"));
|
||||
}
|
||||
};
|
||||
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--help" | "-h" => {
|
||||
print_usage();
|
||||
return Ok(());
|
||||
}
|
||||
"--mode" => {
|
||||
let v = next_value(&mut args, "--mode")?;
|
||||
mode = parse_mode(&v)?;
|
||||
}
|
||||
"--timeout-ms" => {
|
||||
let v = next_value(&mut args, "--timeout-ms")?;
|
||||
cfg.timeout = Duration::from_millis(parse_u64("--timeout-ms", &v)?);
|
||||
}
|
||||
"--retries" => {
|
||||
let v = next_value(&mut args, "--retries")?;
|
||||
cfg.retries = parse_u32("--retries", &v)?;
|
||||
}
|
||||
"--dally-timeout-ms" => {
|
||||
let v = next_value(&mut args, "--dally-timeout-ms")?;
|
||||
cfg.dally_timeout = Duration::from_millis(parse_u64("--dally-timeout-ms", &v)?);
|
||||
}
|
||||
"--dally-retries" => {
|
||||
let v = next_value(&mut args, "--dally-retries")?;
|
||||
cfg.dally_retries = parse_u32("--dally-retries", &v)?;
|
||||
}
|
||||
other => return Err(format!("unknown argument: {other}")),
|
||||
}
|
||||
}
|
||||
|
||||
let client = Client::new(server, cfg);
|
||||
if is_get {
|
||||
client
|
||||
.get(&remote, &PathBuf::from(local), mode)
|
||||
.map_err(|e| format!("{e}"))?;
|
||||
} else {
|
||||
client
|
||||
.put(&PathBuf::from(local), &remote, mode)
|
||||
.map_err(|e| format!("{e}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_socket_addr(s: &str) -> Result<SocketAddr, String> {
|
||||
s.parse::<SocketAddr>().map_err(|e| format!("{e}"))
|
||||
}
|
||||
|
||||
fn parse_mode(s: &str) -> Result<Mode, String> {
|
||||
let Some(mode) = Mode::parse_case_insensitive(s) else {
|
||||
return Err(format!("unknown mode: {s} (expected netascii or octet)"));
|
||||
};
|
||||
if mode == Mode::Mail {
|
||||
return Err("mail mode is obsolete (RFC 1350, Section 1)".to_string());
|
||||
}
|
||||
Ok(mode)
|
||||
}
|
||||
|
||||
fn next_value(
|
||||
args: &mut impl Iterator<Item = String>,
|
||||
what: &'static str,
|
||||
) -> Result<String, String> {
|
||||
args.next().ok_or_else(|| format!("missing {what}"))
|
||||
}
|
||||
|
||||
fn parse_u64(flag: &'static str, value: &str) -> Result<u64, String> {
|
||||
value
|
||||
.parse::<u64>()
|
||||
.map_err(|e| format!("{flag}: invalid value: {e}"))
|
||||
}
|
||||
|
||||
fn parse_u32(flag: &'static str, value: &str) -> Result<u32, String> {
|
||||
value
|
||||
.parse::<u32>()
|
||||
.map_err(|e| format!("{flag}: invalid value: {e}"))
|
||||
}
|
||||
|
||||
fn print_usage() {
|
||||
eprintln!(
|
||||
"\
|
||||
pfs-tftp-client
|
||||
|
||||
USAGE:
|
||||
pfs-tftp-client get <server:port> <remote_filename> <local_path> [OPTIONS]
|
||||
pfs-tftp-client put <server:port> <local_path> <remote_filename> [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--mode <octet|netascii> Transfer mode (default: octet)
|
||||
--timeout-ms <MILLIS> Packet timeout (default: 5000)
|
||||
--retries <N> Retransmit attempts (default: 5)
|
||||
--dally-timeout-ms <MILLIS> Wait time for duplicate final DATA (default: timeout)
|
||||
--dally-retries <N> Dally attempts (default: 2)
|
||||
--help, -h Show this help
|
||||
"
|
||||
);
|
||||
}
|
||||
109
src/main.rs
109
src/main.rs
@@ -1,3 +1,110 @@
|
||||
use std::{net::SocketAddr, path::PathBuf, time::Duration};
|
||||
|
||||
use pfs_tftp_sync::{Server, ServerConfig};
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
match run() {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
std::process::exit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
let mut cfg = ServerConfig::default();
|
||||
|
||||
let mut args = std::env::args().skip(1);
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--help" | "-h" => {
|
||||
print_usage();
|
||||
return Ok(());
|
||||
}
|
||||
"--bind" => {
|
||||
let value = next_value(&mut args, "--bind")?;
|
||||
cfg.bind = value.parse::<SocketAddr>().map_err(|e| format!("{e}"))?;
|
||||
}
|
||||
"--root" => {
|
||||
let value = next_value(&mut args, "--root")?;
|
||||
cfg.root = PathBuf::from(value);
|
||||
}
|
||||
"--allow-write" => cfg.allow_write = true,
|
||||
"--overwrite" => cfg.overwrite = true,
|
||||
"--timeout-ms" => {
|
||||
let value = next_value(&mut args, "--timeout-ms")?;
|
||||
cfg.timeout = Duration::from_millis(parse_u64("--timeout-ms", &value)?);
|
||||
}
|
||||
"--retries" => {
|
||||
let value = next_value(&mut args, "--retries")?;
|
||||
cfg.retries = parse_u32("--retries", &value)?;
|
||||
}
|
||||
"--dally-timeout-ms" => {
|
||||
let value = next_value(&mut args, "--dally-timeout-ms")?;
|
||||
cfg.dally_timeout = Duration::from_millis(parse_u64("--dally-timeout-ms", &value)?);
|
||||
}
|
||||
"--dally-retries" => {
|
||||
let value = next_value(&mut args, "--dally-retries")?;
|
||||
cfg.dally_retries = parse_u32("--dally-retries", &value)?;
|
||||
}
|
||||
other => return Err(format!("unknown argument: {other}")),
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"Serving TFTP on {} (root: {}, allow_write: {}, overwrite: {})",
|
||||
cfg.bind,
|
||||
cfg.root.display(),
|
||||
cfg.allow_write,
|
||||
cfg.overwrite
|
||||
);
|
||||
|
||||
Server::new(cfg)
|
||||
.serve()
|
||||
.map_err(|e| format!("server error: {e}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn next_value(
|
||||
args: &mut impl Iterator<Item = String>,
|
||||
flag: &'static str,
|
||||
) -> Result<String, String> {
|
||||
args.next()
|
||||
.ok_or_else(|| format!("{flag} requires a value"))
|
||||
}
|
||||
|
||||
fn parse_u64(flag: &'static str, value: &str) -> Result<u64, String> {
|
||||
value
|
||||
.parse::<u64>()
|
||||
.map_err(|e| format!("{flag}: invalid value: {e}"))
|
||||
}
|
||||
|
||||
fn parse_u32(flag: &'static str, value: &str) -> Result<u32, String> {
|
||||
value
|
||||
.parse::<u32>()
|
||||
.map_err(|e| format!("{flag}: invalid value: {e}"))
|
||||
}
|
||||
|
||||
fn print_usage() {
|
||||
eprintln!(
|
||||
"\
|
||||
pfs-tftp (server)
|
||||
|
||||
USAGE:
|
||||
pfs-tftp [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--bind <IP:PORT> Bind address (default: 0.0.0.0:6969)
|
||||
--root <DIR> Root directory (default: .)
|
||||
--allow-write Enable WRQ (default: disabled)
|
||||
--overwrite Allow overwriting existing files (default: disabled)
|
||||
--timeout-ms <MILLIS> Packet timeout (default: 5000)
|
||||
--retries <N> Retransmit attempts (default: 5)
|
||||
--dally-timeout-ms <MILLIS>Wait time for duplicate final DATA (default: timeout)
|
||||
--dally-retries <N> Dally attempts (default: 2)
|
||||
--help, -h Show this help
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user