#include "bits/types/struct_timeval.h" #include "sys/ipc.h" #include "sys/shm.h" #include #include #include #include #include #include #include #include #include #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; char *muted_clients; 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 { Client **clients; char name[16]; int client_count; } Group; static void handle_hearbeat_timeouts(Client *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(HEARTBEAT_TIMEOUT); } } 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 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}; 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; } 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 *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_USER_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, .stype = ACK_ACCEPTED, }; printf("User accepted: %s\n", msgbuf.sender); send(msg_queue_id, &response); break; } case Message: { printf("Recieved message, checking which client\n"); // Find the client that sent the message then forward it to all the // specified recipients Client *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: %s\n", msgbuf.sender, msgbuf.message); if (client == NULL || !client->logged_in) { // Client not found or not logged in printf("Client 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 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); if (target_type == 'u') { Client *target_client = NULL; for (int i = 0; i < client_count; i++) { if (strncmp(clients[i].nickname, 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 from user %s to %s\n", client->nickname, target_client->nickname); 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; } // 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) { // 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_clients: { Client *client = NULL; for (int i = 0; i < client_count; i++) { if (strncmp(clients[i].id, msgbuf.sender, COMMAND_LENGTH) == 0) { client = &clients[i]; break; } } // TODO: Rethink if we even want to respond in such case if (client == NULL || !client->logged_in) { msgbuf_t response = { .mtype = Message, .stype = ERR_NOT_LOGGED_IN, }; send(read_status, &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; } char list_buffer[MESSAGE_LENGTH] = ""; 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_clients, .command = "", }; strncpy(response.command, active_only ? "active" : "all", COMMAND_LENGTH); strncpy(response.message, list_buffer, MESSAGE_LENGTH); send(client->queue_id, &response); break; } case Mute: case Unmute: break; case Signal: // Signals are sent from server to client, not the other way around // so we ignore them here lol break; } } return 0; }