diff --git a/src/primaite/simulator/file_system/file_system.py b/src/primaite/simulator/file_system/file_system.py index b2037729..9581a32b 100644 --- a/src/primaite/simulator/file_system/file_system.py +++ b/src/primaite/simulator/file_system/file_system.py @@ -226,7 +226,7 @@ class FileSystem(SimComponent): folder = self.get_folder(folder_name) if folder: return folder.get_file(file_name) - self.fs.sys_log.info(f"file not found /{folder_name}/{file_name}") + self.sys_log.info(f"file not found /{folder_name}/{file_name}") def delete_file(self, folder_name: str, file_name: str): """ diff --git a/src/primaite/simulator/system/services/ftp/ftp_client.py b/src/primaite/simulator/system/services/ftp/ftp_client.py index 687e9a12..8e93df1b 100644 --- a/src/primaite/simulator/system/services/ftp/ftp_client.py +++ b/src/primaite/simulator/system/services/ftp/ftp_client.py @@ -6,10 +6,10 @@ from primaite.simulator.network.protocols.ftp import FTPCommand, FTPPacket, FTPS from primaite.simulator.network.transmission.network_layer import IPProtocol from primaite.simulator.network.transmission.transport_layer import Port from primaite.simulator.system.core.software_manager import SoftwareManager -from primaite.simulator.system.services.service import Service +from primaite.simulator.system.services.ftp.ftp_service import FTPServiceABC -class FTPClient(Service): +class FTPClient(FTPServiceABC): """ A class for simulating an FTP client service. @@ -61,17 +61,6 @@ class FTPClient(Service): # return true if connected successfully else false self.connected = False - def _process_response(self, payload: FTPPacket): - """ - Process any FTPPacket responses. - - :param: payload: The FTPPacket payload - :type: FTPPacket - """ - if payload.ftp_command == FTPCommand.PORT: - if payload.status_code == FTPStatusCode.OK: - self.connected = True - def send_file( self, dest_ip_address: IPv4Address, @@ -109,23 +98,13 @@ class FTPClient(Service): ) else: # send STOR request - payload: FTPPacket = FTPPacket( - ftp_command=FTPCommand.STOR, - ftp_command_args={ - "dest_folder_name": dest_folder_name, - "dest_file_name": dest_file_name, - "file_size": file_to_transfer.sim_size, - }, - packet_payload_size=file_to_transfer.sim_size, + return self._send_data( + file=file_to_transfer, + dest_folder_name=dest_folder_name, + dest_file_name=dest_file_name, + dest_ip_address=dest_ip_address, + dest_port=dest_port, ) - software_manager: SoftwareManager = self.software_manager - software_manager.send_payload_to_session_manager( - payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port - ) - - if payload.status_code == Port.FTP: - self._disconnect_from_server() - return True def request_file( self, @@ -138,7 +117,48 @@ class FTPClient(Service): is_reattempt: Optional[bool] = False, ) -> bool: """Request a file from a target IP address.""" - pass + # check if FTP is currently connected to IP + self.connected = self._connect_to_server( + dest_ip_address=dest_ip_address, + dest_port=dest_port, + ) + + if not self.connected: + if is_reattempt: + return False + + return self.request_file( + src_folder_name=src_folder_name, + src_file_name=src_file_name, + dest_folder_name=dest_folder_name, + dest_file_name=dest_file_name, + dest_ip_address=dest_ip_address, + dest_port=dest_port, + is_reattempt=True, + ) + else: + # send retrieve request + payload: FTPPacket = FTPPacket( + ftp_command=FTPCommand.RETR, + ftp_command_args={ + "src_folder_name": src_folder_name, + "src_file_name": src_file_name, + "dest_file_name": dest_file_name, + "dest_folder_name": dest_folder_name, + }, + ) + software_manager: SoftwareManager = self.software_manager + software_manager.send_payload_to_session_manager( + payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port + ) + + # the payload should have ok status code + if payload.status_code == FTPStatusCode.OK: + self.sys_log.info(f"File {src_folder_name}/{src_file_name} found in FTP server.") + return True + else: + self.sys_log.error(f"File {src_folder_name}/{src_file_name} does not exist in FTP server") + return False def receive(self, payload: FTPPacket, session_id: Optional[str] = None, **kwargs) -> bool: """Receives a payload from the SessionManager.""" @@ -146,5 +166,5 @@ class FTPClient(Service): self.sys_log.error(f"{payload} is not an FTP packet") return False - self._process_response(payload=payload) + self._process_ftp_command(payload=payload, session_id=session_id) return True diff --git a/src/primaite/simulator/system/services/ftp/ftp_server.py b/src/primaite/simulator/system/services/ftp/ftp_server.py index ead6d503..c575479a 100644 --- a/src/primaite/simulator/system/services/ftp/ftp_server.py +++ b/src/primaite/simulator/system/services/ftp/ftp_server.py @@ -1,12 +1,12 @@ from typing import Any, Optional -from primaite.simulator.network.protocols.ftp import FTPCommand, FTPPacket, FTPStatusCode +from primaite.simulator.network.protocols.ftp import FTPPacket from primaite.simulator.network.transmission.network_layer import IPProtocol from primaite.simulator.network.transmission.transport_layer import Port -from primaite.simulator.system.services.service import Service +from primaite.simulator.system.services.ftp.ftp_service import FTPServiceABC -class FTPServer(Service): +class FTPServer(FTPServiceABC): """ A class for simulating an FTP server service. @@ -24,48 +24,11 @@ class FTPServer(Service): super().__init__(**kwargs) self.start() - def _process_ftp_command(self, payload: FTPPacket) -> FTPPacket: - # handle PORT request - if payload.ftp_command == FTPCommand.PORT: - # check that the port is valid - if isinstance(payload.ftp_command_args, Port) and payload.ftp_command_args.value in range(0, 65535): - # return successful connection - payload.status_code = FTPStatusCode.OK - - # handle STOR request - if payload.ftp_command == FTPCommand.STOR: - # check that the file is created in the computed hosting the FTP server - if self._process_store_data(payload=payload): - payload.status_code = FTPStatusCode.OK - - return payload - - def _process_store_data(self, payload: FTPPacket) -> bool: - """Handle the transfer of data from Client to this Server.""" - try: - file_name = payload.ftp_command_args["dest_file_name"] - folder_name = payload.ftp_command_args["dest_folder_name"] - file_size = payload.ftp_command_args["file_size"] - self.file_system.create_file( - file_name=file_name, - folder_name=folder_name, - size=file_size, - ) - self.sys_log.info( - f"Created item in {self.name}: {payload.ftp_command_args['dest_folder_name']}/" - f"{payload.ftp_command_args['dest_file_name']}" - ) - # file should exist - return self.file_system.get_file(file_name=file_name, folder_name=folder_name) is not None - except Exception as e: - self.sys_log.error(f"Unable to store file in {self.name}: {e}") - return False - def receive(self, payload: Any, session_id: Optional[str] = None, **kwargs) -> bool: """Receives a payload from the SessionManager.""" if not isinstance(payload, FTPPacket): self.sys_log.error(f"{payload} is not an FTP packet") return False - self.send(self._process_ftp_command(payload=payload), session_id) + self.send(self._process_ftp_command(payload=payload, session_id=session_id), session_id) return True diff --git a/src/primaite/simulator/system/services/ftp/ftp_service.py b/src/primaite/simulator/system/services/ftp/ftp_service.py new file mode 100644 index 00000000..6214c510 --- /dev/null +++ b/src/primaite/simulator/system/services/ftp/ftp_service.py @@ -0,0 +1,132 @@ +from abc import ABC +from ipaddress import IPv4Address +from typing import Optional + +from primaite.simulator.file_system.file_system import File +from primaite.simulator.network.protocols.ftp import FTPCommand, FTPPacket, FTPStatusCode +from primaite.simulator.network.transmission.transport_layer import Port +from primaite.simulator.system.core.software_manager import SoftwareManager +from primaite.simulator.system.services.service import Service + + +class FTPServiceABC(Service, ABC): + """ + Abstract Base Class for FTP Client and Service. + + Contains shared methods between both classes. + """ + + def _process_ftp_command(self, payload: FTPPacket, session_id: Optional[str] = None, **kwargs) -> FTPPacket: + # handle PORT request + if payload.ftp_command == FTPCommand.PORT: + # check that the port is valid + if isinstance(payload.ftp_command_args, Port) and payload.ftp_command_args.value in range(0, 65535): + # return successful connection + payload.status_code = FTPStatusCode.OK + + # handle STOR request + if payload.ftp_command == FTPCommand.STOR: + # check that the file is created in the computed hosting the FTP server + if self._store_data(payload=payload): + payload.status_code = FTPStatusCode.OK + + if payload.ftp_command == FTPCommand.RETR: + # check that the file exists in the FTP Server + file: File = self.file_system.get_file( + folder_name=payload.ftp_command_args["src_folder_name"], + file_name=payload.ftp_command_args["src_file_name"], + ) + if file: + payload.status_code = FTPStatusCode.OK + self._send_data( + file=file, + dest_folder_name=payload.ftp_command_args["dest_folder_name"], + dest_file_name=payload.ftp_command_args["dest_file_name"], + session_id=session_id, + ) + + return payload + + def _store_data(self, payload: FTPPacket) -> bool: + """ + Handle the transfer of data. + + :param: payload: The FTP Packet that contains the file data + :type: FTPPacket + """ + try: + file_name = payload.ftp_command_args["dest_file_name"] + folder_name = payload.ftp_command_args["dest_folder_name"] + file_size = payload.ftp_command_args["file_size"] + self.file_system.create_file( + file_name=file_name, + folder_name=folder_name, + size=file_size, + ) + self.sys_log.info( + f"Created item in {self.sys_log.hostname}: {payload.ftp_command_args['dest_folder_name']}/" + f"{payload.ftp_command_args['dest_file_name']}" + ) + # file should exist + return self.file_system.get_file(file_name=file_name, folder_name=folder_name) is not None + except Exception as e: + self.sys_log.error(f"Unable to create file in {self.sys_log.hostname}: {e}") + return False + + def _send_data( + self, + file: File, + dest_folder_name: str, + dest_file_name: str, + dest_ip_address: Optional[IPv4Address] = None, + dest_port: Optional[Port] = None, + session_id: Optional[str] = None, + ) -> bool: + # send STOR request + payload: FTPPacket = FTPPacket( + ftp_command=FTPCommand.STOR, + ftp_command_args={ + "dest_folder_name": dest_folder_name, + "dest_file_name": dest_file_name, + "file_size": file.sim_size, + }, + packet_payload_size=file.sim_size, + ) + software_manager: SoftwareManager = self.software_manager + software_manager.send_payload_to_session_manager( + payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id + ) + + if payload.status_code == FTPStatusCode.OK: + return True + + return False + + def _retrieve_data(self, payload: FTPPacket, session_id: Optional[str] = None) -> bool: + """ + Handle the transfer of data from Server to Client. + + :param: payload: The FTP Packet that contains the file data + :type: FTPPacket + """ + try: + # find the file + file_name = payload.ftp_command_args["src_file_name"] + folder_name = payload.ftp_command_args["src_folder_name"] + retrieved_file: File = self.file_system.get_file(folder_name=folder_name, file_name=file_name) + + # if file does not exist, return an error + if not retrieved_file: + self.sys_log.error( + f"File {payload.ftp_command_args['dest_folder_name']}/" + f"{payload.ftp_command_args['dest_file_name']} does not exist in {self.sys_log.hostname}" + ) + return False + else: + # send requested data + return self._send_data( + file=retrieved_file, dest_file_name=file_name, dest_folder_name=folder_name, session_id=session_id + ) + except Exception as e: + self.sys_log.error(f"Unable to retrieve file from {self.sys_log.hostname}: {e}") + return False diff --git a/tests/integration_tests/system/test_ftp_client_server.py b/tests/integration_tests/system/test_ftp_client_server.py index e062e0b7..fbbe6011 100644 --- a/tests/integration_tests/system/test_ftp_client_server.py +++ b/tests/integration_tests/system/test_ftp_client_server.py @@ -57,3 +57,6 @@ def test_ftp_client_retrieve_file_from_server(uc2_network): dest_file_name="test_file.txt", dest_ip_address=backup_server.nics.get(next(iter(backup_server.nics))).ip_address, ) + + # client should have retrieved the file + assert ftp_client.file_system.get_file(folder_name="downloads", file_name="test_file.txt")