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:
parent
5519be9222
commit
dcd8c1a1a5
@ -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
|
||||
|
@ -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
|
||||
|
@ -12,7 +12,7 @@ You can run `python3 src/myftp/client.py --directory <insert valid directory tha
|
||||
|
||||
### Server
|
||||
|
||||
By default, the server IP address or hostname or server name will be `0.0.0.0` (meaning it will bind to all interfaces). The `--port_number` flag, if not specified will be by default `12000`.
|
||||
By default, the server IP address or hostname or server name will be `0.0.0.0` or `localhost` (meaning it will bind to all interfaces). The `--port_number` flag, if not specified will be by default `12000`.
|
||||
|
||||
You can run `python3 src/myftp/server.py --directory <insert valid directory that you have read/write permissions>` to start the server or `python3 src/myftp/server.py --ip_addr <insert ip addr of the server> --port_number <insert port number here> --debug 1 --directory <insert valid directory that you have read/write permissions>` for debugging purposes and to specify the port number.
|
||||
|
||||
|
BIN
client_directory/file_local.txt
Normal file
BIN
client_directory/file_local.txt
Normal file
Binary file not shown.
BIN
client_directory/image_local.png
Normal file
BIN
client_directory/image_local.png
Normal file
Binary file not shown.
BIN
server_directory/file_server.txt
Normal file
BIN
server_directory/file_server.txt
Normal file
Binary file not shown.
BIN
server_directory/image_server.txt
Normal file
BIN
server_directory/image_server.txt
Normal file
Binary file not shown.
@ -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__":
|
||||
|
Loading…
x
Reference in New Issue
Block a user