Reworked msg command to be easier to use. Added group messaging.

This commit is contained in:
2026-01-06 19:47:12 +01:00
parent 0bf6e710d4
commit 904efbd9b9
4 changed files with 177 additions and 161 deletions
+119 -99
View File
@@ -15,36 +15,35 @@
#include "commands.h"
#include "util/util.h"
#define send(queue_id, msgbuf_ptr) \
msgsnd(queue_id, msgbuf_ptr, sizeof(*(msgbuf_ptr)) - sizeof(long), 0)
#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;
char message_id[COMMAND_LENGTH];
} saved_message_t;
typedef struct {
char *id;
char id[COMMAND_LENGTH];
bool logged_in;
char *muted_clients;
char nickname[16];
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;
} Client_t;
typedef struct {
Client **clients;
char name[16];
Client_t **members;
char name[COMMAND_LENGTH - 1]; // In the command one char is used for # to indicate a group
int client_count;
} Group;
} Group_t;
static void handle_hearbeat_timeouts(Client *clients, int client_count,
int semaphore_id) {
static void handle_hearbeat_timeouts(Client_t *clients, int client_count, int semaphore_id) {
struct sembuf sem_op = {
.sem_num = 0,
.sem_op = -1,
@@ -56,10 +55,8 @@ static void handle_hearbeat_timeouts(Client *clients, int client_count,
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);
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;
}
@@ -74,24 +71,29 @@ int main(int argc, char **argv) {
// TODO: Load config file with clients and groups
int client_count = 2;
int group_count = 3;
int clients_memory_id =
shmget(IPC_PRIVATE, sizeof(Client) * client_count, IPC_CREAT | 0666);
Client *clients = shmat(clients_memory_id, NULL, 0);
Group *groups = malloc(sizeof(Group) * 3);
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) * 3);
int semaphore_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);
semctl(semaphore_id, 0, SETVAL, 1);
clients[0] = (Client){.id = "test1",
.logged_in = false,
.muted_clients = NULL,
.nickname = "Kregielnia",
.last_seen = 0};
clients[1] = (Client){.id = "test2",
.logged_in = false,
.muted_clients = NULL,
.nickname = "Bajzel",
.last_seen = 0};
clients[0] =
(Client_t){.id = "test1", .logged_in = false, .muted_clients = NULL, .nickname = "Kregielnia", .last_seen = 0};
clients[1] =
(Client_t){.id = "test2", .logged_in = false, .muted_clients = NULL, .nickname = "Bajzel", .last_seen = 0};
groups[0] = (Group_t){.name = "HelloChat", .client_count = 2, .members = NULL};
groups[0].members = malloc(sizeof(Client_t *) * client_count);
groups[0].members[0] = &clients[0];
groups[0].members[1] = &clients[1];
groups[1] = (Group_t){.name = "Macarena", .client_count = 0, .members = NULL};
groups[1].members = malloc(sizeof(Client_t *) * client_count);
groups[2] = (Group_t){.name = "Bananownia", .client_count = 1, .members = NULL};
groups[2].members = malloc(sizeof(Client_t *) * client_count);
groups[2].members[0] = &clients[1];
int server_queue_id = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
@@ -100,8 +102,7 @@ int main(int argc, char **argv) {
return 1;
}
int id_file_handle =
open("/home/piotr/server_queue_id", O_WRONLY | O_CREAT | O_TRUNC, 0666);
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;
@@ -121,13 +122,12 @@ int main(int argc, char **argv) {
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);
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 *client = NULL;
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];
@@ -185,7 +185,7 @@ int main(int argc, char **argv) {
case Message: {
// Find the client that sent the message then forward it to all the
// specified recipients
Client *client = NULL;
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];
@@ -193,8 +193,7 @@ int main(int argc, char **argv) {
}
}
printf("Received message from client id %s to %s\n", msgbuf.sender,
msgbuf.command);
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
@@ -208,24 +207,30 @@ int main(int argc, char **argv) {
}
// 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
// group. A group is indicated by a # at the start of the command
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);
Client_t **recipients = malloc(sizeof(Client_t *) * client_count - 1); // We won't send to ourselves so one less
if (target_type == 'u') {
Client *target_client = NULL;
if (target_type != '#') {
for (int i = 0; i < client_count; i++) {
if (strncmp(clients[i].nickname, target_id, COMMAND_LENGTH) == 0) {
target_client = &clients[i];
if (strncmp(clients[i].nickname, msgbuf.command, COMMAND_LENGTH) == 0) {
recipients[0] = &clients[i];
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;
}
}
if (target_client == NULL) {
// Target client not found or not logged in
printf("Target client %s not found\n", target_id);
// 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_USER_NOT_FOUND,
@@ -234,50 +239,55 @@ int main(int argc, char **argv) {
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 (strncmp(target_group->members[i]->id, client->id, COMMAND_LENGTH) == 0) {
continue;
}
recipients[recipient_idx++] = target_group->members[i];
}
if (recipient_idx < client_count - 1) {
recipients[recipient_idx] = NULL;
}
}
// 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 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];
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_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++];
}
saved_message_t *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);
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);
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);
printf("Forwarding message from user %s to %s\n", client->nickname, target_client->nickname);
msgbuf_t forward_msg = {
.mtype = Message,
.sender = "",
@@ -291,27 +301,14 @@ int main(int argc, char **argv) {
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;
}
// TODO: Accepted and Delivered ack
break;
}
case Logout: {
}
case Hearbeat:
for (int i = 0; i < client_count; i++) {
if (strncmp(clients[i].id, msgbuf.sender, COMMAND_LENGTH) == 0 &&
clients[i].logged_in) {
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,
@@ -329,8 +326,8 @@ int main(int argc, char **argv) {
}
}
break;
case List_clients: {
Client *client = NULL;
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];
@@ -342,6 +339,33 @@ int main(int argc, char **argv) {
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 (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
@@ -349,7 +373,6 @@ int main(int argc, char **argv) {
if (strncmp(msgbuf.command, "active", COMMAND_LENGTH) == 0) {
active_only = true;
}
char list_buffer[MESSAGE_LENGTH] = "";
for (int i = 0; i < client_count; i++) {
if (active_only && !clients[i].logged_in)
@@ -362,7 +385,7 @@ int main(int argc, char **argv) {
}
msgbuf_t response = {
.mtype = List_clients,
.mtype = List,
.command = "",
};
@@ -372,7 +395,7 @@ int main(int argc, char **argv) {
break;
}
case Inbox: {
Client *client = NULL;
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];
@@ -394,20 +417,17 @@ int main(int argc, char **argv) {
// 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);
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 *original_sender = NULL;
Client_t *original_sender = NULL;
for (int j = 0; j < client_count; j++) {
if (strncmp(clients[j].nickname, saved_msg->sender, COMMAND_LENGTH) ==
0) {
if (strncmp(clients[j].nickname, saved_msg->sender, COMMAND_LENGTH) == 0) {
original_sender = &clients[j];
break;
}