feat: implement TFTP client and server binaries
Add command-line programs for TFTP operations: tftpd - TFTP server daemon: - Configurable root directory for serving files - Optional write support with --writable flag - Optional file overwriting with --overwrite flag - Custom port binding (default: 69) - Path traversal protection for security tftp - TFTP client: - get command for downloading files - put command for uploading files - Support for both octet and netascii modes - Verbose mode for debugging Also adds comprehensive integration tests that verify client-server communication with real network I/O. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,5 +17,5 @@ mod packet;
|
||||
mod state;
|
||||
|
||||
pub use error::{Error, ErrorCode, Result};
|
||||
pub use packet::{Mode, Opcode, Packet, MAX_DATA_SIZE, TFTP_PORT};
|
||||
pub use packet::{MAX_DATA_SIZE, Mode, Opcode, Packet, TFTP_PORT};
|
||||
pub use state::{ClientState, Event, ServerState, TransferDirection};
|
||||
|
||||
@@ -244,12 +244,11 @@ impl Packet {
|
||||
.position(|&b| b == 0)
|
||||
.ok_or(Error::MissingNullTerminator { field: "filename" })?;
|
||||
|
||||
let filename = std::str::from_utf8(&payload[..filename_end]).map_err(|e| {
|
||||
Error::InvalidUtf8 {
|
||||
let filename =
|
||||
std::str::from_utf8(&payload[..filename_end]).map_err(|e| Error::InvalidUtf8 {
|
||||
field: "filename",
|
||||
source: e,
|
||||
}
|
||||
})?;
|
||||
})?;
|
||||
|
||||
// Find the mode string after the filename null terminator
|
||||
let mode_start = filename_end + 1;
|
||||
@@ -263,11 +262,12 @@ impl Packet {
|
||||
.ok_or(Error::MissingNullTerminator { field: "mode" })?
|
||||
+ mode_start;
|
||||
|
||||
let mode_str =
|
||||
std::str::from_utf8(&payload[mode_start..mode_end]).map_err(|e| Error::InvalidUtf8 {
|
||||
let mode_str = std::str::from_utf8(&payload[mode_start..mode_end]).map_err(|e| {
|
||||
Error::InvalidUtf8 {
|
||||
field: "mode",
|
||||
source: e,
|
||||
})?;
|
||||
}
|
||||
})?;
|
||||
|
||||
let mode = Mode::parse(mode_str)?;
|
||||
|
||||
@@ -594,10 +594,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_invalid_mode() {
|
||||
assert!(matches!(
|
||||
Mode::parse("binary"),
|
||||
Err(Error::InvalidMode(_))
|
||||
));
|
||||
assert!(matches!(Mode::parse("binary"), Err(Error::InvalidMode(_))));
|
||||
assert!(matches!(Mode::parse("mail"), Err(Error::InvalidMode(_))));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
//! ... (continues until final DATA with < 512 bytes)
|
||||
//! ```
|
||||
|
||||
use crate::{Error, Mode, Packet, Result, MAX_DATA_SIZE};
|
||||
use crate::{Error, MAX_DATA_SIZE, Mode, Packet, Result};
|
||||
|
||||
/// Direction of data transfer.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
||||
Reference in New Issue
Block a user