diff --git a/Makefile b/Makefile index a58ee25..1c11da9 100644 --- a/Makefile +++ b/Makefile @@ -5,11 +5,11 @@ all: client server client: mkdir -p build - $(CC) $(CFLAGS) -o build/client client.c + $(CC) $(CFLAGS) -o build/client client.c util/util.c server: mkdir -p build - $(CC) $(CFLAGS) -o build/server server.c + $(CC) $(CFLAGS) -o build/server server.c util/util.c clean: rm -f client server diff --git a/client.c b/client.c index 2502841..32c1201 100644 --- a/client.c +++ b/client.c @@ -29,6 +29,70 @@ static void cleanup_queue(int qid) { } } +static void input_loop(int server_queue_id, const char *client_id) { + while (1) { + // Minimum length plus extra space fore command name and seperators + // I'd prefer some more space than required to not have to worry about cut + // offs + char buffer[COMMAND_LENGTH + MESSAGE_LENGTH + 64]; + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + break; + } + + buffer[strcspn(buffer, "\n")] = '\0'; + + if (strncmp(buffer, "/msg ", 5) == 0) { + // In a /msg command we expect {u|g}: + char *space_ptr = strchr(buffer + 5, ' '); + if (space_ptr == NULL) { + printf("Invalid /msg format. Use /msg u: \n"); + continue; + } + char target[COMMAND_LENGTH]; + size_t target_len = space_ptr - (buffer + 5); + if (target_len >= COMMAND_LENGTH) { + printf("Target nickname too long\n"); + continue; + } + strncpy(target, buffer + 5, target_len); + target[target_len] = '\0'; + char *message = space_ptr + 1; + if (strlen(message) == 0) { + printf("Message cannot be empty\n"); + continue; + } + + // Check the format of the command string + if (target[1] != ':') { + printf("Invalid target format. Use u: or g:\n"); + continue; + } + + if (target[0] != 'u' && target[0] != 'g') { + printf("Invalid target type. Use 'u' for user or 'g' for group\n"); + continue; + } + + msgbuf_t msg = {.mtype = Message, .sender = ""}; + strncpy(msg.sender, client_id, COMMAND_LENGTH - 1); + snprintf(msg.command, COMMAND_LENGTH, "%s", target); + strncpy(msg.message, message, MESSAGE_LENGTH - 1); + if (send(server_queue_id, &msg) == -1) { + perror("msgsnd(/msg) failed"); + } + } else if (strncmp(buffer, "/list", 5) == 0) { + // handle list + } else if (strncmp(buffer, "/mute ", 6) == 0) { + // handle mute + } else if (strcmp(buffer, "/quit") == 0) { + // exit + } else { + printf("Unknown command\n"); + } + } +} + int main(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "Usage: %s \n", argv[0]); @@ -91,33 +155,27 @@ int main(int argc, char *argv[]) { return 0; } - /* Send a test message to the server */ - msgbuf_t test = {.mtype = Message, .sender = ""}; - strncpy(test.sender, client_id, COMMAND_LENGTH - 1); - snprintf(test.message, MESSAGE_LENGTH, "Hello from %s", client_id); - - if (msgsnd(server_queue_id, &test, sizeof(test) - sizeof(long), 0) == -1) { - printf("DEBUG: test.mtype = %u\n", test.mtype); - perror("failed while sending a test message"); - cleanup_queue(client_queue_id); - return 1; + if (fork() == 0) { + input_loop(server_queue_id, client_id); + return 0; } - printf("Sent test message to server: \"%s\"\n", test.message); + // /* Send a test message to the server */ + // msgbuf_t test = {.mtype = Message, .command = "u:test2", .sender = ""}; + // strncpy(test.sender, client_id, COMMAND_LENGTH - 1); + // snprintf(test.message, MESSAGE_LENGTH, "Hello from %s", client_id); - /* Now wait for server forwarded message(s) and a signal ack. - We'll wait until we've seen both a Message and a Signal ack or timeout. - */ - int seen_message = 0; - int seen_ack = 0; - const int MAX_ITER = 10; + // if (msgsnd(server_queue_id, &test, sizeof(test) - sizeof(long), 0) == -1) { + // printf("DEBUG: test.mtype = %u\n", test.mtype); + // perror("failed while sending a test message"); + // cleanup_queue(client_queue_id); + // return 1; + // } - // for (int i = 0; i < MAX_ITER && !(seen_message && seen_ack); ++i) { while (1) { msgbuf_t incoming; ssize_t got = msgrcv(client_queue_id, &incoming, sizeof(incoming) - sizeof(long), 0, 0); - perror("Got a message damn"); if (got == -1) { perror("msgrcv(incoming) failed"); @@ -130,14 +188,10 @@ int main(int argc, char *argv[]) { switch (incoming.mtype) { case Message: - printf("Received Message: \"%s\"\n", incoming.message); - seen_message = 1; + printf("%s: %s\n", incoming.sender, incoming.message); break; case Signal: - printf("Received Signal: code=%d\n", incoming.stype); - if (incoming.stype == ACK_ACCEPTED || incoming.stype == ACK_DELIVERED) { - seen_ack = 1; - } + break; default: printf("Received unknown mtype=%ld stype=%d\n", (long)incoming.mtype, @@ -146,15 +200,6 @@ int main(int argc, char *argv[]) { } } - if (!seen_message) { - fprintf(stderr, "Did not receive forwarded message from server\n"); - } - if (!seen_ack) { - fprintf(stderr, "Did not receive ack from server\n"); - } - - /* Logout/cleanup: remove our queue */ cleanup_queue(client_queue_id); - printf("Client exiting\n"); return 0; } diff --git a/server.c b/server.c index 36e7751..dd6c019 100644 --- a/server.c +++ b/server.c @@ -13,10 +13,18 @@ #include #include "commands.h" +#include "util/util.h" #define send(queue_id, msgbuf_ptr) \ msgsnd(queue_id, msgbuf_ptr, sizeof(*(msgbuf_ptr)) - sizeof(long), 0) +typedef struct { + char sender[COMMAND_LENGTH]; + char command[COMMAND_LENGTH]; + char message[MESSAGE_LENGTH]; + int message_id; +} saved_message_t; + typedef struct { char *id; bool logged_in; @@ -24,6 +32,9 @@ typedef struct { char nickname[16]; int queue_id; long last_seen; + // Things for keeping track of the saved messages + saved_message_t saved_messages[20]; + int saved_message_count; } Client; typedef struct { @@ -36,7 +47,7 @@ static void handle_hearbeat_timeouts(Client *clients, int client_count, int semaphore_id) { struct sembuf sem_op = { .sem_num = 0, - .sem_op = 1, + .sem_op = -1, .sem_flg = 0, }; while (1) { @@ -53,7 +64,7 @@ static void handle_hearbeat_timeouts(Client *clients, int client_count, clients[i].queue_id = -1; } } - sem_op.sem_op = -1; + sem_op.sem_op = 1; semop(semaphore_id, &sem_op, 1); sleep(HEARTBEAT_TIMEOUT); } @@ -117,7 +128,7 @@ int main(int argc, char **argv) { case Login: printf("Received login request for id: %s\n", msgbuf.sender); Client *client = NULL; - for (int i = 0; i < 1; i++) { + for (int i = 0; i < client_count; i++) { if (strncmp(clients[i].id, msgbuf.sender, COMMAND_LENGTH) == 0) { client = &clients[i]; break; @@ -157,6 +168,7 @@ int main(int argc, char **argv) { .mtype = Login, .stype = ACK_ACCEPTED, }; + printf("User accepted: %s\n", msgbuf.sender); send(msg_queue_id, &response); break; @@ -186,24 +198,102 @@ int main(int argc, char **argv) { continue; } - printf("Forwarding message: %s\n", msgbuf.message); + // Now we have to decode if we are to forward this message to a user or a + // group first character of command indicates that - u or g + char target_type = msgbuf.command[0]; + char target_id[COMMAND_LENGTH]; + // Skip the first char and a separtor (:) + strncpy(target_id, msgbuf.command + 2, COMMAND_LENGTH - 2); - // TODO: Find the actual recipients, for now broadcast - msgbuf_t forward_msg = { - .mtype = Message, - }; - strncpy(forward_msg.message, msgbuf.message, MESSAGE_LENGTH); - // Yes, the client sending the message also receives it - for (int i = 0; i < client_count; i++) { - if (clients[i].logged_in) { - send(clients[i].queue_id, &forward_msg); + if (target_type == 'u') { + Client *target_client = NULL; + for (int i = 0; i < client_count; i++) { + if (strncmp(clients[i].id, target_id, COMMAND_LENGTH) == 0) { + target_client = &clients[i]; + break; + } } + + if (target_client == NULL) { + // Target client not found or not logged in + printf("Target client %s not found or not logged in\n", target_id); + msgbuf_t response = { + .mtype = Message, + .stype = ERR_USER_NOT_FOUND, + }; + send(client->queue_id, &response); + continue; + } + + // Check if the target is online or not, if they are not, store the + // message + if (!target_client->logged_in) { + if (target_client->saved_message_count >= 20) { + // Discard the oldest one and shift the rest to make room + for (int i = 1; i < target_client->saved_message_count; i++) { + target_client->saved_messages[i - 1] = + target_client->saved_messages[i]; + } + saved_message_t *saved_msg = + &target_client + ->saved_messages[target_client->saved_message_count - 1]; + strncpy(saved_msg->sender, client->nickname, COMMAND_LENGTH); + strncpy(saved_msg->message, msgbuf.message, MESSAGE_LENGTH); + saved_msg->message_id = rand(); // Random id for the message + printf("Message saved for offline client %s\n", target_id); + + // Send out an ack for the sender + msgbuf_t response = { + .mtype = Message, + .stype = ACK_ACCEPTED, + }; + send(client->queue_id, &response); + continue; + } + + saved_message_t *saved_msg = + &target_client + ->saved_messages[target_client->saved_message_count++]; + strncpy(saved_msg->sender, client->nickname, COMMAND_LENGTH); + strncpy(saved_msg->message, msgbuf.message, MESSAGE_LENGTH); + saved_msg->message_id = rand(); // Random id for the message + + msgbuf_t response = { + .mtype = Message, + .stype = ACK_ACCEPTED, + }; + send(client->queue_id, &response); + printf("Message saved for offline client %s\n", target_id); + continue; + } + + printf("Forwarding message to user %s: %s\n", target_client->id, + msgbuf.message); + msgbuf_t forward_msg = { + .mtype = Message, + .sender = "", + }; + strncpy(forward_msg.sender, client->nickname, COMMAND_LENGTH); + strncpy(forward_msg.message, msgbuf.message, MESSAGE_LENGTH); + send(target_client->queue_id, &forward_msg); + + forward_msg.stype = ACK_ACCEPTED; + forward_msg.mtype = Signal; + send(client->queue_id, &forward_msg); + + printf("Forwarding message: %s\n", msgbuf.message); + } else if (target_type == 'g') { + // Forward to a group + + } else { + printf("Invalid target type: %c\n", target_type); + msgbuf_t response = { + .mtype = Message, + .stype = ERR_INVALID_FORMAT, + }; + send(client->queue_id, &response); + continue; } - - forward_msg.stype = ACK_ACCEPTED; - forward_msg.mtype = Signal; - send(client->queue_id, &forward_msg); - // TODO: Accepted and Delivered ack break; case Logout: diff --git a/util/util.c b/util/util.c new file mode 100644 index 0000000..ce71823 --- /dev/null +++ b/util/util.c @@ -0,0 +1,26 @@ +#include +#include + +static char alphs[] = {'-', '_', '0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', + 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', + 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\0'}; + +// Generate an id used for messages. Could be less than 16 chars, but thats what +// fits into the `command` field of `msgbuf_t`. +void get_id(char *id_buf) { + int alph_size = strlen(alphs) - 1; + + for (int i = 0; i < 16; i++) { + int random_num; + do { + random_num = rand(); + } while (random_num >= (RAND_MAX - RAND_MAX % alph_size)); + + random_num %= alph_size; + id_buf[i] = alphs[random_num]; + } + id_buf[15] = '\0'; +} diff --git a/util/util.h b/util/util.h new file mode 100644 index 0000000..38cc69b --- /dev/null +++ b/util/util.h @@ -0,0 +1 @@ +void get_id(char *id_buf);