use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum TransferProtocol { Sftp, Zmodem, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum TransferDirection { Upload, Download, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct TransferRequest { pub protocol: TransferProtocol, pub direction: TransferDirection, pub local_path: String, pub remote_path: String, } impl TransferRequest { pub fn validate(&self) -> Result<(), String> { if self.local_path.trim().is_empty() { return Err("local path is required".to_string()); } if self.remote_path.trim().is_empty() { return Err("remote path is required".to_string()); } if self.local_path.contains("..") { return Err("local path traversal is not allowed".to_string()); } Ok(()) } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum TransferEvent { Queued, Progress { bytes_done: u64, bytes_total: u64 }, Completed, Failed(String), } #[cfg(test)] mod tests { use super::*; #[test] fn validates_sftp_download_request() { let request = TransferRequest { protocol: TransferProtocol::Sftp, direction: TransferDirection::Download, local_path: "downloads/file.txt".to_string(), remote_path: "/home/tom/file.txt".to_string(), }; assert!(request.validate().is_ok()); } #[test] fn validates_zmodem_upload_request() { let request = TransferRequest { protocol: TransferProtocol::Zmodem, direction: TransferDirection::Upload, local_path: "uploads/firmware.bin".to_string(), remote_path: "firmware.bin".to_string(), }; assert!(request.validate().is_ok()); } #[test] fn rejects_local_path_traversal() { let request = TransferRequest { protocol: TransferProtocol::Sftp, direction: TransferDirection::Download, local_path: "../secret".to_string(), remote_path: "/tmp/secret".to_string(), }; assert!(request.validate().unwrap_err().contains("traversal")); } }