2023-09-29 20:14:42 +01:00
|
|
|
import shutil
|
2023-09-21 15:13:30 +01:00
|
|
|
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.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:
|
2023-09-25 14:31:57 +01:00
|
|
|
"""
|
|
|
|
|
Process the command in the FTP Packet.
|
|
|
|
|
|
|
|
|
|
:param: payload: The FTP Packet to process
|
|
|
|
|
:type: payload: FTPPacket
|
|
|
|
|
:param: session_id: session ID linked to the FTP Packet. Optional.
|
|
|
|
|
:type: session_id: Optional[str]
|
|
|
|
|
"""
|
2023-09-28 12:23:49 +01:00
|
|
|
if payload.ftp_command is not None:
|
|
|
|
|
self.sys_log.info(f"Received FTP {payload.ftp_command.name} command.")
|
|
|
|
|
|
2023-09-21 15:13:30 +01:00
|
|
|
# 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:
|
2023-09-25 14:31:57 +01:00
|
|
|
if self._retrieve_data(payload=payload, session_id=session_id):
|
2023-09-21 15:13:30 +01:00
|
|
|
payload.status_code = FTPStatusCode.OK
|
|
|
|
|
|
|
|
|
|
return payload
|
|
|
|
|
|
|
|
|
|
def _store_data(self, payload: FTPPacket) -> bool:
|
|
|
|
|
"""
|
2023-09-25 14:31:57 +01:00
|
|
|
Stores the data in the FTP Service's host machine.
|
2023-09-21 15:13:30 +01:00
|
|
|
|
|
|
|
|
: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"]
|
2023-09-29 20:14:42 +01:00
|
|
|
real_file_path = payload.ftp_command_args.get("real_file_path")
|
|
|
|
|
is_real = real_file_path is not None
|
|
|
|
|
file = self.file_system.create_file(
|
|
|
|
|
file_name=file_name, folder_name=folder_name, size=file_size, real=is_real
|
2023-09-21 15:13:30 +01:00
|
|
|
)
|
|
|
|
|
self.sys_log.info(
|
2023-10-03 14:59:48 +01:00
|
|
|
f"{self.name}: Created item in {self.sys_log.hostname}: {payload.ftp_command_args['dest_folder_name']}/"
|
2023-09-21 15:13:30 +01:00
|
|
|
f"{payload.ftp_command_args['dest_file_name']}"
|
|
|
|
|
)
|
2023-09-29 20:14:42 +01:00
|
|
|
if is_real:
|
|
|
|
|
shutil.copy(real_file_path, file.sim_path)
|
2023-09-21 15:13:30 +01:00
|
|
|
# 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:
|
2023-09-25 14:31:57 +01:00
|
|
|
"""
|
|
|
|
|
Sends data from the host FTP Service's machine to another FTP Service's host machine.
|
|
|
|
|
|
|
|
|
|
:param: file: File to send to the target FTP Service.
|
|
|
|
|
:type: file: File
|
|
|
|
|
|
|
|
|
|
:param: dest_folder_name: The name of the folder where the file will be stored in the FTP Server.
|
|
|
|
|
:type: dest_folder_name: str
|
|
|
|
|
|
|
|
|
|
:param: dest_file_name: The name of the file to be saved on the FTP Server.
|
|
|
|
|
:type: dest_file_name: str
|
|
|
|
|
|
|
|
|
|
:param: dest_ip_address: The IP address of the machine that hosts the FTP Server.
|
|
|
|
|
:type: dest_ip_address: Optional[IPv4Address]
|
|
|
|
|
|
|
|
|
|
:param: dest_port: The open port of the machine that hosts the FTP Server. Default is Port.FTP.
|
|
|
|
|
:type: dest_port: Optional[Port]
|
|
|
|
|
|
|
|
|
|
:param: session_id: session ID linked to the FTP Packet. Optional.
|
|
|
|
|
:type: session_id: Optional[str]
|
|
|
|
|
"""
|
2023-09-21 15:13:30 +01:00
|
|
|
# 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,
|
2023-09-29 20:14:42 +01:00
|
|
|
"real_file_path": file.sim_path if file.real else None,
|
2023-09-21 15:13:30 +01:00
|
|
|
},
|
|
|
|
|
packet_payload_size=file.sim_size,
|
|
|
|
|
)
|
2023-10-03 14:59:48 +01:00
|
|
|
self.sys_log.info(f"{self.name}: Sending file {file.folder.name}/{file.name}")
|
|
|
|
|
response = self.send(
|
2023-09-21 15:13:30 +01:00
|
|
|
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id
|
|
|
|
|
)
|
|
|
|
|
|
2023-10-03 14:59:48 +01:00
|
|
|
if response and payload.status_code == FTPStatusCode.OK:
|
2023-09-21 15:13:30 +01:00
|
|
|
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"]
|
2023-09-25 14:31:57 +01:00
|
|
|
dest_folder_name = payload.ftp_command_args["dest_folder_name"]
|
|
|
|
|
dest_file_name = payload.ftp_command_args["dest_file_name"]
|
2023-09-21 15:13:30 +01:00
|
|
|
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(
|
2023-09-25 14:31:57 +01:00
|
|
|
file=retrieved_file,
|
|
|
|
|
dest_file_name=dest_file_name,
|
|
|
|
|
dest_folder_name=dest_folder_name,
|
|
|
|
|
session_id=session_id,
|
2023-09-21 15:13:30 +01:00
|
|
|
)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.sys_log.error(f"Unable to retrieve file from {self.sys_log.hostname}: {e}")
|
|
|
|
|
return False
|
2023-10-03 14:59:48 +01:00
|
|
|
|
|
|
|
|
def send(
|
|
|
|
|
self,
|
|
|
|
|
payload: FTPPacket,
|
|
|
|
|
session_id: Optional[str] = None,
|
|
|
|
|
dest_ip_address: Optional[IPv4Address] = None,
|
|
|
|
|
dest_port: Optional[Port] = None,
|
|
|
|
|
**kwargs,
|
|
|
|
|
) -> bool:
|
|
|
|
|
"""
|
|
|
|
|
Sends a payload to the SessionManager.
|
|
|
|
|
|
|
|
|
|
:param payload: The payload to be sent.
|
|
|
|
|
:param dest_ip_address: The ip address of the payload destination.
|
|
|
|
|
:param dest_port: The port of the payload destination.
|
|
|
|
|
:param session_id: The Session ID the payload is to originate from. Optional.
|
|
|
|
|
|
|
|
|
|
:return: True if successful, False otherwise.
|
|
|
|
|
"""
|
|
|
|
|
self.sys_log.info(f"{self.name}: Sending FTP {payload.ftp_command.name} {payload.ftp_command_args}")
|
|
|
|
|
|
|
|
|
|
return super().send(
|
|
|
|
|
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id, **kwargs
|
|
|
|
|
)
|