diff --git a/src/myftp/client.py b/src/myftp/client.py index d26cb52..9852914 100644 --- a/src/myftp/client.py +++ b/src/myftp/client.py @@ -19,11 +19,20 @@ 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 = "000" -get_request_opcode = "001" -change_request_opcode = "010" -summary_request_opcode = "011" -help_requrest_opcode = "100" +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" + +# 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" # custome type to represent the hostname(server name) and the server port Address = Tuple[str, int] @@ -64,7 +73,10 @@ class UDPClient: # help elif command == "help": - continue + request_payload: str = help_requrest_opcode + "00000" + print( + f"myftp> - {self.mode} - : asking for help from the server" + ) if self.debug else None # get command handling elif get_command_pattern.match(command): @@ -87,7 +99,7 @@ class UDPClient: f"myftp> - {self.mode} - : summary file {filename} from the server" ) if self.debug else None - # summary command handling + # change command handling elif change_command_pattern.match(command): _, old_filename, new_filename = command.split() print( @@ -100,9 +112,10 @@ class UDPClient: ) continue - client_socket.send(command.encode()) - modified_message = client_socket.recv(2048) + client_socket.send(request_payload.encode("utf-8")) + modified_message = client_socket.recv(2048)[1:] print(modified_message.decode()) + client_socket.close() # type: ignore except ConnectionRefusedError: print( @@ -112,8 +125,6 @@ class UDPClient: print( f"myftp> - {self.mode} - {error} happened." ) - finally: - client_socket.close() # type: ignore # ping pong UDP def check_udp_server(self): @@ -170,9 +181,7 @@ def get_address_input() -> Address: # Ensure there are exactly two parts if len(input_parts) != 2: - raise ValueError( - "myftp>Invalid input. Please enter a servername/hostname/ip address as a string and the port number as an integer separated by a space." - ) + raise ValueError # Extract the values and create the tuple string_part, int_part = input_parts @@ -183,7 +192,7 @@ def get_address_input() -> Address: except ValueError as e: print( - f"Error: {e}. Invalid input. Please enter a servername/hostname/ip address as a string and the port number as an integer separated by a space." + 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." ) diff --git a/src/myftp/server.py b/src/myftp/server.py index 543f59f..e5e2b68 100644 --- a/src/myftp/server.py +++ b/src/myftp/server.py @@ -17,6 +17,13 @@ unknown_request_rescode: str = "100" unsuccessful_change_rescode: str = "101" help_rescode: str = "110" +# 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" + class UDPServer: def __init__( @@ -41,33 +48,45 @@ class UDPServer: try: while not shut_down: message, clientAddress = self.server_socket.recvfrom(2048) - message_in_utf8 = message.decode() + request_payload = message.decode() print( - f"myftp> - {self.mode} - received message from client at {clientAddress}: {message_in_utf8}" + f"myftp> - {self.mode} - received message from client at {clientAddress}: {request_payload}" ) if self.debug else None # check for connectivity - if message_in_utf8 == "ping": - response_message = "pong" + if request_payload == "ping": + self.server_socket.sendto("pong".encode(), clientAddress) + continue # list files available on server - elif message_in_utf8 == "list": + 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": + print( + f"myftp> - {self.mode} - received help request" + ) if self.debug else None + rescode = help_rescode + response_data_string = "get,put,summary,change,help,bye" + else: - response_message = message_in_utf8.upper() + # handle unrecognized request here + pass + + payload: bytes = self.build_res_payload(rescode, response_data_string) + + self.server_socket.sendto(payload, clientAddress) print( - f"myftp> - {self.mode} - sent message to client at {clientAddress}: {response_message}" + f"myftp> - {self.mode} - sent message to client at {clientAddress}: {payload}" ) if self.debug else None - self.server_socket.sendto(response_message.encode(), clientAddress) - except KeyboardInterrupt: shut_down = True self.server_socket.close() @@ -76,6 +95,40 @@ class UDPServer: finally: print(f"myftp> - {self.mode} - Closed the server socket\n") + # assembling the payload to send back to the client + def build_res_payload(self, + rescode: str, + response_data_string: str) -> bytes: + + bytes_response_data = response_data_string.encode("utf-8") + + data_len = len(bytes_response_data) + + 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 + + # 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) + + print(f"myftp> - {self.mode} - binary_data_len {binary_data_len[2:]}") if self.debug 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.mode} - First byte assembled for rescode {rescode}: {bin(int.from_bytes(first_byte, byteorder='big'))[2:]}") if self.debug else None + + except Exception as e: + raise Exception(e) + + res_payload = first_byte + bytes_response_data + + return res_payload + def get_files_in_directory(directory_path: str) -> list[str]: file_list = []