diff --git a/Makefile b/Makefile index 8d1eb6d..83dd49d 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ all: client server client: mkdir -p build - $(CC) $(CFLAGS) -o build/client client.c util/util.c + $(CC) $(CFLAGS) -o build/client client.c server: mkdir -p build diff --git a/client.c b/client.c index 81a93e6..3ba6512 100644 --- a/client.c +++ b/client.c @@ -116,6 +116,32 @@ static void input_loop(int server_queue_id, const char *client_id) { if (send(server_queue_id, &msg) == -1) { perror("msgsnd(/list) failed"); } + } else if (strncmp(buffer, "/join ", 6) == 0) { + char *group_name = buffer + 6; + if (strlen(group_name) == 0) { + printf("Group name cannot be empty\n"); + continue; + } + + msgbuf_t msg = {.mtype = JoinGroup, .sender = ""}; + strncpy(msg.sender, client_id, COMMAND_LENGTH); + strncpy(msg.command, group_name, COMMAND_LENGTH - 1); + if (send(server_queue_id, &msg) == -1) { + perror("msgsnd(/join) failed"); + } + } else if (strncmp(buffer, "/leave ", 7) == 0) { + char *group_name = buffer + 7; + if (strlen(group_name) == 0) { + printf("Group name cannot be empty\n"); + continue; + } + + msgbuf_t msg = {.mtype = LeaveGroup, .sender = ""}; + strncpy(msg.sender, client_id, COMMAND_LENGTH); + strncpy(msg.command, group_name, COMMAND_LENGTH - 1); + if (send(server_queue_id, &msg) == -1) { + perror("msgsnd(/leave) failed"); + } } else if (strncmp(buffer, "/mute ", 6) == 0) { // handle mute } else if (strcmp(buffer, "/quit") == 0 || strcmp(buffer, "/exit") == 0 || strcmp(buffer, "/q") == 0) { @@ -277,6 +303,26 @@ int main(int argc, char *argv[]) { printf("Unhandled signal - code %d.\n", incoming.stype); } break; + case JoinGroup: + if (incoming.stype == ACK_ACCEPTED) { + printf("Successfully joined the group.\n"); + } else if (incoming.stype == ERR_USER_NOT_FOUND) { + printf("Group does not exist.\n"); + } else if (incoming.stype == ERR_ALREADY_IN_GROUP) { + printf("You are already in this group.\n"); + } else { + printf("Failed to join group: Unknown error (code %d).\n", incoming.stype); + } + break; + case LeaveGroup: + if (incoming.stype == ACK_ACCEPTED) { + printf("Successfully left the group.\n"); + } else if (incoming.stype == ERR_USER_NOT_FOUND) { + printf("Group does not exist.\n"); + } else { + printf("Failed to leave group: Unknown error (code %d).\n", incoming.stype); + } + break; default: printf("Received unknown command of mtype=%ld stype=%d\n", (long)incoming.mtype, incoming.stype); break; diff --git a/commands.h b/commands.h index 180faef..e7dc853 100644 --- a/commands.h +++ b/commands.h @@ -10,7 +10,8 @@ typedef enum { Logout, Hearbeat, List, - Group, + JoinGroup, + LeaveGroup, Mute, Unmute, Signal, @@ -23,6 +24,7 @@ typedef enum { ERR_UNKNOWN_COMMAND, ERR_NOT_LOGGED_IN, ERR_ALREADY_LOGGED_IN, + ERR_ALREADY_IN_GROUP, ERR_USER_NOT_FOUND, ERR_USER_MUTED, ERR_INVALID_FORMAT diff --git a/server.c b/server.c index c11263d..f7e28a6 100644 --- a/server.c +++ b/server.c @@ -1,4 +1,3 @@ -#include "bits/types/struct_timeval.h" #include "sys/ipc.h" #include "sys/shm.h" #include @@ -98,8 +97,9 @@ int main(int argc, char **argv) { for (int i = 0; i < group_count; i++) { strncpy(groups[i].name, config.groups[i].name, COMMAND_LENGTH - 2); - groups[i].client_count = config.groups[i].member_count; - groups[i].members = malloc(sizeof(Client_t *) * config.groups[i].member_count); + // Allocate space for all clients to be able to join, not just initial members + groups[i].client_count = client_count; + groups[i].members = calloc(client_count, sizeof(Client_t *)); for (int j = 0; j < config.groups[i].member_count; j++) { // Find the client with the given id for (int k = 0; k < client_count; k++) { @@ -236,12 +236,14 @@ int main(int argc, char **argv) { // Now we have to decode if we are to forward this message to a user or a // group. A group is indicated by a # at the start of the command char target_type = msgbuf.command[0]; - Client_t **recipients = malloc(sizeof(Client_t *) * client_count - 1); // We won't send to ourselves so one less + Client_t **recipients = malloc(sizeof(Client_t *) * (client_count - 1)); // We won't send to ourselves so one less if (target_type != '#') { + recipients[0] = NULL; // Initialize to NULL in case no match is found for (int i = 0; i < client_count; i++) { if (strncmp(clients[i].nickname, msgbuf.command, COMMAND_LENGTH) == 0) { recipients[0] = &clients[i]; + recipients[1] = NULL; // Mark end of list break; } } @@ -263,6 +265,7 @@ int main(int argc, char **argv) { .stype = ERR_USER_NOT_FOUND, }; send(client->queue_id, &response); + free(recipients); continue; } @@ -280,6 +283,18 @@ int main(int argc, char **argv) { } } + // Check if we found any recipients + if (recipients[0] == NULL) { + printf("Target user %s not found\n", msgbuf.command); + msgbuf_t response = { + .mtype = Signal, + .stype = ERR_USER_NOT_FOUND, + }; + send(client->queue_id, &response); + free(recipients); + continue; + } + // Now forward the message to all recipients for (int i = 0; i < client_count - 1; i++) { Client_t *target_client = recipients[i]; @@ -329,6 +344,7 @@ int main(int argc, char **argv) { printf("Forwarding message: %s\n", msgbuf.message); } + free(recipients); break; } case Logout: { @@ -381,7 +397,7 @@ int main(int argc, char **argv) { for (int i = 0; i < group_count; i++) { bool isMember = false; for (int j = 0; j < groups[i].client_count; j++) { - if (strncmp(groups[i].members[j]->id, client->id, COMMAND_LENGTH) == 0) { + if (groups[i].members[j] != NULL && strncmp(groups[i].members[j]->id, client->id, COMMAND_LENGTH) == 0) { isMember = true; break; } @@ -487,7 +503,152 @@ int main(int argc, char **argv) { client->saved_message_count = 0; break; } + // Command field contains the group name + case JoinGroup: { + Client_t *client = NULL; + for (int i = 0; i < client_count; i++) { + if (strncmp(clients[i].id, msgbuf.sender, COMMAND_LENGTH) == 0) { + client = &clients[i]; + break; + } + } + + if (client == NULL || !client->logged_in) + continue; + + printf("Request to join for group: %s\n", msgbuf.command); + + Group_t *target_group = NULL; + for (int i = 0; i < group_count; i++) { + printf("Comparing group[%d].name='%s' (len=%zu) with command='%s' (len=%zu)\n", i, groups[i].name, + strlen(groups[i].name), msgbuf.command, strlen(msgbuf.command)); + if (strcmp(groups[i].name, msgbuf.command) == 0) { + target_group = &groups[i]; + printf("Group match found!\n"); + break; + } + } + + // Target group not found + if (target_group == NULL) { + printf("Target group %s not found\n", msgbuf.command); + msgbuf_t response = { + .mtype = JoinGroup, + .stype = ERR_USER_NOT_FOUND, + }; + send(client->queue_id, &response); + continue; + } + + // Check if user is already in the group + bool is_in_group = false; + printf("Group '%s' has %d member slots\n", target_group->name, target_group->client_count); + for (int i = 0; i < target_group->client_count; i++) { + if (target_group->members[i] != NULL) { + printf(" Slot %d: %s\n", i, target_group->members[i]->id); + if (strncmp(client->id, target_group->members[i]->id, COMMAND_LENGTH) == 0) { + is_in_group = true; + break; + } + } else { + printf(" Slot %d: NULL\n", i); + } + } + + if (is_in_group) { + msgbuf_t response = {.mtype = JoinGroup, .stype = ERR_ALREADY_IN_GROUP}; + send(client->queue_id, &response); + continue; + } + + // Only after we know that the user isn't in the group, by going over the whole list, we can add them + // We could do that in the loop above, if there was a guarantee of there not being gaps in the array + // This should be a linked list... well it's too late now... + bool added = false; + for (int i = 0; i < target_group->client_count; i++) { + if (target_group->members[i] == NULL) { + target_group->members[i] = client; + msgbuf_t response = { + .mtype = JoinGroup, + .stype = ACK_ACCEPTED, + }; + send(client->queue_id, &response); + printf("Client %s joined group %s\n", client->id, target_group->name); + added = true; + break; + } + } + + if (!added) { + msgbuf_t response = { + .mtype = JoinGroup, + .stype = ERR_USER_NOT_FOUND, // Reusing errors, why not + }; + send(client->queue_id, &response); + } + break; + } + case LeaveGroup: { + Client_t *client = NULL; + for (int i = 0; i < client_count; i++) { + if (strncmp(clients[i].id, msgbuf.sender, COMMAND_LENGTH) == 0) { + client = &clients[i]; + break; + } + } + + if (client == NULL || !client->logged_in) + continue; + + printf("Request to leave group: %s\n", msgbuf.command); + + Group_t *target_group = NULL; + for (int i = 0; i < group_count; i++) { + printf("Comparing group[%d].name='%s' with command='%s'\n", i, groups[i].name, msgbuf.command); + if (strcmp(groups[i].name, msgbuf.command) == 0) { + target_group = &groups[i]; + break; + } + } + + // Target group not found + if (target_group == NULL) { + printf("Target group %s not found\n", msgbuf.command); + msgbuf_t response = { + .mtype = LeaveGroup, + .stype = ERR_USER_NOT_FOUND, + }; + send(client->queue_id, &response); + continue; + } + + bool was_in_group = false; + for (int i = 0; i < target_group->client_count; i++) { + if (target_group->members[i] != NULL && + strncmp(client->id, target_group->members[i]->id, COMMAND_LENGTH) == 0) { + target_group->members[i] = NULL; + msgbuf_t response = { + .mtype = LeaveGroup, + .stype = ACK_ACCEPTED, + }; + send(client->queue_id, &response); + printf("Client %s left group %s\n", client->id, target_group->name); + was_in_group = true; + break; + } + } + + if (!was_in_group) { + msgbuf_t response = { + .mtype = LeaveGroup, + .stype = ERR_USER_NOT_FOUND, // Could add ERR_NOT_IN_GROUP error code + }; + send(client->queue_id, &response); + } + break; + } case Mute: + case Unmute: break; case Signal: