From dcd8c1a1a5fdfdc1ae249dd4a8bf3f0f0472a55c Mon Sep 17 00:00:00 2001 From: minhtrannhat Date: Wed, 6 Dec 2023 17:30:12 -0500 Subject: [PATCH] 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 --- Dockerfile.client | 6 +- Dockerfile.server | 5 +- README.md | 2 +- client_directory/file_local.txt | Bin 0 -> 750 bytes client_directory/image_local.png | Bin 0 -> 750 bytes server_directory/file_server.txt | Bin 0 -> 750 bytes server_directory/image_server.txt | Bin 0 -> 750 bytes src/myftp/client.py | 241 ++++++++++++++------------ src/myftp/server.py | 271 +++++++++++++++++++----------- 9 files changed, 318 insertions(+), 207 deletions(-) create mode 100644 client_directory/file_local.txt create mode 100644 client_directory/image_local.png create mode 100644 server_directory/file_server.txt create mode 100644 server_directory/image_server.txt diff --git a/Dockerfile.client b/Dockerfile.client index 38b37ae..ee14fef 100644 --- a/Dockerfile.client +++ b/Dockerfile.client @@ -9,8 +9,10 @@ WORKDIR /client # Copy the local client to the container RUN mkdir /client_directory -RUN dd if=/dev/urandom of=/client_directory/file_local.txt bs=1024 count=10 -RUN dd if=/dev/urandom of=/client_directory/image_local.png bs=1024 count=50 + +# these files is only 750 bytes +RUN dd if=/dev/urandom of=/client_directory/file_local.txt bs=1 count=750 +RUN dd if=/dev/urandom of=/client_directory/image_local.png bs=1 count=750 # Start your Python application #CMD python client.py --debug 1 diff --git a/Dockerfile.server b/Dockerfile.server index 0a8ef43..3d8b45c 100644 --- a/Dockerfile.server +++ b/Dockerfile.server @@ -9,8 +9,9 @@ WORKDIR /server # Create files with random content in the /server directory RUN mkdir /server_directory -RUN dd if=/dev/urandom of=/server_directory/file_server.txt bs=1024 count=10 -RUN dd if=/dev/urandom of=/server_directory/image_server.png bs=1024 count=50 +# these files is only 750 bytes +RUN dd if=/dev/urandom of=/server_directory/file_server.txt bs=1 count=750 +RUN dd if=/dev/urandom of=/server_directory/image_server.png bs=1 count=750 # Start your Python application #CMD python server.py --port_number 12000 --debug 1 diff --git a/README.md b/README.md index 44ee8a4..7dde3de 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ You can run `python3 src/myftp/client.py --directory ` to start the server or `python3 src/myftp/server.py --ip_addr --port_number --debug 1 --directory ` for debugging purposes and to specify the port number. diff --git a/client_directory/file_local.txt b/client_directory/file_local.txt new file mode 100644 index 0000000000000000000000000000000000000000..686cfdf36cecaf41601651e797502aa10f58ef72 GIT binary patch literal 750 zcmV3S zcrBg65L#fVUKAaI>^zmVYH5-;qr`%#D$3Efxaosf16+VmVrzT-%YMcYJGfTTfg5S% zb)Yk4I%wR-1$>1-au`GORmb+hX15|Z&%&LyMtxJUgY0F*HXj} z6cw0CLs2@FNfa8)Jl|%}^Ic> z9SDAJ0{VTUfd2ZJ=k=++%GLT>?bA5|IHK7&uR#17&vhuLBBVM?sF~8UN&Jfm{RTHD!W! z(|2R6K_!{y?5g}2SLmPtF#_PR?4LCYBuU>j5xqYsj6KHVnPF@(TG0lpH^NdFz)ibP zp7N`zvndhFMK#o?8&=>9wKcH#F7cae>^a?($EiyK;X(J$&|n~vJ6RScjD^F`zs`Ht zJN+X+-~`)QVce4MUkT@dPYh>N^}ew6SB}+qom>N;Gkk*QK3;fr!0MZ3vl5WmAAHje z`mDf4zmHU|bucp5bTowZLsI8`fFU}oeamtO)Mo3TOYtRHP_uy6`U&2Y5&GFKRXWH$ zX&r)yz;c)=3&aQB=)%0Jc@6=F07NAffvtEUOY+sil%?#`p&Y1b&khr3t19XyKPN?ou8FU=D%?_c3QdN;Ld^ zqvuISVO4?)BC%BCCWfCnkTaNdj#~UH>N2 zqbt3t1rf9GidBy6t}&4X(-Cj}X<+iguS$Dg0{%kZY zmlS)ccVR(j0>=$BoFE|!RK2&5mV}*y?y9L>ikE;MH7Kv9Gj;2yJTU)RA{=eO^H2F` zOwZ_+X6jJm@n!0U?J5Kl5bk3r86ggdJ0>%1pdFCYVcX`L;1*}+10eF(R@DCMY)}eo geRO|*OgC(@UNUDs3(_~0N%efQHr@!R*o!+!ho)$89RL6T literal 0 HcmV?d00001 diff --git a/server_directory/file_server.txt b/server_directory/file_server.txt new file mode 100644 index 0000000000000000000000000000000000000000..57b8c5802673a9f69044da0100492a37c15d37fc GIT binary patch literal 750 zcmVM44b=2}@62*$GD{Ub$vdAkDT<5)MJ@4SFzb-;P%dHMlObYwe`=T8u10 zcKP>i5iC&^##I6Mi&_fugGvXn>Kl9%7c(bMsvy4=S1nX182A=17TGPwhRyr2w6wG4 z;*qIt8#fMHgbnmu2L_f=gh=fE>Tb)8SiN>UF(prq2KQlT&Uvt>2_A#bYqr$;luV*0 zaJp7GK%fHu@&b8ZDx^bTRueYM#2_Tq9a+kJ8)sfKA|tXS?oyENS$vdOs3G4U6y8YA z#6K`g5g$Xhh*VsD3zMCDZnLfWZRPyX?y7A?h{Otl&(JD*Xwd##+hs0xgW`C9ig2Ki z&@1((;;Xtm`R;SqEQR&5(o};W%F?)$_$XC0p5u`zkFnmKw-;+&yvh&d&GN3(UmR(x zeQ~_Z>1VGM(B_uxr9kemos0R0s||h9QD&_DS>50z;&gXGAHY<$v^<`Sy^M4O%Vlx!XqRo1Y1yvki#kEHGe zgLUAFfD-^0mm#X8nUfG3>n?n3?31Jk5|VN{z|Ohc#CkrtWp3<8VYPh96SfDMG4rf7acx;ZjYy( zrm9u7-?|o@M+44r|=c7f`7eUomMuR+7BpX7eeaRw;m3cY=iLybz=9h1T&U*%?lpfUS#o@*R&fzvI|TRQsa1pg%S>WY+tpn() z8TE}b19_|d!_y;3$jls#L9m4oW}!jW#0Tz zJXS)So$L&ZfYtdiea@40uVLHwvKSxsW1_>hwDyo@w)JOX;AK%Xn55Rr=QA`A0>PIM z{^qxuOrgSmg>+6$;%UnT1y;+TqAOJ#v2W(i9H|uxhyh)<2R3T|H0{z<7cOhO~+H&Y}Nb?&b{T zaS&fGE_3x9%x@R|kewbN=Y662>H zSDU)ZK83z`W?z_($#$S*K#E5-e6Hm9;z_VT6m*$#2|3L^l@BBkGlXKyZcG92ud#dU gb6zg*3<#SII|m=>Px# literal 0 HcmV?d00001 diff --git a/src/myftp/client.py b/src/myftp/client.py index a29d6e6..f47964c 100644 --- a/src/myftp/client.py +++ b/src/myftp/client.py @@ -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__": diff --git a/src/myftp/server.py b/src/myftp/server.py index f1cf3bb..9110ae0 100644 --- a/src/myftp/server.py +++ b/src/myftp/server.py @@ -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__":