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;
};
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;

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);
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);

View File

@@ -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);
}
@@ -42,51 +42,72 @@ void *handle_connection(void *args) {
handle_args_t *handleArgs = args;
char buffer[1024] = {0};
int bytesReceived;
char *message[1024];
status_t status = CONNECTED;
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);
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);
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;
}
@@ -94,6 +115,7 @@ void *handle_connection(void *args) {
connections_t *connections_create(const int size) {
connections_t *connections = malloc(sizeof(connections_t));
connections->size = size;
connections->len = 0;
connections->clients = calloc(size, sizeof(client_connection_t));
return connections;
}
@@ -106,8 +128,8 @@ void connections_append(connections_t *connections, SOCKET client, char *usernam
exit(1);
}
pthread_mutex_lock(&mutex);
size_t new_size = connections->size + 1;
if (new_size > connections->size) {
size_t new_len = connections->len + 1;
if (new_len > connections->size) {
connections->size = connections->size+64;
client_connection_t *new_array = realloc(connections->clients, connections->size * sizeof(client_connection_t));
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");
}
}
connections->clients[connections->size].client = client;
connections->clients[connections->size].username = username;
connections->size = new_size;
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);
};
void connections_remove(connections_t *connections, SOCKET client) {
if (connections == NULL) {
if (connections == NULL || connections->clients == NULL) {
exit(1);
}
pthread_mutex_lock(&mutex);
@@ -136,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) {

View File

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

View File

@@ -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...")