#include "sys/ipc.h" #include "sys/shm.h" #include #include #include #include #include #include #include #include #include #include // commands.h already is included by util.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]; char message_id[COMMAND_LENGTH]; } saved_message_t; typedef struct { char id[COMMAND_LENGTH]; bool logged_in; char (*muted_clients)[COMMAND_LENGTH]; int muted_count; char (*muted_groups)[COMMAND_LENGTH]; int muted_groups_count; char nickname[COMMAND_LENGTH]; // Nicknames cannot contain special characters // like # (reserved for groups) 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_t; typedef struct { Client_t **members; char name[COMMAND_LENGTH - 1]; // In the command one char is used for # to indicate a group int client_count; } Group_t; static void handle_hearbeat_timeouts(Client_t *clients, int client_count, int semaphore_id) { struct sembuf sem_op = { .sem_num = 0, .sem_op = -1, .sem_flg = 0, }; while (1) { sem_op.sem_op = 1; semop(semaphore_id, &sem_op, 1); struct timeval tv; gettimeofday(&tv, NULL); for (int i = 0; i < client_count; i++) { if (clients[i].logged_in && (tv.tv_sec - clients[i].last_seen) > HEARTBEAT_TIMEOUT) { printf("Client %s has timed out due to missed heartbeats\n", clients[i].id); clients[i].logged_in = false; clients[i].queue_id = -1; } } sem_op.sem_op = 1; semop(semaphore_id, &sem_op, 1); sleep(1); } } int main(int argc, char **argv) { server_config_t config; // If we are given a path, try to load the config from there if (argc >= 2) { load_config(&config, argv[1]); } else { fprintf(stderr, "No config file provided, using default location example.conf\n"); load_config(&config, "./example.conf"); } int client_count = config.client_count; int group_count = config.group_count; int clients_memory_id = shmget(IPC_PRIVATE, sizeof(Client_t) * client_count, IPC_CREAT | 0666); Client_t *clients = shmat(clients_memory_id, NULL, 0); Group_t *groups = malloc(sizeof(Group_t) * group_count); // Temporary buffer for ACK_DELIVERs // This could be larger and if there are even more clients in a group it won't be good so let's hope there won't be saved_message_t message_buffer[20] = {0}; int message_buffer_count = 0; int semaphore_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666); semctl(semaphore_id, 0, SETVAL, 1); for (int i = 0; i < client_count; i++) { strncpy(clients[i].id, config.clients[i].client_id, COMMAND_LENGTH - 1); clients[i].logged_in = false; clients[i].muted_clients = calloc(client_count, COMMAND_LENGTH); clients[i].muted_count = 0; clients[i].muted_groups = calloc(group_count, COMMAND_LENGTH); clients[i].muted_groups_count = 0; strncpy(clients[i].nickname, config.clients[i].nickname, COMMAND_LENGTH - 1); clients[i].queue_id = -1; clients[i].last_seen = 0; clients[i].saved_message_count = 0; } for (int i = 0; i < group_count; i++) { strncpy(groups[i].name, config.groups[i].name, COMMAND_LENGTH - 2); // 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++) { if (strncmp(clients[k].id, config.groups[i].member_ids[j], COMMAND_LENGTH) == 0) { groups[i].members[j] = &clients[k]; break; } } } } // No longer useful after copying the data over for (int i = 0; i < group_count; i++) { for (int j = 0; j < config.groups[i].member_count; j++) { free(config.groups[i].member_ids[j]); } } free(config.clients); free(config.groups); int server_queue_id = msgget(IPC_PRIVATE, 0666 | IPC_CREAT); if (server_queue_id == -1) { perror("msgget failed"); return 1; } int id_file_handle = open("/home/piotr/server_queue_id", O_WRONLY | O_CREAT | O_TRUNC, 0666); if (!id_file_handle) { perror("Failed to open file"); return 1; } if (write(id_file_handle, &server_queue_id, sizeof(int)) == -1) { perror("Failed to write server queue id to file"); return 1; } close(id_file_handle); if (fork() == 0) { handle_hearbeat_timeouts(clients, client_count, semaphore_id); return 0; } printf("Server ready.\n"); msgbuf_t msgbuf; while (1) { // Read from the ipc, then handle the command int read_status = msgrcv(server_queue_id, &msgbuf, sizeof(msgbuf) - sizeof(long), 0, 0); switch (msgbuf.mtype) { case Login: { printf("Received login request for id: %s\n", msgbuf.sender); 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; } } // Reuse the buffer to get the client's message queue id int msg_queue_id = atoi(msgbuf.command); if (client == NULL) { printf("User not found: %s\n", msgbuf.sender); msgbuf_t response = { .mtype = Login, .stype = ERR_NOT_FOUND, }; send(msg_queue_id, &response); continue; } if (client->logged_in) { printf("User already logged in: %s\n", msgbuf.sender); msgbuf_t response = { .mtype = Login, .stype = ERR_ALREADY_LOGGED_IN, }; send(msg_queue_id, &response); continue; } client->logged_in = true; client->queue_id = msg_queue_id; struct timeval tv; gettimeofday(&tv, NULL); client->last_seen = tv.tv_sec; msgbuf_t response = { .mtype = Login, .command = "", .stype = ACK_ACCEPTED, }; strncpy(response.command, client->nickname, COMMAND_LENGTH); printf("User accepted: %s\n", msgbuf.sender); send(msg_queue_id, &response); // Check if there are any saved messages for this client if (client->saved_message_count > 0) { response.mtype = Inbox; snprintf(response.command, COMMAND_LENGTH, "0"); send(client->queue_id, &response); } break; } case Message: { // Find the client that sent the message then forward it to all the // specified recipients 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; } } printf("Received message from client id %s to %s\n", msgbuf.sender, msgbuf.command); if (client == NULL || !client->logged_in) { // Client not found or not logged in printf("Sender not found or not logged in"); msgbuf_t response = { .mtype = Message, .stype = ERR_NOT_LOGGED_IN, }; send(read_status, &response); continue; } // 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 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; } } } else { // Target is a group, find it first Group_t *target_group = NULL; for (int i = 0; i < group_count; i++) { if (strncmp(groups[i].name, msgbuf.command + 1, COMMAND_LENGTH - 1) == 0) { target_group = &groups[i]; break; } } // Target group not found if (target_group == NULL) { printf("Target group %s not found\n", msgbuf.command + 1); msgbuf_t response = { .mtype = Message, .stype = ERR_NOT_FOUND, }; send(client->queue_id, &response); free(recipients); continue; } // Forward the message to all group members except the sender int recipient_idx = 0; for (int i = 0; i < target_group->client_count; i++) { if (target_group->members[i] == NULL) continue; if (strncmp(target_group->members[i]->id, client->id, COMMAND_LENGTH) == 0) { continue; } // Check if this member has muted the group bool has_muted_group = false; for (int j = 0; j < target_group->members[i]->muted_groups_count; j++) { if (strcmp(target_group->members[i]->muted_groups[j], target_group->name) == 0) { has_muted_group = true; break; } } if (has_muted_group) { printf("Group message to %s blocked (group %s is muted)\n", target_group->members[i]->nickname, target_group->name); continue; } recipients[recipient_idx++] = target_group->members[i]; } if (recipient_idx < client_count - 1) { recipients[recipient_idx] = NULL; } } // Check if we found any recipients if (recipients[0] == NULL) { if (target_type == '#') { printf("Group message sent but no recipients received it (all muted or group empty)\n"); msgbuf_t response = { .mtype = Signal, .stype = ACK_ACCEPTED, }; send(client->queue_id, &response); } else { printf("Target user %s not found\n", msgbuf.command); msgbuf_t response = { .mtype = Signal, .stype = ERR_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]; // Just the empty pointer so no more recipients if (target_client == NULL) break; // Check if target_client has muted the sender bool is_muted = false; for (int j = 0; j < target_client->muted_count; j++) { if (strcmp(target_client->muted_clients[j], client->nickname) == 0) { is_muted = true; break; } } if (is_muted) { printf("Message from %s to %s blocked (muted)\n", client->nickname, target_client->nickname); continue; // Skip this recipient, discard the message } // Check if the target is online or not, if they are not, store the message if (!target_client->logged_in) { saved_message_t *saved_msg = NULL; 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_msg = &target_client->saved_messages[target_client->saved_message_count - 1]; } else { // There is room for a new message saved_msg = &target_client->saved_messages[target_client->saved_message_count++]; } get_id(saved_msg->message_id); strncpy(saved_msg->sender, client->nickname, COMMAND_LENGTH); strncpy(saved_msg->message, msgbuf.message, MESSAGE_LENGTH); msgbuf_t response = { .mtype = Signal, .stype = ACK_ACCEPTED, }; send(client->queue_id, &response); printf("Message saved for offline client %s\n", target_client->id); continue; } printf("Forwarding message from user %s to %s\n", client->nickname, target_client->nickname); msgbuf_t forward_msg = { .mtype = Message, .sender = "", }; get_id(forward_msg.command); strncpy(forward_msg.sender, client->nickname, COMMAND_LENGTH); strncpy(forward_msg.message, msgbuf.message, MESSAGE_LENGTH); send(target_client->queue_id, &forward_msg); // Add to message buffer for tracking ACK_DELIVERED if (message_buffer_count < 20) { saved_message_t *buffered = &message_buffer[message_buffer_count++]; strncpy(buffered->message_id, forward_msg.command, COMMAND_LENGTH); strncpy(buffered->sender, client->id, COMMAND_LENGTH); strncpy(buffered->command, target_client->id, COMMAND_LENGTH); strncpy(buffered->message, msgbuf.message, MESSAGE_LENGTH); } forward_msg.stype = ACK_ACCEPTED; forward_msg.mtype = Signal; send(client->queue_id, &forward_msg); printf("Forwarding message: %s\n", msgbuf.message); } free(recipients); break; } case Logout: { for (int i = 0; i < client_count; i++) { if (strncmp(clients[i].id, msgbuf.sender, COMMAND_LENGTH) == 0 && clients[i].logged_in) { clients[i].logged_in = false; clients[i].queue_id = -1; printf("Client %s logged out\n", msgbuf.sender); break; } } break; } case Hearbeat: for (int i = 0; i < client_count; i++) { if (strncmp(clients[i].id, msgbuf.sender, COMMAND_LENGTH) == 0 && clients[i].logged_in) { // Take the semaphore to update last_seen struct sembuf sem_op = { .sem_num = 0, .sem_op = -1, .sem_flg = 0, }; semop(semaphore_id, &sem_op, 1); struct timeval tv; gettimeofday(&tv, NULL); clients[i].last_seen = tv.tv_sec; // Free the semaphore sem_op.sem_op = 1; semop(semaphore_id, &sem_op, 1); } } break; case List: { 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; } char list_buffer[MESSAGE_LENGTH] = ""; if (strncmp(msgbuf.command, "groups", COMMAND_LENGTH) == 0) { for (int i = 0; i < group_count; i++) { bool isMember = false; for (int j = 0; j < groups[i].client_count; j++) { if (groups[i].members[j] != NULL && strncmp(groups[i].members[j]->id, client->id, COMMAND_LENGTH) == 0) { isMember = true; break; } } char line[COMMAND_LENGTH + 4 + 9]; // Tell the user if they are in the group or not snprintf(line, COMMAND_LENGTH + 4 + 9, "- %s%s\n", groups[i].name, isMember ? " (member)" : ""); strncat(list_buffer, line, MESSAGE_LENGTH - strlen(list_buffer) - 1); } msgbuf_t response = { .mtype = List, .command = "groups", }; strncpy(response.message, list_buffer, MESSAGE_LENGTH); send(client->queue_id, &response); continue; } // Parse the command parameter // Just check if the user wants the active clients // Not worth checking if the other option is valid bool active_only = false; if (strncmp(msgbuf.command, "active", COMMAND_LENGTH) == 0) { active_only = true; } for (int i = 0; i < client_count; i++) { if (active_only && !clients[i].logged_in) continue; // Even a tiny bit of formatting, we fancy like that char line[COMMAND_LENGTH + 4]; snprintf(line, COMMAND_LENGTH + 4, "- %s\n", clients[i].nickname); strncat(list_buffer, line, MESSAGE_LENGTH - strlen(list_buffer) - 1); } msgbuf_t response = { .mtype = List, .command = "", }; strncpy(response.command, active_only ? "active" : "all", COMMAND_LENGTH); strncpy(response.message, list_buffer, MESSAGE_LENGTH); send(client->queue_id, &response); break; } case Inbox: { 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; msgbuf_t response = { .mtype = Inbox, .sender = "", .command = "", .message = "", }; // Now, for all the stored messages, send them one by one, incrementing // the counter in command for (int i = 0; i < client->saved_message_count; i++) { snprintf(response.command, COMMAND_LENGTH, "%d", i + 1); strncpy(response.sender, client->saved_messages[i].sender, COMMAND_LENGTH); strncpy(response.message, client->saved_messages[i].message, MESSAGE_LENGTH); send(client->queue_id, &response); } // Now send ACK_DELIVERED to senders for (int i = 0; i < client->saved_message_count; i++) { saved_message_t *saved_msg = &client->saved_messages[i]; Client_t *original_sender = NULL; for (int j = 0; j < client_count; j++) { if (strncmp(clients[j].nickname, saved_msg->sender, COMMAND_LENGTH) == 0) { original_sender = &clients[j]; break; } } // Technically, the first check should never fail, as we can't remove // users dynamically but I'll leave it here anyway if (original_sender == NULL || !original_sender->logged_in) { continue; } msgbuf_t ack_msg = { .mtype = Signal, .sender = "", .command = "", .stype = ACK_DELIVERED, }; strncpy(ack_msg.sender, client->nickname, COMMAND_LENGTH); send(original_sender->queue_id, &ack_msg); } 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_NOT_FOUND, }; send(client->queue_id, &response); continue; } // Check if user is already in the group bool is_in_group = false; for (int i = 0; i < target_group->client_count; i++) { if (target_group->members[i] != NULL) { if (strncmp(client->id, target_group->members[i]->id, COMMAND_LENGTH) == 0) { is_in_group = true; break; } } } 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_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++) { 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_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_NOT_FOUND, // Could add ERR_NOT_IN_GROUP error code }; send(client->queue_id, &response); } break; } case Mute: { 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; // Check if muting a group (starts with #) or a user if (msgbuf.command[0] == '#') { // Muting a group char *group_name = msgbuf.command + 1; // Skip the # // Find the group Group_t *target_group = NULL; for (int i = 0; i < group_count; i++) { if (strcmp(groups[i].name, group_name) == 0) { target_group = &groups[i]; break; } } if (target_group == NULL) { msgbuf_t response = {.mtype = Mute, .stype = ERR_NOT_FOUND}; strncpy(response.command, msgbuf.command, COMMAND_LENGTH); send(client->queue_id, &response); continue; } // Check if already muted bool already_muted = false; for (int i = 0; i < client->muted_groups_count; i++) { if (strcmp(client->muted_groups[i], group_name) == 0) { already_muted = true; break; } } if (already_muted) { msgbuf_t response = {.mtype = Mute, .stype = ERR_ALREADY_IN_GROUP}; strncpy(response.command, msgbuf.command, COMMAND_LENGTH); send(client->queue_id, &response); continue; } // Add to muted groups list if (client->muted_groups_count < group_count) { strncpy(client->muted_groups[client->muted_groups_count], group_name, COMMAND_LENGTH - 1); client->muted_groups_count++; msgbuf_t response = {.mtype = Mute, .stype = ACK_ACCEPTED}; strncpy(response.command, msgbuf.command, COMMAND_LENGTH); send(client->queue_id, &response); printf("Client %s muted group %s\n", client->id, group_name); } } else { // Muting a user Client_t *target = NULL; for (int i = 0; i < client_count; i++) { if (strcmp(clients[i].nickname, msgbuf.command) == 0) { target = &clients[i]; break; } } if (target == NULL) { msgbuf_t response = {.mtype = Mute, .stype = ERR_NOT_FOUND}; send(client->queue_id, &response); continue; } // Check if already muted bool already_muted = false; for (int i = 0; i < client->muted_count; i++) { if (strcmp(client->muted_clients[i], target->nickname) == 0) { already_muted = true; break; } } if (already_muted) { msgbuf_t response = {.mtype = Mute, .stype = ERR_ALREADY_IN_GROUP}; send(client->queue_id, &response); continue; } // Add to muted list if (client->muted_count < client_count) { strncpy(client->muted_clients[client->muted_count], target->nickname, COMMAND_LENGTH - 1); client->muted_count++; msgbuf_t response = {.mtype = Mute, .stype = ACK_ACCEPTED}; strncpy(response.command, msgbuf.command, COMMAND_LENGTH); send(client->queue_id, &response); printf("Client %s muted %s\n", client->id, target->nickname); } } break; } case Unmute: { 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; // Check if unmuting a group (starts with #) or a user if (msgbuf.command[0] == '#') { char *group_name = msgbuf.command + 1; int muted_index = -1; for (int i = 0; i < client->muted_groups_count; i++) { if (strcmp(client->muted_groups[i], group_name) == 0) { muted_index = i; break; } } if (muted_index == -1) { msgbuf_t response = {.mtype = Unmute, .stype = ERR_NOT_FOUND}; strncpy(response.command, msgbuf.command, COMMAND_LENGTH); send(client->queue_id, &response); continue; } // Remove from muted groups list by shifting remaining elements for (int i = muted_index; i < client->muted_groups_count - 1; i++) { strncpy(client->muted_groups[i], client->muted_groups[i + 1], COMMAND_LENGTH); } client->muted_groups_count--; msgbuf_t response = {.mtype = Unmute, .stype = ACK_ACCEPTED, .command = ""}; strncpy(response.command, msgbuf.command, COMMAND_LENGTH); send(client->queue_id, &response); printf("Client %s unmuted group %s\n", client->id, group_name); } else { int muted_index = -1; for (int i = 0; i < client->muted_count; i++) { if (strcmp(client->muted_clients[i], msgbuf.command) == 0) { muted_index = i; break; } } if (muted_index == -1) { msgbuf_t response = {.mtype = Unmute, .stype = ERR_NOT_FOUND}; strncpy(response.command, msgbuf.command, COMMAND_LENGTH); send(client->queue_id, &response); continue; } // Remove from muted list by shifting remaining elements for (int i = muted_index; i < client->muted_count - 1; i++) { strncpy(client->muted_clients[i], client->muted_clients[i + 1], COMMAND_LENGTH); } client->muted_count--; msgbuf_t response = {.mtype = Unmute, .stype = ACK_ACCEPTED}; strncpy(response.command, msgbuf.command, COMMAND_LENGTH); send(client->queue_id, &response); printf("Client %s unmuted %s\n", client->id, msgbuf.command); } break; } case Signal: { // Handle the delivery signal from the recipent int message_idx = -1; if (msgbuf.stype == ACK_DELIVERED) { for (int i = 0; i < message_buffer_count; i++) { if (strncmp(msgbuf.command, message_buffer[i].message_id, COMMAND_LENGTH) == 0) { message_idx = i; break; } } if (message_idx == -1) continue; saved_message_t *message = &message_buffer[message_idx]; for (int i = 0; i < client_count; i++) { if (strncmp(message->sender, clients[i].id, COMMAND_LENGTH) == 0) { msgbuf_t ack = {.mtype = Signal, .stype = ACK_DELIVERED, .command = ""}; // Send the information to whom the message got delivered to strncpy(ack.sender, msgbuf.sender, COMMAND_LENGTH); send(clients[i].queue_id, &ack); // Remove from message buffer by shifting remaining elements for (int j = message_idx; j < message_buffer_count - 1; j++) { message_buffer[j] = message_buffer[j + 1]; } message_buffer_count--; break; } } } break; } } } return 0; }