# target_simulator/utils/tftp_client.py import socket import struct import io from target_simulator.utils.logger import get_logger class TFTPError(Exception): def __init__(self, code, message): super().__init__(f"TFTP Error {code}: {message}") self.code = code self.message = message TFTP_PORT = 69 TFTP_BLOCK_SIZE = 512 class TFTPClient: def __init__(self, server_ip, server_port=TFTP_PORT, timeout=5): self.server_ip = server_ip self.server_port = int(server_port) self.timeout = timeout self.logger = get_logger(__name__) def _validate_params(self, filename, mode): if not filename or not isinstance(filename, str): raise ValueError("Invalid filename") if mode not in ("octet", "netascii"): raise ValueError("Invalid mode: must be 'octet' or 'netascii'") def upload(self, filename, fileobj, mode="octet"): self._validate_params(filename, mode) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(self.timeout) is_text_stream = isinstance(fileobj, io.TextIOBase) try: mode_bytes = mode.encode('ascii') wrq = struct.pack('!H', 2) + filename.encode('ascii') + b'\0' + mode_bytes + b'\0' self.logger.debug(f"Sending WRQ to {self.server_ip}:{self.server_port} for '{filename}'") sock.sendto(wrq, (self.server_ip, self.server_port)) self.logger.debug("Waiting for initial ACK(0)...") data, server = sock.recvfrom(1024) self.logger.debug(f"Received {len(data)} bytes from {server}: {data.hex()}") # --- GESTIONE RISPOSTA ANOMALA --- if len(data) >= 4: opcode, block = struct.unpack('!HH', data[:4]) elif len(data) >= 2: # Il pacchetto รจ troppo corto per contenere un block number. # Potrebbe essere un ACK malformato o un errore. self.logger.warning(f"Received a short packet ({len(data)} bytes). Assuming it's a malformed ACK/ERROR.") opcode = struct.unpack('!H', data[:2])[0] block = 0 # Assumiamo blocco 0 per l'ACK iniziale else: raise TFTPError(-1, f"Invalid packet received. Length is {len(data)} bytes, expected at least 2.") if opcode == 5: # ERROR error_code = struct.unpack('!H', data[2:4])[0] if len(data) >= 4 else -1 error_msg = data[4:].split(b'\0', 1)[0].decode(errors='replace') if len(data) > 4 else "Unknown error (short packet)" raise TFTPError(error_code, error_msg) if opcode != 4 or block != 0: raise TFTPError(-1, f'Unexpected response to WRQ. Opcode: {opcode}, Block: {block}') self.logger.debug("Initial ACK(0) received correctly. Starting data transfer.") block_num = 1 while True: chunk = fileobj.read(TFTP_BLOCK_SIZE) if is_text_stream: if not chunk: chunk_bytes = b'' else: chunk_bytes = chunk.encode("ascii") else: chunk_bytes = chunk pkt = struct.pack('!HH', 3, block_num) + chunk_bytes self.logger.debug(f"Sending DATA block {block_num} ({len(pkt)} bytes) to {server}") sock.sendto(pkt, server) self.logger.debug(f"Waiting for ACK({block_num})...") data, _ = sock.recvfrom(1024) self.logger.debug(f"Received {len(data)} bytes for ACK({block_num}): {data.hex()}") if len(data) < 4: raise TFTPError(-1, f"Invalid ACK packet for block {block_num}. Length is {len(data)} bytes.") opcode, ack_block = struct.unpack('!HH', data[:4]) if opcode == 5: error_code = struct.unpack('!H', data[2:4])[0] error_msg = data[4:].split(b'\0', 1)[0].decode(errors='replace') raise TFTPError(error_code, error_msg) if opcode != 4 or ack_block != block_num: # Gestione di ACK duplicati (comune su UDP) if opcode == 4 and ack_block == block_num - 1: self.logger.warning(f"Received duplicate ACK for block {ack_block}. Resending block {block_num}.") sock.sendto(pkt, server) # Resend current packet continue # Skip to next recvfrom else: raise TFTPError(-1, f'Unexpected ACK. Expected block {block_num}, got {ack_block}') if len(chunk_bytes) < TFTP_BLOCK_SIZE: self.logger.debug("Last block sent and ACKed. Transfer complete.") break block_num = (block_num + 1) % 65536 return True finally: sock.close() def download(self, filename, fileobj, mode="octet"): # ... (implementation from your file, non modificata) pass