Compare commits

..

2 Commits

Author SHA1 Message Date
e87c1f74f4 ready to test 2025-07-30 12:56:32 +01:00
88fa2fa2a3 ready to test 2025-07-30 08:59:21 +01:00
6 changed files with 212 additions and 50 deletions

View File

@@ -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()

5
data.c
View File

@@ -99,6 +99,11 @@ char *char_array_get_until_char(char_array_t *array, char c) {
return ret; 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) { char *char_array_get_bytes(char_array_t *array, size_t length) {
if (length > array->size) { if (length > array->size) {
return NULL; return NULL;

1
data.h
View File

@@ -22,6 +22,7 @@ void char_array_print_hex(const char_array_t *array);
void char_array_wipe(char_array_t *array); void char_array_wipe(char_array_t *array);
bool char_array_has_char(char_array_t *array, char c); bool char_array_has_char(char_array_t *array, char c);
char *char_array_get_until_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); char *char_array_get_bytes(char_array_t *array, size_t length);
void char_array_shift_bytes(char_array_t *array, size_t length); void char_array_shift_bytes(char_array_t *array, size_t length);

View File

@@ -30,7 +30,7 @@ int main() {
handle_args_t *args = malloc(sizeof(handle_args_t)); handle_args_t *args = malloc(sizeof(handle_args_t));
args->client = client; args->client = client;
args->connection = connection_number++; args->connection = connection_number++;
args->connections = *connections; args->connections = connections;
pthread_t thread; pthread_t thread;
pthread_create(&thread, nullptr, handle_connection, args); pthread_create(&thread, nullptr, handle_connection, args);
} }
@@ -42,51 +42,72 @@ void *handle_connection(void *args) {
handle_args_t *handleArgs = args; handle_args_t *handleArgs = args;
char buffer[1024] = {0}; char buffer[1024] = {0};
int bytesReceived; int bytesReceived;
char *message[1024]; status_t status = CONNECTED;
char *message = malloc(sizeof(char)*1024);
char_array_t *data = char_array_create(1024); char_array_t *data = char_array_create(1024);
send(handleArgs->client, WELCOME, sizeof(WELCOME), 0); send(handleArgs->client, WELCOME, sizeof(WELCOME), 0);
bytesReceived = recv(handleArgs->client, buffer, sizeof(buffer), 0); char *username = malloc(sizeof(char)*1024);
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);
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);
exit(3);
}
pthread_mutex_lock(&mutex);
strcat(message, "* The room contains: ");
for (int i = 0; i < handleArgs->connections.size; i++) {
strcat(message, handleArgs->connections.clients[i].username);
strcat(message, " ");
}
message[strlen(message)-1] = '\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);
while ((bytesReceived = recv(handleArgs->client, buffer, sizeof(buffer), 0)) > 0) { while ((bytesReceived = recv(handleArgs->client, buffer, sizeof(buffer), 0)) > 0) {
printf("{%d} Client sent: |%d| \n", handleArgs->connection, bytesReceived); printf("{%d} Client sent: |%d| \n", handleArgs->connection, bytesReceived);
char_array_append(data, buffer, bytesReceived); char_array_append(data, buffer, bytesReceived);
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; char *request;
while ((request = char_array_get_until_char(data, '\n')) != NULL) { while ((request = char_array_get_until_char(data, '\n')) != NULL) {
sprintf(message, "[%s] %s\n", username, request); sprintf(message, "[%s] %s\n", username, request);
broadcast(handleArgs->connections, &handleArgs->client, message);
}
break;
} }
memset(buffer, 0, sizeof(buffer));
} }
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); free(args);
return NULL; return NULL;
} }
@@ -94,6 +115,7 @@ void *handle_connection(void *args) {
connections_t *connections_create(const int size) { connections_t *connections_create(const int size) {
connections_t *connections = malloc(sizeof(connections_t)); connections_t *connections = malloc(sizeof(connections_t));
connections->size = size; connections->size = size;
connections->len = 0;
connections->clients = calloc(size, sizeof(client_connection_t)); connections->clients = calloc(size, sizeof(client_connection_t));
return connections; return connections;
} }
@@ -106,8 +128,8 @@ void connections_append(connections_t *connections, SOCKET client, char *usernam
exit(1); exit(1);
} }
pthread_mutex_lock(&mutex); pthread_mutex_lock(&mutex);
size_t new_size = connections->size + 1; size_t new_len = connections->len + 1;
if (new_size > connections->size) { if (new_len > connections->size) {
connections->size = connections->size+64; connections->size = connections->size+64;
client_connection_t *new_array = realloc(connections->clients, connections->size * sizeof(client_connection_t)); client_connection_t *new_array = realloc(connections->clients, connections->size * sizeof(client_connection_t));
connections->clients = new_array; connections->clients = new_array;
@@ -115,16 +137,16 @@ void connections_append(connections_t *connections, SOCKET client, char *usernam
printf("Failed to allocate memory for array\n"); printf("Failed to allocate memory for array\n");
} }
} }
connections->clients[connections->size].client = client; connections->clients[connections->len].client = client;
connections->clients[connections->size].username = username; connections->clients[connections->len].username = username;
connections->size = new_size; connections->len = new_len;
printf("{%llu} Connection added\n", connections->size); printf("{%llu} Connection added\n", connections->size);
pthread_mutex_unlock(&mutex); pthread_mutex_unlock(&mutex);
}; };
void connections_remove(connections_t *connections, SOCKET client) { void connections_remove(connections_t *connections, SOCKET client) {
if (connections == NULL) { if (connections == NULL || connections->clients == NULL) {
exit(1); exit(1);
} }
pthread_mutex_lock(&mutex); pthread_mutex_lock(&mutex);
@@ -136,15 +158,20 @@ void connections_remove(connections_t *connections, SOCKET client) {
for (int j = i; j < connections->size; j++) { for (int j = i; j < connections->size; j++) {
connections->clients[j] = connections->clients[j+1]; connections->clients[j] = connections->clients[j+1];
} }
connections->size--; connections->len--;
printf("{%llu} Connection removed\n", connections->size); printf("{%llu} Connection removed\n", connections->size);
pthread_mutex_unlock(&mutex); pthread_mutex_unlock(&mutex);
return; 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) { void broadcast(const connections_t *connections, const SOCKET *source, const char *message) {
if (connections == NULL || connections->clients == NULL) {
exit(1);
}
pthread_mutex_lock(&mutex); pthread_mutex_lock(&mutex);
for (size_t i = 0; i < connections->size; i++) { for (size_t i = 0; i < connections->size; i++) {
if (connections->clients[i].client != *source) { if (connections->clients[i].client != *source) {

View File

@@ -24,10 +24,13 @@ typedef struct Connections {
typedef struct handleArgs { typedef struct handleArgs {
int connection; int connection;
SOCKET client; SOCKET client;
connections_t connections; connections_t *connections;
} handle_args_t; } handle_args_t;
typedef enum Status {
CONNECTED,
IDENTIFIED
} status_t;
void *handle_connection(void *args); void *handle_connection(void *args);

View File

@@ -2,6 +2,7 @@ import socket
import threading import threading
import time import time
import unittest import unittest
import sys
class TestBudgetChat(unittest.TestCase): class TestBudgetChat(unittest.TestCase):
SERVER_HOST = 'localhost' SERVER_HOST = 'localhost'
@@ -39,9 +40,11 @@ class TestBudgetChat(unittest.TestCase):
_ = self.receive_message(client) # Welcome message _ = self.receive_message(client) # Welcome message
client.send(f"{name}\n".encode('ascii')) client.send(f"{name}\n".encode('ascii'))
# Server should disconnect us # Server should disconnect us
response = self.receive_message(client)
with self.assertRaises((ConnectionResetError, ConnectionAbortedError, socket.error)): 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() client.close()
def test_valid_username(self): def test_valid_username(self):
@@ -95,7 +98,7 @@ class TestBudgetChat(unittest.TestCase):
client.send(f"{username}\n".encode('ascii')) client.send(f"{username}\n".encode('ascii'))
_ = self.receive_message(client) # Room info _ = self.receive_message(client) # Room info
clients.append(client) 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 # Send a message from the last client
test_message = "Hello from last user!" test_message = "Hello from last user!"
@@ -105,6 +108,7 @@ class TestBudgetChat(unittest.TestCase):
expected = f"[{usernames[-1]}] {test_message}" expected = f"[{usernames[-1]}] {test_message}"
for i in range(len(clients)-1): for i in range(len(clients)-1):
received = self.receive_message(clients[i]) received = self.receive_message(clients[i])
print(f"Received: {received}")
self.assertEqual(expected, received) self.assertEqual(expected, received)
# Cleanup # Cleanup
@@ -126,10 +130,49 @@ class TestBudgetChat(unittest.TestCase):
# Close client2 and check if client1 receives departure message # Close client2 and check if client1 receives departure message
client2.close() client2.close()
departure_msg = self.receive_message(client1) departure_msg = self.receive_message(client1)
print(departure_msg)
self.assertTrue("user2" in departure_msg) self.assertTrue("user2" in departure_msg)
self.assertTrue(departure_msg.startswith("*")) self.assertTrue(departure_msg.startswith("*"))
client1.close() 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__': 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...")