diff --git a/client_connection_manager.py b/client_connection_manager.py new file mode 100644 index 0000000..c862197 --- /dev/null +++ b/client_connection_manager.py @@ -0,0 +1,83 @@ +import socket +import time + +def print_bytes(prefix, data): + """Print raw bytes with both hex and ASCII representation""" + hex_data = ' '.join(f'{b:02x}' for b in data) + ascii_data = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in data) + print(f"{prefix} (hex): {hex_data}") + print(f"{prefix} (ascii): {ascii_data}") + +def create_client(name): + """Create and connect a client, handle initial connection sequence""" + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', 40000)) + + # Receive welcome message + welcome = sock.recv(1024) + print(f"\n=== {name} receiving welcome message ===") + print_bytes("Received", welcome) + + # Send username + username = f"{name}\n".encode('ascii') + print(f"\n=== {name} sending username ===") + print_bytes("Sending", username) + sock.send(username) + + # Receive room info + room_info = sock.recv(1024) + print(f"\n=== {name} receiving room info ===") + print_bytes("Received", room_info) + + return sock + +def main(): + # Create first client + print("\nConnecting client 1...") + client1 = create_client("Alice") + + time.sleep(1) # Small delay between connections + + # Create second client + print("\nConnecting client 2...") + client2 = create_client("Bob") + + # Client 1 should receive notification about client 2 + join_notification = client1.recv(1024) + print("\n=== Alice receiving Bob's join notification ===") + print_bytes("Received", join_notification) + + # Client 1 sends a message + message1 = "Hello Bob!\n".encode('ascii') + print("\n=== Alice sending message ===") + print_bytes("Sending", message1) + client1.send(message1) + + # Client 2 receives the message + received1 = client2.recv(1024) + print("\n=== Bob receiving message ===") + print_bytes("Received", received1) + + # Client 2 sends a reply + message2 = "Hi Alice!\n".encode('ascii') + print("\n=== Bob sending message ===") + print_bytes("Sending", message2) + client2.send(message2) + + # Client 1 receives the reply + received2 = client1.recv(1024) + print("\n=== Alice receiving message ===") + print_bytes("Received", received2) + + # Close connections + print("\nClosing connections...") + client1.close() + + received1 = client2.recv(1024) + print("\n=== Bob receiving message ===") + print_bytes("Received", received1) + + client2.close() + +if __name__ == "__main__": + main() diff --git a/data.c b/data.c index cc3718c..196cdf9 100644 --- a/data.c +++ b/data.c @@ -99,6 +99,11 @@ char *char_array_get_until_char(char_array_t *array, char c) { return ret; }; +void char_array_put_until_char(char *arr, char_array_t *array, char c) { + char *data = char_array_get_until_char(array, c); + memcpy(arr, data, strlen(data)); +} + char *char_array_get_bytes(char_array_t *array, size_t length) { if (length > array->size) { return NULL; diff --git a/data.h b/data.h index 089475d..e11436c 100644 --- a/data.h +++ b/data.h @@ -22,6 +22,7 @@ void char_array_print_hex(const char_array_t *array); void char_array_wipe(char_array_t *array); bool char_array_has_char(char_array_t *array, char c); char *char_array_get_until_char(char_array_t *array, char c); +void char_array_put_until_char(char *arr, char_array_t *array, char c); char *char_array_get_bytes(char_array_t *array, size_t length); void char_array_shift_bytes(char_array_t *array, size_t length); diff --git a/server03.c b/server03.c index 5e7460a..0e05209 100644 --- a/server03.c +++ b/server03.c @@ -30,7 +30,7 @@ int main() { handle_args_t *args = malloc(sizeof(handle_args_t)); args->client = client; args->connection = connection_number++; - args->connections = *connections; + args->connections = connections; pthread_t thread; pthread_create(&thread, nullptr, handle_connection, args); } @@ -46,50 +46,67 @@ void *handle_connection(void *args) { char *message = malloc(sizeof(char)*1024); char_array_t *data = char_array_create(1024); send(handleArgs->client, WELCOME, sizeof(WELCOME), 0); - - bytesReceived = recv(handleArgs->client, buffer, sizeof(buffer), 0); - char_array_append(data, buffer, bytesReceived); - char *username = char_array_get_until_char(data, '\n'); - if (username == NULL) { - printf("{%d} Failed to parse username\n", handleArgs->connection); - connections_remove(&handleArgs->connections, handleArgs->client); - free(handleArgs); - pthread_exit(NULL); - exit(3); - } - username = trim(username); - if (!username_is_valid(username)) { - printf("{%d} Invalid username\n", handleArgs->connection); - connections_remove(&handleArgs->connections, handleArgs->client); - free(handleArgs); - pthread_exit(NULL); - exit(3); - } - pthread_mutex_lock(&mutex); - sprintf(message, "* The room contains: "); - for (int i = 0; i < handleArgs->connections.len; i++) { - strcat(message, handleArgs->connections.clients[i].username); - strcat(message, " "); - } - strcat(message, "\n"); - send(handleArgs->client, message, strlen(message), 0); - pthread_mutex_unlock(&mutex); - - connections_append(&handleArgs->connections, handleArgs->client, username); - sprintf(message, "* %s has entered the room\n", username); - broadcast(&handleArgs->connections, &handleArgs->client, message); - - printf("{%d} Username: %s\n", handleArgs->connection, username); - + char *username = malloc(sizeof(char)*1024); while ((bytesReceived = recv(handleArgs->client, buffer, sizeof(buffer), 0)) > 0) { printf("{%d} Client sent: |%d| \n", handleArgs->connection, bytesReceived); char_array_append(data, buffer, bytesReceived); - char *request; - while ((request = char_array_get_until_char(data, '\n')) != NULL) { - sprintf(message, "[%s] %s\n", username, request); + switch (status) { + case CONNECTED: { + if ((username = char_array_get_until_char(data, '\n')) != NULL) { + printf("{%d} Username raw: %s\n", handleArgs->connection, username); + if (username == NULL) { + printf("{%d} Failed to parse username\n", handleArgs->connection); + closesocket(handleArgs->client); + free(handleArgs); + pthread_exit(NULL); + exit(3); + } + username = trim(username); + printf("{%d} Username after trim: %s\n", handleArgs->connection, username); + if (!username_is_valid(username)) { + printf("{%d} Invalid username\n", handleArgs->connection); + closesocket(handleArgs->client); + free(handleArgs); + pthread_exit(NULL); + exit(3); + } + pthread_mutex_lock(&mutex); + sprintf(message, "* The room contains: "); + for (int i = 0; i < handleArgs->connections->len; i++) { + strcat(message, handleArgs->connections->clients[i].username); + strcat(message, " "); + } + strcat(message, "\n"); + send(handleArgs->client, message, strlen(message), 0); + pthread_mutex_unlock(&mutex); + + connections_append(handleArgs->connections, handleArgs->client, username); + sprintf(message, "* %s has entered the room\n", username); + broadcast(handleArgs->connections, &handleArgs->client, message); + + printf("{%d} Username: %s\n", handleArgs->connection, username); + status = IDENTIFIED; + } + break; + } + case IDENTIFIED: { + char *request; + while ((request = char_array_get_until_char(data, '\n')) != NULL) { + sprintf(message, "[%s] %s\n", username, request); + broadcast(handleArgs->connections, &handleArgs->client, message); + } + break; + } } + + memset(buffer, 0, sizeof(buffer)); } + sprintf(message, "* %s has left the room\n", username); + broadcast(handleArgs->connections, &handleArgs->client, message); + connections_remove(handleArgs->connections, handleArgs->client); + printf("{%d} Client disconnected\n", handleArgs->connection); + free(message); free(args); return NULL; @@ -120,8 +137,8 @@ void connections_append(connections_t *connections, SOCKET client, char *usernam printf("Failed to allocate memory for array\n"); } } - connections->clients[connections->size].client = client; - connections->clients[connections->size].username = username; + connections->clients[connections->len].client = client; + connections->clients[connections->len].username = username; connections->len = new_len; printf("{%llu} Connection added\n", connections->size); pthread_mutex_unlock(&mutex); @@ -129,7 +146,7 @@ void connections_append(connections_t *connections, SOCKET client, char *usernam }; void connections_remove(connections_t *connections, SOCKET client) { - if (connections == NULL) { + if (connections == NULL || connections->clients == NULL) { exit(1); } pthread_mutex_lock(&mutex); @@ -141,15 +158,20 @@ void connections_remove(connections_t *connections, SOCKET client) { for (int j = i; j < connections->size; j++) { connections->clients[j] = connections->clients[j+1]; } - connections->size--; + connections->len--; printf("{%llu} Connection removed\n", connections->size); pthread_mutex_unlock(&mutex); return; } } + pthread_mutex_unlock(&mutex); + printf("{%llu} Connection not found\n", connections->size); } void broadcast(const connections_t *connections, const SOCKET *source, const char *message) { + if (connections == NULL || connections->clients == NULL) { + exit(1); + } pthread_mutex_lock(&mutex); for (size_t i = 0; i < connections->size; i++) { if (connections->clients[i].client != *source) { diff --git a/server03.h b/server03.h index 45ef02d..a16469e 100644 --- a/server03.h +++ b/server03.h @@ -24,7 +24,7 @@ typedef struct Connections { typedef struct handleArgs { int connection; SOCKET client; - connections_t connections; + connections_t *connections; } handle_args_t; typedef enum Status { diff --git a/server03_chat_server_tests.py b/server03_chat_server_tests.py index 05d525f..22ccc0c 100644 --- a/server03_chat_server_tests.py +++ b/server03_chat_server_tests.py @@ -2,6 +2,7 @@ import socket import threading import time import unittest +import sys class TestBudgetChat(unittest.TestCase): SERVER_HOST = 'localhost' @@ -39,9 +40,11 @@ class TestBudgetChat(unittest.TestCase): _ = self.receive_message(client) # Welcome message client.send(f"{name}\n".encode('ascii')) # Server should disconnect us - response = self.receive_message(client) with self.assertRaises((ConnectionResetError, ConnectionAbortedError, socket.error)): - client.send("test\n".encode('ascii')) + client.sendall("test\n".encode('ascii')) + response = self.receive_message(client) + response = self.receive_message(client) + response = self.receive_message(client) client.close() def test_valid_username(self): @@ -95,7 +98,7 @@ class TestBudgetChat(unittest.TestCase): client.send(f"{username}\n".encode('ascii')) _ = self.receive_message(client) # Room info clients.append(client) - time.sleep(0.1) # Small delay to prevent race conditions + # time.sleep(0.1) # Small delay to prevent race conditions # Send a message from the last client test_message = "Hello from last user!" @@ -105,6 +108,7 @@ class TestBudgetChat(unittest.TestCase): expected = f"[{usernames[-1]}] {test_message}" for i in range(len(clients)-1): received = self.receive_message(clients[i]) + print(f"Received: {received}") self.assertEqual(expected, received) # Cleanup @@ -126,10 +130,49 @@ class TestBudgetChat(unittest.TestCase): # Close client2 and check if client1 receives departure message client2.close() departure_msg = self.receive_message(client1) + print(departure_msg) self.assertTrue("user2" in departure_msg) self.assertTrue(departure_msg.startswith("*")) client1.close() +def list_tests(): + """List all available tests""" + print("\nAvailable tests:") + test_methods = [method for method in dir(TestBudgetChat) if method.startswith('test_')] + for i, test in enumerate(test_methods, 1): + print(f"{i}. {test}") + print("\n0. Run all tests") + +def run_selected_test(test_number): + """Run a specific test based on user selection""" + suite = unittest.TestSuite() + test_methods = [method for method in dir(TestBudgetChat) if method.startswith('test_')] + + if test_number == 0: + # Run all tests + suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestBudgetChat)) + else: + try: + selected_test = test_methods[test_number - 1] + suite.addTest(TestBudgetChat(selected_test)) + except IndexError: + print("Invalid test number!") + return + + runner = unittest.TextTestRunner(verbosity=2) + runner.run(suite) + if __name__ == '__main__': - unittest.main() + while True: + list_tests() + try: + choice = input("\nEnter test number (or 'q' to quit): ") + if choice.lower() == 'q': + break + test_number = int(choice) + run_selected_test(test_number) + except ValueError: + print("Please enter a valid number!") + + input("\nPress Enter to continue...") \ No newline at end of file