feat(udp): get command finished
- Files size should be around 750 bytes or less - Regenerate test files in docker and in `client_directory` & `server_directory` folders - Abstract the client and server: UDPClient -> Client
This commit is contained in:
		@@ -6,8 +6,8 @@
 | 
			
		||||
from socket import socket, AF_INET, SOCK_DGRAM
 | 
			
		||||
from typing import Pattern, Tuple
 | 
			
		||||
from argparse import ArgumentParser
 | 
			
		||||
import traceback
 | 
			
		||||
import os
 | 
			
		||||
import pickle
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -19,104 +19,119 @@ summary_command_pattern: Pattern = re.compile(r"^summary\s+[^\s]+$")
 | 
			
		||||
change_command_pattern: Pattern = re.compile(r"^change\s+[^\s]+\s+[^\s]+$")
 | 
			
		||||
 | 
			
		||||
# opcodes
 | 
			
		||||
put_request_opcode:str = "000"
 | 
			
		||||
get_request_opcode:str = "001"
 | 
			
		||||
change_request_opcode: str = "010"
 | 
			
		||||
summary_request_opcode: str = "011"
 | 
			
		||||
help_requrest_opcode: str = "100"
 | 
			
		||||
put_request_opcode: int = 0b000
 | 
			
		||||
get_request_opcode: int = 0b001
 | 
			
		||||
change_request_opcode: int = 0b010
 | 
			
		||||
summary_request_opcode: int = 0b011
 | 
			
		||||
help_request_opcode: int = 0b100
 | 
			
		||||
 | 
			
		||||
# Res-code dict
 | 
			
		||||
rescode_dict: dict[str, str] = {
 | 
			
		||||
    "000": "Put/Change Request Successful",
 | 
			
		||||
    "001": "Get Request Successful",
 | 
			
		||||
    "010": "Summary Request Successful",
 | 
			
		||||
    "011": "File Not Found Error",
 | 
			
		||||
    "100": "Unknown Request",
 | 
			
		||||
    "101": "Change Unsuccessful Error",
 | 
			
		||||
    "110": "Help"
 | 
			
		||||
rescode_dict: dict[int, str] = {
 | 
			
		||||
    0b011: "File Not Found Error",
 | 
			
		||||
    0b100: "Unknown Request",
 | 
			
		||||
    0b101: "Change Unsuccessful Error",
 | 
			
		||||
    0b000: "Put/Change Request Successful",
 | 
			
		||||
    0b001: "Get Request Successful",
 | 
			
		||||
    0b010: "Summary Request Successful",
 | 
			
		||||
    0b110: "Help",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# custome type to represent the hostname(server name) and the server port
 | 
			
		||||
Address = Tuple[str, int]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UDPClient:
 | 
			
		||||
    def __init__(self, server_name: str, server_port: int, debug: bool):
 | 
			
		||||
class Client:
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        server_name: str,
 | 
			
		||||
        server_port: int,
 | 
			
		||||
        directory_path: str,
 | 
			
		||||
        debug: bool,
 | 
			
		||||
        protocol: str,
 | 
			
		||||
    ):
 | 
			
		||||
        self.server_name: str = server_name
 | 
			
		||||
        self.server_port: int = server_port
 | 
			
		||||
        self.mode: str = "UDP"
 | 
			
		||||
        self.pong_received: bool = False
 | 
			
		||||
        self.protocol: str = protocol
 | 
			
		||||
        self.directory_path = directory_path
 | 
			
		||||
        self.debug = debug
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
 | 
			
		||||
        # server cannot be reached, stop the client immediately
 | 
			
		||||
        if not self.pong_received:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        client_socket = socket(AF_INET, SOCK_DGRAM)
 | 
			
		||||
        client_socket.settimeout(10)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            while True:
 | 
			
		||||
                # get command from user
 | 
			
		||||
                command = input(f"myftp> - {self.mode} - : ").strip().lower()
 | 
			
		||||
                command = input(f"myftp> - {self.protocol} - : ").strip().lower()
 | 
			
		||||
 | 
			
		||||
                # handling the "bye" command
 | 
			
		||||
                if command == "bye":
 | 
			
		||||
                    client_socket.close()
 | 
			
		||||
                    print(f"myftp> - {self.mode} - Session is terminated")
 | 
			
		||||
                    print(f"myftp> - {self.protocol} - Session is terminated")
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
                # list files available on the server
 | 
			
		||||
                elif command == "list":
 | 
			
		||||
                    self.get_files_list_from_server(client_socket)
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                # help
 | 
			
		||||
                elif command == "help":
 | 
			
		||||
                    request_payload: str = help_requrest_opcode + "00000" # 10000000 
 | 
			
		||||
                    first_byte: int = help_request_opcode << 5
 | 
			
		||||
                    command_name = "help"
 | 
			
		||||
 | 
			
		||||
                    print(
 | 
			
		||||
                        f"myftp> - {self.mode} - Asking for help from the server"
 | 
			
		||||
                        f"myftp> - {self.protocol} - Asking for help from the server"
 | 
			
		||||
                    ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
                # get command handling
 | 
			
		||||
                elif get_command_pattern.match(command):
 | 
			
		||||
                    _, filename = command.split(" ", 1)
 | 
			
		||||
                    command_name, filename = command.split(" ", 1)
 | 
			
		||||
 | 
			
		||||
                    first_byte = (get_request_opcode << 5) + len(filename)
 | 
			
		||||
 | 
			
		||||
                    second_byte_to_n_byte: bytes = filename.encode("ascii")
 | 
			
		||||
 | 
			
		||||
                    print(
 | 
			
		||||
                        f"myftp> - {self.mode} - : Getting file {filename} from the server"
 | 
			
		||||
                        f"myftp> - {self.protocol} - Getting file {filename} from the server"
 | 
			
		||||
                    ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
                # put command handling
 | 
			
		||||
                elif put_command_pattern.match(command):
 | 
			
		||||
                    _, filename = command.split(" ", 1)
 | 
			
		||||
                    print(
 | 
			
		||||
                        f"myftp> - {self.mode} - : Putting file {filename} into the server"
 | 
			
		||||
                        f"myftp> - {self.protocol} - Putting file {filename} into the server"
 | 
			
		||||
                    ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
                # summary command handling
 | 
			
		||||
                elif summary_command_pattern.match(command):
 | 
			
		||||
                    _, filename = command.split(" ", 1)
 | 
			
		||||
                    print(
 | 
			
		||||
                        f"myftp> - {self.mode} - : Summary file {filename} from the server"
 | 
			
		||||
                        f"myftp> - {self.protocol} - Summary file {filename} from the server"
 | 
			
		||||
                    ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
                # change command handling
 | 
			
		||||
                elif change_command_pattern.match(command):
 | 
			
		||||
                    _, old_filename, new_filename = command.split()
 | 
			
		||||
                    print(
 | 
			
		||||
                        f"myftp> - {self.mode} - : Changing file named {old_filename} into {new_filename} on the server"
 | 
			
		||||
                        f"myftp> - {self.protocol} - Changing file named {old_filename} into {new_filename} on the server"
 | 
			
		||||
                    ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
                else:
 | 
			
		||||
                    print(
 | 
			
		||||
                        f"myftp> - {self.mode} - : Invalid command. Supported commands are put, get, summary, change, list and help. Type help for detailed usage."
 | 
			
		||||
                        f"myftp> - {self.protocol} - Invalid command. Supported commands are put, get, summary, change, list and help. Type help for detailed usage."
 | 
			
		||||
                    )
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                # convert the payload to bytes so it can be sent to the server
 | 
			
		||||
                byte_representation_req_payload: bytes = bytes([int(request_payload, 2)])
 | 
			
		||||
                # get or put case
 | 
			
		||||
                if command_name == "get" or command_name == "put":
 | 
			
		||||
                    payload = first_byte.to_bytes(1, "big") + second_byte_to_n_byte
 | 
			
		||||
 | 
			
		||||
                client_socket.sendto(byte_representation_req_payload, (self.server_name, self.server_port))
 | 
			
		||||
                # help case
 | 
			
		||||
                else:
 | 
			
		||||
                    payload: bytes = first_byte.to_bytes(1, "big")
 | 
			
		||||
 | 
			
		||||
                print(
 | 
			
		||||
                    f"myftp> - {self.protocol} - sent payload {bin(int.from_bytes(payload, byteorder='big'))[2:]} to the server"
 | 
			
		||||
                ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
                client_socket.sendto(payload, (self.server_name, self.server_port))
 | 
			
		||||
 | 
			
		||||
                response_payload = client_socket.recv(2048)
 | 
			
		||||
 | 
			
		||||
@@ -124,83 +139,86 @@ class UDPClient:
 | 
			
		||||
 | 
			
		||||
        except ConnectionRefusedError:
 | 
			
		||||
            print(
 | 
			
		||||
                f"myftp> - {self.mode} - ConnectionRefusedError happened. Please restart the client program, make sure the server is running and/or put a different server name and server port."
 | 
			
		||||
                f"myftp> - {self.protocol} - ConnectionRefusedError happened. Please restart the client program, make sure the server is running and/or put a different server name and server port."
 | 
			
		||||
            )
 | 
			
		||||
        except Exception as error:
 | 
			
		||||
            print(
 | 
			
		||||
                f"myftp> - {self.mode} - {error} happened."
 | 
			
		||||
            )
 | 
			
		||||
        finally:
 | 
			
		||||
            client_socket.close()
 | 
			
		||||
 | 
			
		||||
    # ping pong UDP
 | 
			
		||||
    def check_udp_server(self):
 | 
			
		||||
        # Create a UDP socket
 | 
			
		||||
        client_socket = socket(AF_INET, SOCK_DGRAM)
 | 
			
		||||
        # will time out after 5 seconds
 | 
			
		||||
        client_socket.settimeout(5)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            # Send a test message to the server
 | 
			
		||||
            message = b"ping"
 | 
			
		||||
            client_socket.sendto(message, (self.server_name, self.server_port))
 | 
			
		||||
 | 
			
		||||
            # Receive the response
 | 
			
		||||
            data, _ = client_socket.recvfrom(1024)
 | 
			
		||||
 | 
			
		||||
            # If the server responds, consider the address valid
 | 
			
		||||
            print(
 | 
			
		||||
                f"myftp> - {self.mode} - Server at {self.server_name}:{self.server_port} is valid. Response received: {data.decode('utf-8')}"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # code reached here meaning no problem with the connection
 | 
			
		||||
            self.pong_received = True
 | 
			
		||||
 | 
			
		||||
        except TimeoutError:
 | 
			
		||||
            # Server did not respond within the specified timeout
 | 
			
		||||
            print(
 | 
			
		||||
                f"myftp> - {self.mode} - Server at {self.server_name} did not respond within 5 seconds. Check the address or server status."
 | 
			
		||||
                f"myftp> - {self.protocol} - Server at {self.server_name} did not respond within 5 seconds. Check the address or server status."
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        except Exception as error:
 | 
			
		||||
            traceback_info = traceback.format_exc()
 | 
			
		||||
 | 
			
		||||
            print(f"myftp> - {self.protocol} - {error} happened.")
 | 
			
		||||
 | 
			
		||||
            print(traceback_info)
 | 
			
		||||
 | 
			
		||||
        finally:
 | 
			
		||||
            # Close the socket
 | 
			
		||||
            client_socket.close()
 | 
			
		||||
 | 
			
		||||
    # get list of files currently on the server
 | 
			
		||||
    def get_files_list_from_server(self, client_socket: socket) -> list[str]:
 | 
			
		||||
        client_socket.send("list".encode())
 | 
			
		||||
        encoded_message, server_address = client_socket.recvfrom(4096)
 | 
			
		||||
        file_list = pickle.loads(encoded_message)
 | 
			
		||||
        print(f"Received file list from {server_address}: {file_list}")
 | 
			
		||||
        client_socket.close()
 | 
			
		||||
 | 
			
		||||
        return file_list
 | 
			
		||||
 | 
			
		||||
    def parse_response_payload(self,
 | 
			
		||||
                               response_payload: bytes):
 | 
			
		||||
 | 
			
		||||
        # we want to get the first byte as a string i.e "01010010"
 | 
			
		||||
        first_byte: str = bin(response_payload[0])[2:].zfill(8)
 | 
			
		||||
        rescode: str = first_byte[:3]
 | 
			
		||||
        response_data_length: int = int(first_byte[-5:], 2)
 | 
			
		||||
        response_data: bytes = response_payload[1:]
 | 
			
		||||
    def parse_response_payload(self, response_payload: bytes):
 | 
			
		||||
        first_byte = bytes([response_payload[0]])
 | 
			
		||||
        first_byte_binary = int.from_bytes(first_byte, "big")
 | 
			
		||||
        rescode = first_byte_binary >> 5
 | 
			
		||||
        filename_length = first_byte_binary & 0b00011111
 | 
			
		||||
        response_data = response_payload[1:]
 | 
			
		||||
        response_data_length = len(response_data)
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            f"myftp> - {self.mode} - First_byte from server response: {first_byte}. Rescode: {rescode}. Data length: {response_data_length}"
 | 
			
		||||
            f"myftp> - {self.protocol} - First_byte from server response: {first_byte}. Rescode: {rescode}. File name length: {filename_length}. Data length: {response_data_length}"
 | 
			
		||||
        ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            print(
 | 
			
		||||
                f"myftp> - {self.mode} - Res-code meaning: {rescode_dict[rescode]}"
 | 
			
		||||
                f"myftp> - {self.protocol} - Res-code meaning: {rescode_dict[rescode]}"
 | 
			
		||||
            ) if self.debug else None
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            print(f"myftp> - {self.protocol} - Res-code does not have meaning")
 | 
			
		||||
 | 
			
		||||
        # error rescodes
 | 
			
		||||
        if rescode in [0b011, 0b100, 0b101]:
 | 
			
		||||
            print(f"myftp> - {self.protocol} - {rescode_dict[rescode]}")
 | 
			
		||||
 | 
			
		||||
        # successful rescodes
 | 
			
		||||
        else:
 | 
			
		||||
            if rescode == 0b110:
 | 
			
		||||
                print(f"myftp> - {self.protocol} - {response_data.decode('ascii')}")
 | 
			
		||||
            else:
 | 
			
		||||
                self.handle_get_response_from_server(filename_length, response_data)
 | 
			
		||||
 | 
			
		||||
    def handle_get_response_from_server(
 | 
			
		||||
        self, filename_length: int, response_data: bytes
 | 
			
		||||
    ):
 | 
			
		||||
        """
 | 
			
		||||
        response_data is
 | 
			
		||||
        file name (filename_length bytes) +
 | 
			
		||||
        file size (4 bytes) +
 | 
			
		||||
        file content (rest of the bytes)
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            filename = response_data[:filename_length].decode("ascii")
 | 
			
		||||
            file_size = int.from_bytes(
 | 
			
		||||
                response_data[filename_length : filename_length + 4], "big"
 | 
			
		||||
            )
 | 
			
		||||
            file_content = response_data[
 | 
			
		||||
                filename_length + 4 : filename_length + 4 + file_size
 | 
			
		||||
            ]
 | 
			
		||||
 | 
			
		||||
            print(
 | 
			
		||||
                f"myftp> - {self.mode} - Res-code does not have meaning"
 | 
			
		||||
                f"myftp> - {self.protocol} - Filename: {filename}, File_size: {file_size} bytes"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            f"myftp> - {self.mode} - {response_data.decode()}"
 | 
			
		||||
        )
 | 
			
		||||
            with open(os.path.join(self.directory_path, filename), "wb") as file:
 | 
			
		||||
                file.write(file_content)
 | 
			
		||||
 | 
			
		||||
            print(
 | 
			
		||||
                f"myftp> - {self.protocol} - File {filename} has been downloaded successfully"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        except Exception:
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_address_input() -> Address:
 | 
			
		||||
@@ -223,9 +241,9 @@ def get_address_input() -> Address:
 | 
			
		||||
            # Valid tuple, return it
 | 
			
		||||
            return address
 | 
			
		||||
 | 
			
		||||
        except ValueError as e:
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            print(
 | 
			
		||||
                f"Error: Invalid input. Please enter a servername/hostname/ip address as a string and the port number as an integer separated by a space."
 | 
			
		||||
                "Error: Invalid input. Please enter a servername/hostname/ip address as a string and the port number as an integer separated by a space."
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -272,20 +290,29 @@ def init():
 | 
			
		||||
        )
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    user_supplied_address = get_address_input()
 | 
			
		||||
 | 
			
		||||
    # UDP client selected here
 | 
			
		||||
    if protocol_selection == "2":
 | 
			
		||||
        user_supplied_address = get_address_input()
 | 
			
		||||
 | 
			
		||||
        udp_client = UDPClient(
 | 
			
		||||
            user_supplied_address[0], user_supplied_address[1], args.debug
 | 
			
		||||
        udp_client = Client(
 | 
			
		||||
            user_supplied_address[0],
 | 
			
		||||
            user_supplied_address[1],
 | 
			
		||||
            args.directory,
 | 
			
		||||
            args.debug,
 | 
			
		||||
            "UDP",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        udp_client.check_udp_server()
 | 
			
		||||
 | 
			
		||||
        udp_client.run()
 | 
			
		||||
    else:
 | 
			
		||||
        # tcp client here
 | 
			
		||||
        pass
 | 
			
		||||
        tcp_client = Client(
 | 
			
		||||
            user_supplied_address[0],
 | 
			
		||||
            user_supplied_address[1],
 | 
			
		||||
            args.directory,
 | 
			
		||||
            args.debug,
 | 
			
		||||
            "TCP",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        tcp_client.run()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
 
 | 
			
		||||
@@ -5,33 +5,45 @@
 | 
			
		||||
 | 
			
		||||
from socket import socket, AF_INET, SOCK_DGRAM
 | 
			
		||||
from argparse import ArgumentParser
 | 
			
		||||
from typing import Optional, Tuple
 | 
			
		||||
import os
 | 
			
		||||
import pickle
 | 
			
		||||
 | 
			
		||||
# Res-code
 | 
			
		||||
correct_put_and_change_request_rescode: str = "000"
 | 
			
		||||
correct_get_request_rescode: str = "001"
 | 
			
		||||
correct_summary_request_rescode: str = "010"
 | 
			
		||||
file_not_error_rescode: str = "011"
 | 
			
		||||
unknown_request_rescode: str = "100"
 | 
			
		||||
unsuccessful_change_rescode: str = "101"
 | 
			
		||||
help_rescode: str = "110"
 | 
			
		||||
# Res-codes
 | 
			
		||||
rescode_success_dict: dict[str, int] = {
 | 
			
		||||
    "correct_put_and_change_request_rescode": 0b000,
 | 
			
		||||
    "correct_get_request_rescode": 0b001,
 | 
			
		||||
    "correct_summary_request_rescode": 0b010,
 | 
			
		||||
    "help_rescode": 0b110,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
rescode_fail_dict: dict[str, int] = {
 | 
			
		||||
    "file_not_error_rescode": 0b011,
 | 
			
		||||
    "unknown_request_rescode": 0b100,
 | 
			
		||||
    "unsuccessful_change_rescode": 0b101,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# opcodes
 | 
			
		||||
put_request_opcode: str = "000"
 | 
			
		||||
get_request_opcode: str = "001"
 | 
			
		||||
change_request_opcode: str = "010"
 | 
			
		||||
summary_request_opcode: str = "011"
 | 
			
		||||
help_requrest_opcode: str = "100"
 | 
			
		||||
op_codes_dict: dict[int, str] = {
 | 
			
		||||
    0b000: "put",
 | 
			
		||||
    0b001: "get",
 | 
			
		||||
    0b010: "change",
 | 
			
		||||
    0b011: "summary",
 | 
			
		||||
    0b100: "help",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UDPServer:
 | 
			
		||||
class Server:
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self, server_name: str, server_port: int, directory_path: str, debug: bool
 | 
			
		||||
        self,
 | 
			
		||||
        server_name: str,
 | 
			
		||||
        server_port: int,
 | 
			
		||||
        directory_path: str,
 | 
			
		||||
        debug: bool,
 | 
			
		||||
        protocol: str,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        self.server_name = server_name
 | 
			
		||||
        self.server_port = server_port
 | 
			
		||||
        self.mode: str = "UDP"
 | 
			
		||||
        self.protocol: str = protocol
 | 
			
		||||
        self.directory_path = directory_path
 | 
			
		||||
        self.debug = debug
 | 
			
		||||
 | 
			
		||||
@@ -40,122 +52,188 @@ class UDPServer:
 | 
			
		||||
        self.server_socket.bind((self.server_name, self.server_port))
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            f"myftp> - {self.mode} - Server is ready to receive at {self.server_name}:{self.server_port}"
 | 
			
		||||
            f"myftp> - {self.protocol} - Server is ready to receive at {self.server_name}:{self.server_port}"
 | 
			
		||||
        ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
        shut_down = False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            while not shut_down:
 | 
			
		||||
                message, clientAddress = self.server_socket.recvfrom(2048)
 | 
			
		||||
 | 
			
		||||
                # decode for quick and dirty commands like ping and list server files
 | 
			
		||||
                # outside of the scope of the project
 | 
			
		||||
                try:
 | 
			
		||||
                    request_payload = message.decode()
 | 
			
		||||
 | 
			
		||||
                except UnicodeDecodeError:
 | 
			
		||||
                    # most commands (get, put, summary ...) will be handled by this catch block
 | 
			
		||||
                    request_payload: str = bin(int.from_bytes(message, byteorder='big'))[2:]
 | 
			
		||||
 | 
			
		||||
                print(
 | 
			
		||||
                    f"myftp> - {self.mode} ------------------------------------------------------------------"
 | 
			
		||||
                    f"myftp> - {self.protocol} ------------------------------------------------------------------"
 | 
			
		||||
                ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
                req_payload, clientAddress = self.server_socket.recvfrom(2048)
 | 
			
		||||
 | 
			
		||||
                first_byte = bytes([req_payload[0]])
 | 
			
		||||
 | 
			
		||||
                request_type, filename_length = self.decode_first_byte(first_byte)
 | 
			
		||||
 | 
			
		||||
                print(
 | 
			
		||||
                    f"myftp> - {self.mode} - Received message from client at {clientAddress}: {request_payload}"
 | 
			
		||||
                    f"myftp> - {self.protocol} - Received message from client at {clientAddress}: {req_payload}"
 | 
			
		||||
                ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
                # check for connectivity
 | 
			
		||||
                if request_payload == "ping":
 | 
			
		||||
                    self.server_socket.sendto("pong".encode(), clientAddress)
 | 
			
		||||
 | 
			
		||||
                    print(
 | 
			
		||||
                        f"myftp> - {self.mode} - pong sent back to client"
 | 
			
		||||
                    ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                # list files available on server
 | 
			
		||||
                elif request_payload == "list":
 | 
			
		||||
                    encoded_message = pickle.dumps(
 | 
			
		||||
                        get_files_in_directory(self.directory_path)
 | 
			
		||||
                    )
 | 
			
		||||
                    self.server_socket.sendto(encoded_message, clientAddress)
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                # help request handling
 | 
			
		||||
                elif request_payload == help_requrest_opcode + "00000":
 | 
			
		||||
                if request_type == "help":
 | 
			
		||||
                    print(
 | 
			
		||||
                        f"myftp> - {self.mode} - Client message parsed. Received help request"
 | 
			
		||||
                        f"myftp> - {self.protocol} - Client message parsed. Received help request"
 | 
			
		||||
                    ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
                    rescode = help_rescode
 | 
			
		||||
                    response_data_string = "get,put,summary,change,help,bye"
 | 
			
		||||
                    rescode = rescode_success_dict["help_rescode"]
 | 
			
		||||
                    response_data = "get,put,summary,change,help,bye".encode("ascii")
 | 
			
		||||
                    filename = None
 | 
			
		||||
                    filename_length = None
 | 
			
		||||
 | 
			
		||||
                else:
 | 
			
		||||
                    # handle unrecognized request here
 | 
			
		||||
                    pass
 | 
			
		||||
                elif request_type == "get":
 | 
			
		||||
                    pre_payload = self.process_get_req(req_payload[1:])
 | 
			
		||||
 | 
			
		||||
                payload: bytes = self.build_res_payload(rescode, response_data_string)
 | 
			
		||||
                    if (
 | 
			
		||||
                        pre_payload[0] is not None
 | 
			
		||||
                        and pre_payload[1] is not None
 | 
			
		||||
                        and pre_payload[2] is not None
 | 
			
		||||
                    ):
 | 
			
		||||
                        rescode = rescode_success_dict["correct_get_request_rescode"]
 | 
			
		||||
                        filename = pre_payload[0]
 | 
			
		||||
                        filename_length = pre_payload[2]
 | 
			
		||||
                        response_data = pre_payload[1]
 | 
			
		||||
 | 
			
		||||
                self.server_socket.sendto(payload, clientAddress)
 | 
			
		||||
                    else:
 | 
			
		||||
                        rescode = rescode_fail_dict["file_not_error_rescode"]
 | 
			
		||||
                        filename_length = None
 | 
			
		||||
                        filename = None
 | 
			
		||||
                        response_data = None
 | 
			
		||||
 | 
			
		||||
                res_payload: bytes = self.build_res_payload(
 | 
			
		||||
                    rescode=rescode,  # type: ignore
 | 
			
		||||
                    filename_length=filename_length,
 | 
			
		||||
                    filename=filename,  # type: ignore
 | 
			
		||||
                    response_data=response_data,  # type:ignore
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                self.server_socket.sendto(res_payload, clientAddress)
 | 
			
		||||
 | 
			
		||||
                print(
 | 
			
		||||
                    f"myftp> - {self.mode} - Sent message to client at {clientAddress}: {payload}"
 | 
			
		||||
                    f"myftp> - {self.protocol} - Sent message to client at {clientAddress}: {res_payload}"
 | 
			
		||||
                ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
        except KeyboardInterrupt:
 | 
			
		||||
            shut_down = True
 | 
			
		||||
            self.server_socket.close()
 | 
			
		||||
            print(f"myftp> - {self.mode} - Server shutting down")
 | 
			
		||||
            print(f"myftp> - {self.protocol} - Server shutting down")
 | 
			
		||||
 | 
			
		||||
        finally:
 | 
			
		||||
            print(f"myftp> - {self.mode} - Closed the server socket")
 | 
			
		||||
            print(f"myftp> - {self.protocol} - Closed the server socket")
 | 
			
		||||
 | 
			
		||||
    def decode_first_byte(self, first_byte: bytes) -> Tuple[str, int]:
 | 
			
		||||
        """
 | 
			
		||||
        Retrieve the request_type from first byte
 | 
			
		||||
        """
 | 
			
		||||
        if len(first_byte) != 1:
 | 
			
		||||
            raise ValueError("Input is not 1 byte")
 | 
			
		||||
 | 
			
		||||
        first_byte_to_binary = int.from_bytes(first_byte, "big")
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            request_type = op_codes_dict[first_byte_to_binary >> 5]
 | 
			
		||||
 | 
			
		||||
            filename_length_in_bytes = first_byte_to_binary & 0b00011111
 | 
			
		||||
 | 
			
		||||
            print(
 | 
			
		||||
                f"myftp> - {self.protocol} - First byte parsed. Request type: {request_type}. Filename length in bytes: {filename_length_in_bytes}"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            raise KeyError("Can't find the request type of this payload")
 | 
			
		||||
 | 
			
		||||
        return request_type, filename_length_in_bytes
 | 
			
		||||
 | 
			
		||||
    def process_get_req(
 | 
			
		||||
        self, second_byte_to_byte_n: bytes
 | 
			
		||||
    ) -> Tuple[Optional[str], Optional[bytes], Optional[int]]:
 | 
			
		||||
        """
 | 
			
		||||
        Process the get request
 | 
			
		||||
 | 
			
		||||
        If successful, return the filename, content and the content_length
 | 
			
		||||
 | 
			
		||||
        If not, return None, None, None tuple
 | 
			
		||||
        """
 | 
			
		||||
        filename = second_byte_to_byte_n.decode("ascii")
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            with open(os.path.join(self.directory_path, filename), "rb") as file:
 | 
			
		||||
                content = file.read()
 | 
			
		||||
                content_length = len(content)
 | 
			
		||||
 | 
			
		||||
                return filename, content, content_length
 | 
			
		||||
 | 
			
		||||
        except FileNotFoundError:
 | 
			
		||||
            print(f"myftp> - {self.protocol} - file {filename} not found")
 | 
			
		||||
            return (None, None, None)
 | 
			
		||||
 | 
			
		||||
    # assembling the payload to send back to the client
 | 
			
		||||
    def build_res_payload(self,
 | 
			
		||||
                          rescode: str,
 | 
			
		||||
                          response_data_string: str) -> bytes:
 | 
			
		||||
    def build_res_payload(
 | 
			
		||||
        self,
 | 
			
		||||
        rescode: int,
 | 
			
		||||
        filename_length: Optional[int] = None,
 | 
			
		||||
        filename: Optional[str] = None,
 | 
			
		||||
        response_data: Optional[bytes] = None,
 | 
			
		||||
    ) -> bytes:
 | 
			
		||||
        print(
 | 
			
		||||
            f"myftp> - {self.protocol} - Assembling response payload to be sent back to the client"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        print(f"myftp> - {self.mode} - Assembling response payload to be sent back to the client")
 | 
			
		||||
        data_len = len(response_data) if response_data is not None else None
 | 
			
		||||
 | 
			
		||||
        bytes_response_data = response_data_string.encode("utf-8")
 | 
			
		||||
        print(
 | 
			
		||||
            f"myftp> - {self.protocol} - Rescode {format(rescode, '03b')}"
 | 
			
		||||
        ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
        data_len = len(bytes_response_data)
 | 
			
		||||
        print(
 | 
			
		||||
            f"myftp> - {self.protocol} - Length of data {data_len}"
 | 
			
		||||
        ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
        print(f"myftp> - {self.mode} - Rescode {rescode}") if self.debug else None
 | 
			
		||||
        print(f"myftp> - {self.mode} - Length of data {data_len}") if self.debug else None
 | 
			
		||||
        print(f"myftp> - {self.mode} - Data {response_data_string}") if self.debug else None
 | 
			
		||||
        print(
 | 
			
		||||
            f"myftp> - {self.protocol} - Data {response_data}"
 | 
			
		||||
        ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
        # convert to binary
 | 
			
		||||
        try:
 | 
			
		||||
            # pad the length of data to make sure it is always 5 bits
 | 
			
		||||
            # i.e "010" -> "00010"
 | 
			
		||||
            binary_data_len: str = bin(data_len).zfill(5)
 | 
			
		||||
            # get case
 | 
			
		||||
            if filename is not None:
 | 
			
		||||
                first_byte = ((rescode << 5) + len(filename)).to_bytes(1, "big")
 | 
			
		||||
            # help case
 | 
			
		||||
            elif filename is None and response_data is not None:
 | 
			
		||||
                first_byte = ((rescode << 5) + len(response_data)).to_bytes(1, "big")
 | 
			
		||||
            # unsuccessful cases
 | 
			
		||||
            else:
 | 
			
		||||
                first_byte = (rescode << 5).to_bytes(1, "big")
 | 
			
		||||
 | 
			
		||||
            print(f"myftp> - {self.mode} - binary_data_len {binary_data_len[2:]}") if self.debug else None
 | 
			
		||||
            # we only need the firstbyte
 | 
			
		||||
            if filename is None:
 | 
			
		||||
                second_byte_to_FL_plus_five = None
 | 
			
		||||
            else:
 | 
			
		||||
                second_byte_to_FL_plus_five = (
 | 
			
		||||
                    filename.encode() + len(response_data).to_bytes(4, "big")
 | 
			
		||||
                    if response_data is not None
 | 
			
		||||
                    else None
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            # create the first byte
 | 
			
		||||
            # since binary_data_len is of the format 0b00100, we have to remove the first two characters 0b
 | 
			
		||||
            first_byte: bytes = bytes([int(rescode + binary_data_len[2:], 2)])
 | 
			
		||||
            print(
 | 
			
		||||
                f"myftp> - {self.protocol} - First byte assembled for rescode {format(rescode, '03b')}: {bin(int.from_bytes(first_byte, byteorder='big'))[2:]}"
 | 
			
		||||
            ) if self.debug else None
 | 
			
		||||
 | 
			
		||||
            print(f"myftp> - {self.mode} - First byte assembled for rescode {rescode}: {bin(int.from_bytes(first_byte, byteorder='big'))[2:]}") if self.debug else None
 | 
			
		||||
            if second_byte_to_FL_plus_five is not None and response_data is not None:
 | 
			
		||||
                res_payload = first_byte + second_byte_to_FL_plus_five + response_data
 | 
			
		||||
            # help case
 | 
			
		||||
            elif second_byte_to_FL_plus_five is None and response_data is not None:
 | 
			
		||||
                res_payload = first_byte + response_data
 | 
			
		||||
            else:
 | 
			
		||||
                res_payload = first_byte
 | 
			
		||||
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            raise Exception(e)
 | 
			
		||||
            return res_payload
 | 
			
		||||
 | 
			
		||||
        res_payload = first_byte + bytes_response_data
 | 
			
		||||
 | 
			
		||||
        return res_payload
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_files_in_directory(directory_path: str) -> list[str]:
 | 
			
		||||
    file_list = []
 | 
			
		||||
    for _, _, files in os.walk(directory_path):
 | 
			
		||||
        for file in files:
 | 
			
		||||
            file_list.append(file)
 | 
			
		||||
    return file_list
 | 
			
		||||
        except Exception:
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def check_directory(path: str) -> bool:
 | 
			
		||||
@@ -218,15 +296,18 @@ def init():
 | 
			
		||||
 | 
			
		||||
    # UDP client selected here
 | 
			
		||||
    if protocol_selection == "2":
 | 
			
		||||
        udp_server = UDPServer(
 | 
			
		||||
            args.ip_addr, args.port_number, args.directory, args.debug
 | 
			
		||||
        udp_server = Server(
 | 
			
		||||
            args.ip_addr, args.port_number, args.directory, args.debug, "UDP"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        udp_server.run()
 | 
			
		||||
 | 
			
		||||
    else:
 | 
			
		||||
        # tcp client here
 | 
			
		||||
        pass
 | 
			
		||||
        tcp_server = Server(
 | 
			
		||||
            args.ip_addr, args.port_number, args.directory, args.debug, "TCP"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        tcp_server.run()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user