diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c65112f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.pyc +__pycache__/ +output/* +!output/.gitkeep \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c1914fc --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# IF3130 - Jaringan Komputer 2023 +> Tugas Besar - IF3130 Jaringan Komputer 2023 + +## About +This is a simple file transfer program using UDP protocol. The program is written in Python 3.10 and tested on Windows 11 and MacOS. + +## Contributors +Kelompok 1 K03 - GunungE + +| NIM | Nama | +| --- | --- | +| 13521004 | Henry Anand Septian Radityo (Manusia Rasis) | +| 13521007 | Matthew Mahendra (Si Bucin ke Bandung Mulu) | +| 13521015 | Hidayatullah Wildan Ghaly (Manusia Penggali) | +| 13521024 | Ahmad Nadil (yg paling normal) | + +![Kelompok 1 K03](input/penampakan.jpg) + +## Features +### Main Features +- Reliable data transfer using UDP protocol and sliding window protocol +- Handle data transfer in bad network condition (Tested using Clumsy) + +### Bonus Features +- Streaming process using Seek() +- Metadata transfer (file name and extension) +- Peer to Peer file transfer +- Parallel processing for handshake and data transfer in server +- Error Correcting Codes (ECC) using Hamming Code +- End to End device communication +- TicTacToe game using UDP protocol + +## How to Run +1. Run the server +```bash +python server.py -i -p -f +``` + +2. Run the client +```bash +python client.py -ci -cp -i -p -f +``` + +*There is default value for each argument, so you can run the program without any argument* + +Alternatively, you can run the main program to switch between server and client mode +```bash +python main.py +``` + +## Folder Structure +```bash +┣ 📂.github +┃ ┗ 📜.keep +┣ 📂lib +┃ ┣ 📜__init__.py +┃ ┣ 📜Connection.py +┃ ┣ 📜Constant.py +┃ ┣ 📜Hamming.py +┃ ┣ 📜Logger.py +┃ ┣ 📜Node.py +┃ ┣ 📜Segment.py +┃ ┣ 📜SegmentFlag.py +┃ ┗ 📜Utils.py +┣ 📂output +┃ ┗ 📜.gitkeep +┣ 📜.gitignore +┣ 📜client.py +┣ 📜main.py +┣ 📜README.md +┗ 📜server.py +``` + +## References +- [Hamming Code](https://www.geeksforgeeks.org/hamming-code-implementation-in-python/) +- [CRC](https://www.geeksforgeeks.org/cyclic-redundancy-check-python/) +- [UDP Socket](https://www.geeksforgeeks.org/udp-server-client-implementation-c/) diff --git a/client.py b/client.py new file mode 100644 index 0000000..ae17a07 --- /dev/null +++ b/client.py @@ -0,0 +1,216 @@ +import argparse +import json +import socket + +from lib import * +from lib.GameState import GameState + + +class Client(Node): + def __init__(self, connection: Connection, server_ip: str, server_port: str, folder_path: str = None): + self.connection = connection + self.server_ip = server_ip + self.server_port = server_port + self.folder_path = folder_path + self.file_path = None + self.file = [] + self.log = Logger("Client") + self.buffer_size = 1024 + self.segment = Segment() + self.gameState = None + self.player_number = None + + def run(self): + if (self.three_way_handshake()): + self.listen_file() + + def run_game(self): + self.three_way_handshake() + + while True : + # listen for client number + try: + segment_data, _ = self.connection.listen(1) + if(segment_data.is_syn_flag and segment_data.get_header()['seqNumber'] == 0): + #inisialisasi + self.log.success_log(f"Received segment {segment_data.get_header()['seqNumber']} from {self.server_ip}:{self.server_port} with data {segment_data.get_data().decode()}") + self.segment = Segment() + self.segment.set_seq_number(0) + self.segment.set_flag([False, True, False]) + self.connection.send(self.server_ip, self.server_port, self.segment) + self.player_number = segment_data.get_data().decode() + elif (segment_data.is_syn_ack_flag and segment_data.get_header()['seqNumber'] == 1): + # init game state + self.gameState = GameState(self.player_number, json.loads(segment_data.get_data().decode())) + if (self.gameState.clientNumber == '2'): + self.gameState.printBoard() + self.segment = Segment() + self.segment.set_seq_number(1) + self.segment.set_flag([False, True, False]) + # print disini + self.connection.send(self.server_ip, self.server_port, self.segment) + elif (segment_data.is_syn_ack_flag and segment_data.get_header()['seqNumber'] == 2): + if(self.gameState.clientNumber == self.player_number): + self.gameState.board = json.loads(segment_data.get_data().decode()) + self.gameState.printBoard() + + move = self.gameState.input_mark() + self.segment = Segment() + self.segment.set_data(json.dumps(move).encode()) + self.segment.set_flag([True, True, False]) + self.connection.send(self.server_ip, self.server_port, self.segment) + elif(segment_data.get_header()['seqNumber'] == 3): + self.gameState.board = json.loads(segment_data.get_data().decode()) + self.log.success_log("[!] Board Updated") + self.gameState.printBoard() + self.log.alert_log("[!] Waiting for opponent...") + self.segment = Segment() + self.segment.set_flag([False, True, False]) + self.connection.send(self.server_ip, self.server_port, self.segment) + + elif(segment_data.is_fin_flag and segment_data.get_header()['seqNumber'] == 4): + print(segment_data.get_data().decode()) + break + except socket.timeout: + self.connection.send(self.server_ip, self.server_port, self.segment) + except Exception as e: + self.log.warning_log(f'[!] Unknown error occured, exiting...') + exit() + + def three_way_handshake(self): + # Send initial connection + self.log.alert_log(f"[!] Starting 3-way handshake with {self.server_ip}:{self.server_port}") + # Initial Connection + self.segment.update_checksum() + # print("Nilai checksum: ", self.segment.get_header()['checksum']) + self.connection.send(self.server_ip, self.server_port, self.segment) + + while True: + try: + msg, _ = self.connection.listen(TIMEOUT_LISTEN) + self.segment = msg + # Send SYN-ACK + if self.segment.is_syn_flag(): + self.segment = Segment() + self.segment.set_flag([True, True, False]) + self.log.alert_log(f"[!] Sending SYN-ACK to {self.server_ip}:{self.server_port}") + self.connection.send(ip_remote=self.server_ip, port_remote=self.server_port, message=self.segment) + # Resend SYN-ACK + elif self.segment.is_syn_ack_flag(): + self.log.alert_log(f'[!] Resending SYN-ACK to {self.server_ip}:{self.server_port}') + self.connection.send(ip_remote=self.server_ip, port_remote=self.server_port, message=self.segment) + # Complete + elif self.segment.is_ack_flag(): + self.log.alert_log(f"[!] ACK received from {self.server_ip}:{self.server_port}") + break + # Received a segment with no flag (file) + else: + self.log.warning_log(f"[!] Received a segment with no flag (file), resetting connection...") + # Send SYN-ACK + self.segment = Segment() + self.segment.set_flag([True, True, False]) + self.log.alert_log(f"[!] Sending SYN-ACK to {self.server_ip}:{self.server_port}") + self.connection.send(ip_remote=self.server_ip, port_remote=self.server_port, message=self.segment) + except socket.timeout: + if self.segment.is_syn_ack_flag(): + self.log.warning_log("[!] [TIMEOUT] ACK Response Timed out, retrying...") + # self.connection.send(ip_remote=self.server_ip, port_remote=self.server_port, message=self.segment) + else: + self.log.warning_log("[!] [TIMEOUT] SYN Response Timed out") + return 0 + + except Exception as e: + self.log.warning_log(f"[!] Error: {e}") + return 0 + return 1 + + def listen_file(self): + N = WINDOW_SIZE + Rn = 0 + Sn = 0 + METADATA_SEQ = -1 + hamming = Hamming() + + # Terima file, pengulangan hingga file selesai + while True: + try: + file_segment, _ = self.connection.listen() + Sn = file_segment.get_header()['seqNumber'] + self.log.success_log(f"Received segment {Sn} from {self.server_ip}:{self.server_port}") + flag = file_segment.get_flag() + + if (Sn == METADATA_SEQ): + self.file_path = (Utils.decode_metadata(file_segment.get_data())).decode('UTF-8') + self.log.success_log(f"[!] Receiving file {self.file_path}") + ack = Segment() + ack.set_flag([False, True, False]) + ack.set_ack_number(Sn) + self.log.alert_log(f"[!] Sending metadata ACK to {self.server_ip}:{self.server_port}") + self.connection.send(self.server_ip, self.server_port, ack) + METADATA_SEQ -= 1 + continue + + # Jika FIN, maka kirim FIN ACK + elif flag.fin: + self.log.success_log(f"[!] FIN received from {self.server_ip}:{self.server_port}") + # ack ke server + ack = Segment() + ack.set_flag([False, True, True]) + self.connection.send(ip_remote=self.server_ip, port_remote=self.server_port, message=ack) + self.log.alert_log(f"[!] Sending ACK FIN to {self.server_ip}:{self.server_port}") + filePath = self.folder_path + '/' + self.file_path + md5 = Utils.printmd5(filePath) + self.log.success_log(f"MD5 Hash: {md5}") + break + + elif Sn == Rn: + data = file_segment.get_data() + if data: + decodedData = hamming.breakdownBytes(data) + file = open(self.folder_path + '/' + + self.file_path, 'ab') + file.seek(Sn) + file.write(decodedData) + file.close() + Rn += 1 + + # Kirim ACK + ack = Segment() + ack.set_flag([False, True, False]) + ack.set_ack_number(Sn) + + self.connection.send(ip_remote=self.server_ip, port_remote=self.server_port, message=ack) + self.log.alert_log(f"[!] Sending ACK {Sn} to {self.server_ip}:{self.server_port}") + else: + self.log.warning_log(f'[!] Segment {Sn} is refused, expected {Rn}, timing out...') + self.connection.send(ip_remote=self.server_ip, port_remote=self.server_port, message=ack) + + except socket.timeout: + self.log.warning_log("[!] [TIMEOUT] Response Timed out, retrying...") + + except Exception as e: + self.log.warning_log("aaaaaaaaaa", e) + # break + + +def load_args(): + arg = argparse.ArgumentParser() + arg.add_argument('-ci', '--clientip', type=str, default='localhost', help='ip the client is on') + arg.add_argument('-cp', '--clientport', type=int, default=7331, help='port the client is on') + arg.add_argument('-i', '--ip', type=str, default='localhost', help='ip to listen on') + arg.add_argument('-p', '--port', type=int, default=1337, help='port to listen on') + arg.add_argument('-f', '--folder', type=str, default='output', help='path to folder output') + arg.add_argument('-g', '--game', type=str, default='0', help='turn on or off game') + args = arg.parse_args() + return args + + +if __name__ == "__main__": + args = load_args() + print(args.clientip, args.clientport) + if args.game == '0': + klien = Client(Connection(ip = args.clientip, port=args.clientport), server_ip=args.ip, server_port=args.port, folder_path=args.folder) + klien.run() + else : + klien = Client(Connection(ip = args.clientip, port=args.clientport), server_ip=args.ip, server_port=args.port) + klien.run_game() diff --git a/input/Home.mp3 b/input/Home.mp3 new file mode 100644 index 0000000..151fe6e Binary files /dev/null and b/input/Home.mp3 differ diff --git a/input/diretide2020_treasure_idle.webm b/input/diretide2020_treasure_idle.webm new file mode 100644 index 0000000..07ee65a Binary files /dev/null and b/input/diretide2020_treasure_idle.webm differ diff --git a/input/flower.jpeg b/input/flower.jpeg new file mode 100644 index 0000000..297fa98 Binary files /dev/null and b/input/flower.jpeg differ diff --git a/input/ha.txt b/input/ha.txt new file mode 100644 index 0000000..9f43362 --- /dev/null +++ b/input/ha.txt @@ -0,0 +1,51 @@ +Warna cerah yang terlihat +Diriku mahir sembunyi rasa +Sebenarnya hitam pekat +Kututup rapat, jadi rahasia +Satu dua kali, 'ku tak apa-apa +Kamu pikir hidupku baik-baik saja? +Siapa yang peduli kupunya air mata? +Kamu hanya pinta diri berikan tawa +Ha-ha, ha-ha +Ha-ha, ha-ha, ha-ha +Wajah riangku perisai jitu +Ha-ha, ha-ha +Ha-ha, ha-ha, ha-ha +Tapi sampai kapan kubegitu? +Malam hari berwahana +Pikiran liarku juaranya +Bukan 'ku tak berusaha +Ingin kulepas sekuat tenaga +Satu dua kali, 'ku tak apa-apa +Kamu pikir hidupku baik-baik saja? +Siapa yang peduli kupunya air mata? +Kamu hanya pinta diri berikan tawa, ho-oh +Ha-ha, ha-ha +Ha-ha, ha-ha, ha-ha +Wajah riangku perisai jitu +Ha-ha, ha-ha +Ha-ha, ha-ha, ha-ha +Tapi sampai kapan kubegitu? +Aku bulan yang terangi +Malam-malammu yang sepi +Lalu siapa yang temani +Rasa gelisah kupikul sendiri +Ha-ho +(Ha-ha, ha-ha) +Ha-hm, yeah +(Ha-ha, ha-ha) +Ha-oh +(Ha-ha, ha-ha) +Hu-hu-hu +Ha-ha, ha-ha +Ha-ha, ha-ha, ha-ha +Wajah riangku perisai jitu +Ha-ha, ha-ha +Ha-ha, ha-ha, ha-ha +Tapi sampai kapan kubegitu? +Ha-ha, ha-ha +Ha-ha, ha-ha, ha-ha +Wajah riangku perisai jitu (wajah riangku) +Ha-ha, ha-ha +Ha-ha, ha-ha, ha-ha (hu-uu) +Tapi sampai kapan kubegitu? \ No newline at end of file diff --git a/input/pebble.png b/input/pebble.png new file mode 100644 index 0000000..6feb45a Binary files /dev/null and b/input/pebble.png differ diff --git a/input/penampakan.jpg b/input/penampakan.jpg new file mode 100644 index 0000000..633fd1a Binary files /dev/null and b/input/penampakan.jpg differ diff --git a/input/test.txt b/input/test.txt new file mode 100644 index 0000000..9dcee1a --- /dev/null +++ b/input/test.txt @@ -0,0 +1,100 @@ +00000aaaa +11111bbbb +22222cccc +33333dddd +44444eeee +55555ffff +66666gggg +77777hhhh +88888iiii +99999jjjj +00000kkkk +11111llll +22222mmmm +33333nnnn +44444oooo +55555pppp +66666qqqq +77777rrrr +88888ssss +99999tttt +00000uuuu +11111vvvv +22222wwww +33333xxxx +44444yyyy +55555zzzz +66666aaaa +77777bbbb +88888cccc +99999dddd +00000eeee +11111ffff +22222gggg +33333hhhh +44444iiii +55555jjjj +66666kkkk +77777llll +88888mmmm +99999nnnn +00000oooo +11111pppp +22222qqqq +33333rrrr +44444ssss +55555tttt +66666uuuu +77777vvvv +88888wwww +99999xxxx +00000aaaa +11111bbbb +22222cccc +33333dddd +44444eeee +55555ffff +66666gggg +77777hhhh +88888iiii +99999jjjj +00000kkkk +11111llll +22222mmmm +33333nnnn +44444oooo +55555pppp +66666qqqq +77777rrrr +88888ssss +99999tttt +00000uuuu +11111vvvv +22222wwww +33333xxxx +44444yyyy +55555zzzz +66666aaaa +77777bbbb +88888cccc +99999dddd +00000eeee +11111ffff +22222gggg +33333hhhh +44444iiii +55555jjjj +66666kkkk +77777llll +88888mmmm +99999nnnn +00000oooo +11111pppp +22222qqqq +33333rrrr +44444ssss +55555tttt +66666uuuu +77777vvvv +88888wwww +987654321 \ No newline at end of file diff --git a/lib/Connection.py b/lib/Connection.py new file mode 100644 index 0000000..3303257 --- /dev/null +++ b/lib/Connection.py @@ -0,0 +1,36 @@ +import socket +from .Logger import Logger +from .Segment import Segment +from .Constant import * + +class Connection: + def __init__(self, ip: str = 'localhost', port: int = 6969): + self.ip = ip + self.port = port + self.__socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.__socket.bind((self.ip, self.port)) + self.log = Logger("Connection") + + def send(self, ip_remote: str, port_remote: int, message: Segment): + self.__socket.sendto(message.get_bytes(), (ip_remote, port_remote)) + + def listen(self, timeout = TIMEOUT_TIME): + self.__socket.settimeout(timeout) + data, addr = self.__socket.recvfrom(32768) + message = Segment() + message.set_from_bytes(data) + # check checksum + if not message.valid_checksum(): + self.log.warning_log("Checksum not valid") + return message, addr + + def close(self): + self.__socket.close() + + def setTimeout(self, timeout: int): + self.__socket.settimeout(timeout) + + +if __name__ == "__main__": + conn = Connection() + conn.listen() diff --git a/lib/Constant.py b/lib/Constant.py new file mode 100644 index 0000000..158b670 --- /dev/null +++ b/lib/Constant.py @@ -0,0 +1,9 @@ +SYN_FLAG = 0x02 +ACK_FLAG = 0x08 +FIN_FLAG = 0x01 +DEFAULT_FLAG = 0x00 +PAYLOAD_SIZE = 32756 +WINDOW_SIZE = 4 +TIMEOUT_TIME = 1 +TIMEOUT_LISTEN = 30 +MAX_BUFFER = 10 \ No newline at end of file diff --git a/lib/GameManager.py b/lib/GameManager.py new file mode 100644 index 0000000..58be8b2 --- /dev/null +++ b/lib/GameManager.py @@ -0,0 +1,60 @@ +class GameManager: + def __init__(self, p1, p2): + self.gamestate = [["" for i in range(3)] for j in range(3)] + self.playerInfo = [[p1[0],p1[1]],[p2[0],p2[1]]] + self.turn = 1 + self.pemain = 1 + self.menang = False + self.winner = 0 # 1 = p1, 2 = p2, 3 = draw + + def newTurn(self): + if self.pemain == 1: + self.pemain = 2 + else: + self.pemain = 1 + self.turn += 1 + + def setState(self, move: tuple): + row = move[0] + col = move[1] + if self.pemain == 1: + self.gamestate[row-1][col-1] = "X" + else: + self.gamestate[row-1][col-1] = "O" + + def validate(self): + if(not self.menang and self.turn == 10): + self.winner = 3 + self.menang = True + else: + #cek jawaban untuk pemain 1 + for i in range(3): #cek untuk setiap baris + if (self.gamestate[i][0] == "X" and self.gamestate[i][1] == "X" and self.gamestate[i][2] == "X"): + self.turn = 10 + self.menang = True + self.winner = 1 + for i in range(3): #cek untuk setiap kolom + if (self.gamestate[0][i] == "X" and self.gamestate[1][i]=="X" and self.gamestate[2][i]=="X"): + self.turn = 10 + self.menang = True + self.winner = 1 + if (self.gamestate[0][0] == "X" and self.gamestate[1][1] == "X" and self.gamestate[2][2] == "X") or (self.gamestate[2][0] == "X" and self.gamestate[1][1] == "X" and self.gamestate[0][2] == "X"): #cek untuk menyilang + self.turn = 10 + self.menang = True + self.winner = 1 + + #cek jawaban untuk pemain 2 + for i in range(3):#cek untuk setiap baris + if (self.gamestate[i][0] == "O" and self.gamestate[i][1] == "O" and self.gamestate[i][2] == "O"): + self.turn = 10 + self.menang = True + self.winner = 2 + for i in range(3):#cek untuk setiap kolom + if (self.gamestate[0][i] == "O" and self.gamestate[1][i]=="O" and self.gamestate[2][i]=="O"): + self.turn = 10 + self.menang = True + self.winner = 2 + if (self.gamestate[0][0] == "O" and self.gamestate[1][1] == "O" and self.gamestate[2][2] == "O") or (self.gamestate[2][0] == "O" and self.gamestate[1][1] == "O" and self.gamestate[0][2] == "O"): #cek untuk menyilang + self.turn = 10 + self.menang = True + self.winner = 2 \ No newline at end of file diff --git a/lib/GameState.py b/lib/GameState.py new file mode 100644 index 0000000..37eeec7 --- /dev/null +++ b/lib/GameState.py @@ -0,0 +1,37 @@ +class GameState: + def __init__(self, clientNumber,board): + self.clientNumber = clientNumber + self.board = board + + def printBoard(self): + row = self.board + # fill the empty board with 3 blank spaces + row = [[" " + cell + " " if cell != "" else " " for cell in row[i]] for i in range(3)] + + print(row[0][0] + '|' + row[0][1] + '|' + row[0][2]) + print('---+---+---') + print(row[1][0] + '|' + row[1][1] + '|' + row[1][2]) + print('---+---+---') + print(row[2][0] + '|' + row[2][1] + '|' + row[2][2]) + + def input_mark(self): + row = int(input("Masukkan baris Anda ")) + col = int(input("Masukkan kolom Anda ")) + + state = False + while state == False: + if str(row) == "" or str(col) =="": + print("Input baris dan kolom tidak boleh kosong") + row = int(input("Masukkan baris Anda ")) + col = int(input("Masukkan kolom Anda ")) + if (row > 3 or row <=0) or (col <=0 or col > 3): + print("Input baris dan kolom salah! Baris dan kolom harus dalam rentang 1...3") + row = int(input("Masukkan baris Anda ")) + col = int(input("Masukkan kolom Anda ")) + elif self.board[row-1][col-1] == "X" or self.board[row-1][col-1]=="O": + print("Tempat itu sudah diisi") + row = int(input("Masukkan baris Anda ")) + col = int(input("Masukkan kolom Anda ")) + else: + state = True + return row, col \ No newline at end of file diff --git a/lib/Hamming.py b/lib/Hamming.py new file mode 100644 index 0000000..747b563 --- /dev/null +++ b/lib/Hamming.py @@ -0,0 +1,138 @@ +import struct +from .Logger import * +from concurrent.futures import ThreadPoolExecutor + +class Hamming: + + def __init__(self) -> None: + self.log = Logger("Hamming") + + def calcRedundantBits(self, m): + for i in range(m): + if(2**i >= m + i + 1): + return i + + def posRedundantBits(self, data, r): + # asumsi r = 3 + return ((data & 0b1110) << 3) | ((data & 0b0001) << 2) + + def calcParityBits(self, data, r): + p1 = ((data >> 2) & 1) ^ ((data >> 4) & 1) ^ ((data >> 6) & 1) + p2 = ((data >> 1) & 2) ^ ((data >> 4) & 2) ^ ((data >> 5) & 2) + p3 = ((data >> 1) & 8) ^ ((data >> 2) & 8) ^ ((data >> 3) & 8) + p4 = 128 + return data | p1 | p2 | p3 | p4 + + def removeParityBits(self, binary_number, bit_length): + """Remove bits 0, 1, and 3 from a binary number.""" + + binary_number = binary_number & 0b01111111 + + result = 0 + shift = 0 + + for i in range(bit_length): + if i in [0, 1, 3]: + continue + current_bit = (binary_number >> i) & 1 + result |= current_bit << shift + shift += 1 + return result + + def detectError(self, binErr, nr): + n = binErr.bit_length() + res = 0 + for i in range(nr): + val = 0 + for j in range(1, n + 1): + if j & (1 << i): + val ^= (binErr >> (j-1)) & 1 + res += val * (1 << i) + return res + + def int_to_4bit_chunks(self, num): + binary = bin(num)[2:] + if len(binary) % 4: + binary = binary.zfill(len(binary) + 4 - (len(binary) % 4)) + return [int(binary[i:i+4], 2) for i in range(0, len(binary), 4)] + + def breakdownData(self, data): + if isinstance(data, int): + return self.int_to_4bit_chunks(data) + elif isinstance(data, str): + return [chunk for char in data for chunk in self.int_to_4bit_chunks(ord(char))] + elif isinstance(data, bytes): + return [ + self.calcParityBits(self.posRedundantBits(chunk, 3), 3) + # chunk + for b in data for chunk in self.int_to_4bit_chunks(b) + ] + else: + raise TypeError("Unsupported data type") + + def breakdownDataToBytes(self, data): + if not isinstance(data, bytes): + raise TypeError("Unsupported data type") + + result = b'' + for byte in data: + high_nibble = byte >> 4 + low_nibble = byte & 0x0F + + encoded_high = self.hammingEncode(high_nibble) + encoded_low = self.hammingEncode(low_nibble) + + result += struct.pack('B', encoded_high) + result += struct.pack('B', encoded_low) + + return result + + + def hammingEncode(self, data, r:int = 3): + return self.calcParityBits(self.posRedundantBits(data, r), r) + + def hammingRecovery(self, data, r:int = 3): + correction = self.detectError(data, r) + finalResult = data + if correction != 0: + bit_position_to_flip = correction - 1 + finalResult = data ^ (1 << bit_position_to_flip) + self.log.warning_log(f"Error detected at bit position {bit_position_to_flip}") + + res = self.removeParityBits(finalResult, 7) + return res + + def hammingDecode(self, data, r:int = 3): + return self.hammingRecovery(data, r) + + def breakdownBytes(self, data): + final = b'' + if len(data) % 2 != 0: + raise ValueError("Panjang data harus genap untuk dekode Hamming 4-bit.") + + for i in range(0, len(data), 2): + byte1, byte2 = data[i], data[i + 1] + bin_value1 = format(byte1, '08b') + bin_value2 = format(byte2, '08b') + decoded_value1 = self.hammingDecode(int(bin_value1, 2)) + decoded_value2 = self.hammingDecode(int(bin_value2, 2)) + combined_byte = (decoded_value1 << 4) | decoded_value2 + final += struct.pack('B', combined_byte) + + return final + + +if __name__ == "__main__": + with open("test.txt", 'rb') as file: + file_content = file.read() + + hamming = Hamming() + + # example encode + splitData = hamming.breakdownDataToBytes(file_content) + print(splitData) + + # example decode + finals = hamming.breakdownBytes(splitData) + print(finals) + \ No newline at end of file diff --git a/lib/Logger.py b/lib/Logger.py new file mode 100644 index 0000000..fced925 --- /dev/null +++ b/lib/Logger.py @@ -0,0 +1,17 @@ +class Logger: + WARNING = "\033[91m" + ALERT = "\033[93m" + SUCCESS = "\033[92m" + DEFAULT = "\033[0m" + + def __init__(self, name): + self.name = name + + def success_log(self, msg): + print(f"{self.SUCCESS}[{self.name}]{self.DEFAULT} {msg}") + + def alert_log(self, msg): + print(f"{self.ALERT}[{self.name}]{self.DEFAULT} {msg}") + + def warning_log(self, msg): + print(f"{self.WARNING}[{self.name}]{self.DEFAULT} {msg}") diff --git a/lib/Node.py b/lib/Node.py new file mode 100644 index 0000000..26a34db --- /dev/null +++ b/lib/Node.py @@ -0,0 +1,7 @@ +from .Segment import Segment +from abc import ABC, abstractmethod + +class Node(ABC): + @abstractmethod + def run(): + pass \ No newline at end of file diff --git a/lib/Segment.py b/lib/Segment.py new file mode 100644 index 0000000..171f055 --- /dev/null +++ b/lib/Segment.py @@ -0,0 +1,158 @@ +import struct +from .Constant import * +from .SegmentFlag import * + +class Segment: + # -- Internal Function -- + def __init__(self): + # Initalize segment + self.header = {} + self.header['seqNumber'] = 0 + self.header['ackNumber'] = 0 + self.header['flag'] = DEFAULT_FLAG + # self.header['emptyPadding'] = 0 + self.header['checksum'] = 0 + + self.data = b"" # apa ini kak? payload tapi isinya apa? + + def __str__(self): + # Optional, override this method for easier print(segmentA) + output = "" + output += f"{'Sequence number':24} | {self.header['seqNumber']}\n" + output += f"{'Acknowledgement number':24} | {self.header['ackNumber']}\n" + output += f"{'Checksum':24} | {self.header['checksum']}\n" + output += f"{'Flag':24} | {self.header['flag']}\n" + output += f"{'Payload':24} | {self.data}\n" + return output + + # -- Setter -- + def set_header(self, header : dict): + # Set header from dictionary + self.header = header + self.update_checksum() + + def set_data(self, data : bytes): + # Set payload from bytes + self.data = data + self.update_checksum() + + def set_seq_number(self, seq_number : int): + # Set sequence number + self.header['seqNumber'] = seq_number + self.update_checksum() + + def set_ack_number(self, ack_number : int): + # Set acknowledgement number + self.header['ackNumber'] = ack_number + self.update_checksum() + + def set_flag(self, flag_list : list): + # Set flag from list of flag (SYN, ACK, FIN) + initial_flag = DEFAULT_FLAG + if flag_list[0]: + initial_flag |= SYN_FLAG + if flag_list[1]: + initial_flag |= ACK_FLAG + if flag_list[2]: + initial_flag |= FIN_FLAG + self.header['flag'] = initial_flag + self.update_checksum() + + + # -- Getter -- + def get_flag(self) -> SegmentFlag: + # return flag in segmentflag + return SegmentFlag(self.header['flag']) + + def get_header(self) -> dict: + # Return header in dictionary form + return self.header + + def get_data(self) -> bytes: + # Return payload in bytes + return self.data + + # -- Marshalling -- + def set_from_bytes(self, src : bytes): + # From pure bytes, unpack() and set into python variable + # 44112: iibbh + header_bytes = src[:12] + header_tup = struct.unpack('iibbh', header_bytes) + header = { + 'seqNumber': header_tup[0], + 'ackNumber': header_tup[1], + 'flag': header_tup[2], + 'checksum': header_tup[4] & 0xffff + } + self.header = header + self.data = src[12:] + # self.update_checksum() + + def get_bytes(self) -> bytes: + # Convert this object to pure bytes + header_bytes = struct.pack('iibbH', self.header['seqNumber'], self.header['ackNumber'], self.header['flag'], DEFAULT_FLAG, self.header['checksum']) + return header_bytes + self.data + + def get_bytes_no_checksum(self) -> bytes: + # Get bytes without checksum + header_bytes = struct.pack('iibb', self.header['seqNumber'], self.header['ackNumber'], self.header['flag'], DEFAULT_FLAG) + return header_bytes + self.data + + # -- Checksum -- + def valid_checksum(self) -> bool: + # Use __calculate_checksum() and check integrity of this object + return self.calculate_checksum() == self.header['checksum'] + + def calculate_checksum(self) -> int: + # Calculate checksum here, return checksum result + sum = 0 + data_bytes = self.get_bytes_no_checksum() + + if (len(data_bytes) % 2 != 0): + data_bytes = b'\x00' + data_bytes + + for i in range(0, len(data_bytes), 2): + sum += int.from_bytes(data_bytes[i:i+2], byteorder='big') + while sum >> 16: + sum = (sum & 0xffff) + (sum >> 16) + return ~sum & 0xffff + + def update_checksum(self): + checksum = self.calculate_checksum() + checksum &= 0xffff + self.header['checksum'] = checksum + + def is_syn_flag(self) -> bool: + # Check if this segment has only SYN flag + return self.header['flag'] == SYN_FLAG + + def is_ack_flag(self) -> bool: + # Check if this segment has only ACK flag + return self.header['flag'] == ACK_FLAG + + def is_fin_flag(self) -> bool: + # Check if this segment has only FIN flag + return self.header['flag'] == FIN_FLAG + + def is_syn_ack_flag(self) -> bool: + # Check if this segment has SYN and ACK flag + return self.header['flag'] == SYN_FLAG | ACK_FLAG + + def is_fin_ack_flag(self) -> bool: + # Check if this segment has ACK and FIN flag + return self.header['flag'] == ACK_FLAG | FIN_FLAG + +if __name__ == '__main__': + segment = Segment() + segment.header['seqNumber'] = 1 + segment.header['ackNumber'] = 2 + segment.header['flag'] = SYN_FLAG + segment.data = b"Hello World" + segment.update_checksum() + print(segment.calculate_checksum()) + if segment.valid_checksum(): + print("Checksum valid") + else: + print("Checksum invalid") + print(segment.get_bytes_no_checksum().hex()) + print(segment.get_bytes().hex()) \ No newline at end of file diff --git a/lib/SegmentFlag.py b/lib/SegmentFlag.py new file mode 100644 index 0000000..5f0e29c --- /dev/null +++ b/lib/SegmentFlag.py @@ -0,0 +1,23 @@ +# Constant Values +from .Constant import * + +class SegmentFlag: + def __init__(self, flag: bytes) -> None: + # Flag init + self.syn = True if flag & SYN_FLAG else False + self.ack = True if flag & ACK_FLAG else False + self.fin = True if flag & FIN_FLAG else False + + def get_flag_bytes(self) -> bytes: + # Return flag bytes + flag = DEFAULT_FLAG + flag |= (SYN_FLAG if self.syn else DEFAULT_FLAG) + flag |= (ACK_FLAG if self.ack else DEFAULT_FLAG) + flag |= (FIN_FLAG if self.fin else DEFAULT_FLAG) + return flag + +if __name__ == "__main__": + seg = SegmentFlag(0x02) + print(seg.get_flag_bytes()) + + \ No newline at end of file diff --git a/lib/Utils.py b/lib/Utils.py new file mode 100644 index 0000000..5e4fbfc --- /dev/null +++ b/lib/Utils.py @@ -0,0 +1,54 @@ +from .Constant import * +from .Hamming import * +import hashlib + +def breakdown_file(data: bytes) -> list: + list_data = [] + hamming = Hamming() + half_payload = PAYLOAD_SIZE // 2 + total_chunks = len(data) // half_payload + processed_chunks = 0 + + def update_progress_bar(): + nonlocal processed_chunks + progress = processed_chunks / total_chunks + bar_length = 40 + bar = '=' * int(bar_length * progress) + '-' * (bar_length - int(bar_length * progress)) + print(f"\rConverting to bytes: [{bar}] {int(progress * 100)}%", end='', flush=True) + + while len(data) > 0: + if len(data) > half_payload: + encoded_data = hamming.breakdownDataToBytes(data[:half_payload]) + list_data.append(encoded_data) + data = data[half_payload:] + else: + encoded_data = hamming.breakdownDataToBytes(data) + list_data.append(encoded_data) + data = b'' + processed_chunks += 1 + update_progress_bar() + + print() # Print a new line after the progress bar is complete + return list_data + +def merge_file(list_data: list) -> bytes: + data = b'' + for i in list_data: + data += i + return data + +def encode_metadata(data: bytes) -> bytes: + hamming = Hamming() + encodedData = hamming.breakdownDataToBytes(data) + return encodedData + +def decode_metadata(data: bytes) -> bytes: + hamming = Hamming() + decodedData = hamming.breakdownBytes(data) + return decodedData + +def printmd5(data: str): + with open(data, 'rb') as file_obj: + file_contents = file_obj.read() + md5_hash = hashlib.md5(file_contents).hexdigest() + return md5_hash \ No newline at end of file diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..6dcd91f --- /dev/null +++ b/lib/__init__.py @@ -0,0 +1,8 @@ +from .Connection import * +from .Constant import * +from .Logger import * +from .Node import * +from .Segment import * +from .SegmentFlag import * +from .Utils import * +from .Hamming import * \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..5356575 --- /dev/null +++ b/main.py @@ -0,0 +1,52 @@ +# make a caller for server or client +import os +from server import Server +from client import Client +from lib import * + + +if __name__ == '__main__': + while True: + print("1. Server") + print("2. Client") + print("3. Exit") + choice = input("Enter your choice: ") + if choice == '1': + input_file = input("Enter file name: ") + ip = input("Enter ip (default = localhost): ") + port = input("Enter port (default = 8080): ") + # paralel = int(input("Enter paralel: ")) + if (not os.path.isfile(input_file)): + print("File not found") + continue + if (input_file == ''): + print("File name cannot be empty") + continue + if (ip == ''): + ip = 'localhost' + if (port == ''): + port = 8080 + server = Server(Connection(ip=ip, port=int(port)),file_path=input_file) + server.run() + elif choice == '2': + client_ip = input("Enter client ip (default = localhost): ") + if (client_ip == ''): + client_ip = 'localhost' + client_port = input("Enter client port (default = 6000): ") + if (client_port == ''): + client_port = 6000 + ip = input("Enter ip server: ") + if (ip == ''): + ip = 'localhost' + port = input("Enter port: ") + if (port == ''): + port = 8080 + folder = input("Enter folder: ") + if (folder == ''): + folder = 'output' + client = Client(Connection(ip=client_ip, port=int(client_port)), server_ip=ip, server_port=int(port), folder_path=folder) + client.run() + elif choice == '3': + break + else: + print("Invalid choice") \ No newline at end of file diff --git a/output/.gitkeep b/output/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/server.py b/server.py new file mode 100644 index 0000000..420cfdd --- /dev/null +++ b/server.py @@ -0,0 +1,356 @@ +import argparse +import socket +import os +import threading +import time +from typing import Dict, List, Tuple +import json + +from lib import * +from lib.GameManager import GameManager + +class Server(Node): + def __init__(self, connection: Connection, file_path: str = None): + self.log = Logger("Server") + self.connection = connection + self.segment = Segment() + self.file_path = file_path + if(file_path != None): + self.file = open(self.file_path, 'rb').read() + self.file_segment = breakdown_file(self.file) + self.file_name, self.file_extension = os.path.splitext(os.path.basename(file_path)) + self.port_clients = [] + self.client_list = [] + self.parallel = False + + # prompt user for parallel mode + while True: + ALERT = "\033[93m[Server]\033[0m Turn on parallel mode? (y/n): " + parallel = input(ALERT) + if parallel.lower() == 'y': + self.parallel = True + break + elif parallel.lower() == 'n': + self.parallel = False + break + else: + self.log.warning_log("[!] Invalid prompt input") + + def run(self): + while True: + self.client_list = [] + self.listen_for_clients() + if not self.parallel: + self.start_connection() + + def run_game(self): + # Listen for clients + while len(self.client_list) < 2: + self.listen_for_clients() + for client in self.client_list: + self.three_way_handshake(ip_client=client[0], port_client=client[1]) + + # Sending initial gamestate + i = 0 + while True: + try: + # Send SYN + client = self.client_list[i] + seg = Segment() + seg.set_seq_number(0) + seg.set_data(str(self.client_list.index(client)+1).encode()) + seg.set_flag([True, False, False]) + self.connection.send(client[0], client[1], seg) + ack, _ = self.connection.listen() + if ack.is_ack_flag(): + i += 1 + if i == 2: + break + except socket.timeout: + self.log.warning_log("[!] [TIMEOUT] Response Timed out, retrying...") + + gm = GameManager(self.client_list[0], self.client_list[1]) + + # Sending first round state + i = 0 + while True: + try: + client = self.client_list[i] + seg = Segment() + seg.set_seq_number(1) + seg.set_data(json.dumps(gm.gamestate).encode()) + seg.set_flag([True, True, False]) + self.connection.send(client[0], client[1], seg) + ack, _ = self.connection.listen() + if (ack.is_ack_flag()): + i += 1 + if i == 2: + break + except socket.timeout: + self.log.warning_log("[!] [TIMEOUT] Response Timed out, retrying...") + + while (not gm.menang): + # Step 1 : Kirim current player turn + seg = Segment() + board_bytes = json.dumps(gm.gamestate).encode() + seg.set_data(board_bytes) + seg.set_seq_number(2) + seg.set_flag([True, True, False]) + self.connection.send(self.client_list[gm.pemain-1][0], self.client_list[gm.pemain-1][1], seg) + # Step 2 : Terima Move + while True: + try: + move, (p_ip, p_port) = self.connection.listen(TIMEOUT_LISTEN) + index = self.client_list.index((p_ip, p_port)) + if (index + 1 == gm.pemain): + if (move.is_ack_flag()): + break + gm.setState(json.loads(move.get_data().decode())) + seg = Segment() + seg.set_seq_number(3) + seg.set_data(json.dumps(gm.gamestate).encode()) + self.connection.send(self.client_list[gm.pemain-1][0], self.client_list[gm.pemain-1][1], seg) + gm.newTurn() + gm.validate() + except socket.timeout: + self.log.warning_log("[!] [TIMEOUT] Response Timed out, retrying...") + self.connection.send(self.client_list[gm.pemain-1][0], self.client_list[gm.pemain-1][1], seg) + + segFin = Segment() + segFin.set_flag([False, False, True]) + segFin.set_seq_number(4) + msg = "" + if(gm.winner == 0 or gm.winner == 3): + msg = "Draw Game" + elif(gm.winner == 1 or gm.winner == 2): + msg = f"Player {gm.winner} wins the game" + + segFin.set_data(msg.encode()) + while True: + for client in self.client_list: + self.connection.send(client[0], client[1], segFin) + + def listen_for_clients(self): + while True: + if (self.parallel): + self.parallel_listen() + else: + try: + # self.log.alert_log(f"[!] Broadcasting {self.file_name}{self.file_extension} to {self.connection.ip}:{self.connection.port}") + _, (client_ip, client_port) = self.connection.listen(TIMEOUT_LISTEN) + if (client_ip, client_port) not in self.client_list: + self.client_list.append((client_ip, client_port)) + self.log.alert_log(f"[!] Client {client_ip}:{client_port} connected") + self.log.alert_log(f"[!] Total client connected: {len(self.client_list)}") + # input to listen for more clients + while True: + ALERT = "\033[93m[Server]\033[0m Listen for more clients? (y/n): " + prompt = input(ALERT) + if prompt.lower() == 'y': + break + elif prompt.lower() == 'n': + return + else: + self.log.warning_log("[!] Invalid prompt input") + except socket.timeout: + self.log.alert_log("[!] Timeout, no more clients connected") + return + except ValueError: + self.log.warning_log(f"[!] Invalid message received, ignoring...") + except Exception as e: + self.log.warning_log(f"[!] Error: {e}") + exit() + + def connection_handler(self, ip: str, port: int): + self.three_way_handshake(ip_client=ip, port_client=port) + self.send_file(ip_client=ip, port_client=port) + + def parallel_listen(self): + self.parallel_client_list: Dict[Tuple[str, int], List[Segment]] = {} + while True: + try: + self.log.alert_log(f"[!] Listening for clients on {self.connection.ip}:{self.connection.port}") + msg, addr = self.connection.listen(TIMEOUT_LISTEN) + if addr not in self.parallel_client_list: # Make sure client is not already connected + self.log.alert_log(f"[!] Client {addr[0]}:{addr[1]} connected") + self.log.alert_log(f"[!] Total client connected: {len(self.parallel_client_list) + 1}") + self.parallel_client_list[addr] = [] + process = threading.Thread(target=self.start_connection, kwargs={'ip': addr[0], 'port': addr[1]}) + process.start() + else: # If client is already connected, append message to client's message list + self.parallel_client_list[addr].append(msg) + + except socket.timeout: + self.log.alert_log("[!] Timeout, no more clients connected") + break + except Exception as e: + self.log.warning_log(f"[!] Error: {e}") + # break + + def get_response(self, ip_client: str, port_client: int): + if (self.parallel): + timeout = time.time() + 1 + while True: + if time.time() > timeout: + raise socket.timeout + if len(self.parallel_client_list[(ip_client, port_client)]) > 0: + return self.parallel_client_list[(ip_client, port_client)].pop(0), (ip_client, port_client) + else: + return self.connection.listen(TIMEOUT_TIME) + + def start_connection(self, ip: str = None, port: int = None): + if not self.parallel: + for client in self.client_list: + if self.three_way_handshake(ip_client=client[0], port_client=client[1]): + self.send_file(ip_client=client[0], port_client=client[1]) + else: + if self.three_way_handshake(ip_client=ip, port_client=port): + self.send_file(ip_client=ip, port_client=port) + + def three_way_handshake(self, ip_client: str, port_client: int): + self.log.alert_log(f"[!] Starting 3-way handshake with {ip_client}:{port_client}") + self.segment.set_flag([True, False, False]) + while True: + # Send SYN + if self.segment.is_syn_flag(): + self.log.alert_log(f"[!] Sending SYN to {ip_client}:{port_client}") + self.connection.send(ip_remote=ip_client, port_remote=port_client, message=self.segment) + + try: + msg, _ = self.get_response(ip_client, port_client) + self.segment = msg + + except socket.timeout: + self.log.warning_log("[!] [TIMEOUT] SYN Timed out, retrying...") + return False + # Send ACK + elif self.segment.is_syn_ack_flag(): + self.log.alert_log(f"[!] SYN-ACK received from {ip_client}:{port_client}") + self.log.alert_log(f"[!] Sending ACK to {ip_client}:{port_client}") + self.segment = Segment() + self.segment.set_flag([False, True, False]) + self.connection.send(ip_remote=ip_client, port_remote=port_client, message=self.segment) + break + else: + self.log.warning_log("[!] Not SYN or SYN-ACK, client already connected") + return False + self.log.success_log(f"[!] 3-way handshake with {ip_client}:{port_client} complete") + return True + + # KIRIM FILE + def send_file(self, ip_client: str, port_client: int): + # INISIALISASI SEGMENT + N = WINDOW_SIZE + Rn = 0 + Sb = 0 + Sm = N - 1 + SegmentCount = len(self.file_segment) + self.log.alert_log(f"[!] Segment count: {SegmentCount}") + isMetaData = True + METADATA_SEQ = -1 + + while True: + if (isMetaData): + segment = Segment() + segment.set_seq_number(METADATA_SEQ) + completeFileName = (self.file_name + self.file_extension).encode() + encodedMetadata = Utils.encode_metadata(completeFileName) + segment.set_data(encodedMetadata) + self.connection.send(ip_client, port_client, segment) + self.log.alert_log(f"[!] Sending Metadata to {ip_client}:{port_client}") + isMetaData = False + else: + while (Sb <= Rn <= Sm and Rn < SegmentCount): + segment = Segment() + segment.set_seq_number(Rn) + if Rn >= len(self.file_segment): + break + segment.set_data(self.file_segment[Rn]) + self.connection.send(ip_client, port_client, segment) + self.log.alert_log(f"[!] Sending segment {Rn}/{SegmentCount - 1} to {ip_client}:{port_client}") + Rn += 1 + try: + msg, addr = self.connection.listen() + if msg.is_syn_ack_flag(): + # Reset connection + try: + self.log.warning_log(f'[!] Resetting connection with {ip_client}:{port_client}') + self.three_way_handshake(ip_client, port_client) + self.send_file(ip_client, port_client) + except Exception as e: + self.log.warning_log(f"[!] Error: {e}") + return + if addr != (ip_client, port_client): + # if the address haven't been registered in the client list, register it and start connection + if addr not in self.parallel_client_list: + self.log.alert_log(f"[!] Client {addr[0]}:{addr[1]} connected") + self.parallel_client_list[addr] = [] + process = threading.Thread(target=self.start_connection, kwargs={'ip': addr[0], 'port': addr[1]}) + process.start() + else: + self.log.warning_log(f"[!] Received message from unknown address {addr}, ignoring...") + continue + ack_number = msg.get_header()['ackNumber'] + self.log.success_log(f"[!] ACK {ack_number} received from {ip_client}:{port_client}") + if ack_number == SegmentCount - 1: + self.close_connection(ip_client, port_client) + return + if ack_number > SegmentCount - 1 or ack_number < -1: + continue + else: + Sb = ack_number + Sm = Sb + (N - 1) + except socket.timeout: + Rn = Sb + self.log.warning_log(f"[!] [TIMEOUT] ACK Response Timed out with {ip_client}:{port_client}") + except Exception as e: + self.log.warning_log(f"[!] Errorsss: {e}") + # return + + def close_connection(self, ip_client: str, port_client: int): + # Kirim FIN + fin = Segment() + fin.set_flag([False, False, True]) + self.connection.send(ip_client, port_client, fin) + self.log.alert_log("Sending FIN") + # Terima FIN ACK + while True: + try: + fin, _ = self.connection.listen() + bendera_fin = fin.get_flag() + if bendera_fin.fin and bendera_fin.ack: + self.log.success_log(f"[!] FIN ACK received from {ip_client}:{port_client}") + break + else: + self.log.warning_log("[!] Not FIN ACK") + except Exception as e: + self.log.warning_log("[!] Connection timed out while waiting for FIN ACK") + break + # Tutup koneksi + if (self.parallel): + self.parallel_client_list.pop((ip_client, port_client)) + + md5 = Utils.printmd5(self.file_path) + self.log.success_log(f"MD5 Hash: {md5}") + self.log.success_log(f'Connection with {ip_client}:{port_client} closed') + + +def load_args(): + arg = argparse.ArgumentParser() + arg.add_argument('-i', '--ip', type=str, default='localhost', help='ip to listen on') + arg.add_argument('-p', '--port', type=int, default=1337, help='port to listen on') + arg.add_argument('-f', '--file', type=str, default='input.txt', help='path to file input') + arg.add_argument('-g', '--game', type=int, default=0, help='turn on/off game mode') + args = arg.parse_args() + return args + + +if __name__ == '__main__': + while True: + args = load_args() + if args.game: + server = Server(Connection(ip=args.ip, port=args.port)) + server.run_game() + else: + server = Server(Connection(ip=args.ip, port=args.port),file_path=args.file) + server.run()