diff options
author | Raymaekers Luca <raymaekers.luca@gmail.com> | 2024-10-31 00:32:07 +0100 |
---|---|---|
committer | Raymaekers Luca <raymaekers.luca@gmail.com> | 2024-11-03 00:58:07 +0100 |
commit | b9aeccef208d6d5b7d40b71886981723f1e14b95 (patch) | |
tree | 0312eeeb23f17bd6ba7861c112a382a10207eda9 /server.c | |
parent | 48733b6acfa27af8e030d9b7abfb9109b1ce89e0 (diff) |
Added ID system with 1 and 2-way communication
Each client now has an ID that is permanently stored to ID_FILE
location. To implement this each client now uses two connections to the
server, one for bidirectional communication and one for unidirectional
communication. This makes it easier to not receive unexpected message.
Also each client and server now has a Client struct that represents a
client and a clientsArena associated with it.
Minor changes:
- Added logging to LOGFILE, that can be turned with LOGGING macro.
- Added more error types
- Added error handling on server
- Added error messages
- Added convenience functions
- Added disconnectAndNotify() function for convenience
- Use recvTextMessageResult as multiple-value-return-type instead of **
- Separated protocol stuff into protocol.h
- Added Result types when wanting to return multiple values
- Do not allocate arena's with malloc
- Added recvAnyMessageType for receiving messages that do not need to
be stored
- Add UNIFD and BIFD file descriptors for separating requests
chatty.c:
- Convert ID to string in screen_home()
- Removed the fds global variable
- Pass fds to threadReconnect
- Implement faster sleep with nanosleep(2)
- Close file descriptors when failed so we do not have too many file
descriptors open
server.c:
- Send presence messages on disconnect & connect
- renamed i to conn
Diffstat (limited to 'server.c')
-rw-r--r-- | server.c | 567 |
1 files changed, 388 insertions, 179 deletions
@@ -1,11 +1,15 @@ #include "chatty.h" +#include "protocol.h" #include <assert.h> +#include <fcntl.h> #include <netinet/in.h> #include <poll.h> +#include <signal.h> #include <stdarg.h> #include <string.h> #include <sys/socket.h> +#include <sys/stat.h> #include <unistd.h> // timeout on polling @@ -14,276 +18,481 @@ #define MAX_CONNECTIONS 16 // Get number of connections from arena position // NOTE: this is somewhat wrong, because of when disconnections happen -#define FDS_SIZE (fdsArena->pos / sizeof(*fds)) +#define FDS_SIZE (fdsArena.pos / sizeof(struct pollfd)) +#define CLIENTS_SIZE (clientsArena.pos / sizeof(Client)) + +// Enable/Disable saving clients permanently to file +#define IMPORT_ID +// Where to save clients +#define CLIENTS_FILE "_clients" +// Where to write logs +#define LOGFILE "server.log" +// Log to LOGFILE instead of stderr +// #define LOGGING // enum for indexing the fds array enum { FDS_STDIN = 0, FDS_SERVER, FDS_CLIENTS }; -// Has information on clients -// For each pollfd in fds there should be a matching client in clients -// clients[i] <=> fds[i] +// Client information typedef struct { u8 author[AUTHOR_LEN]; // matches author property on other message types - Bool initialized; // boolean + ID id; + struct pollfd* pollunifd; // Index in fds array + struct pollfd* pollbifd; // Index in fds array } Client; +#define CLIENT_FMT "[%s](%lu)" +#define CLIENT_ARG(client) client.author, client.id + +typedef enum { + UNIFD = 0, + BIFD +} ClientFD; + +// TODO: remove +// For handing out new ids to connections. +global_variable u32 nclients = 0; + +// Returns client matching id in clients. +// clientsArena is used to get an upper bound. +// Returns 0 if there was no client found. +Client* +getClientByID(Arena* clientsArena, ID id) +{ + Client* clients = clientsArena->addr; + for (u32 i = 0; i < (clientsArena->pos / sizeof(*clients)); i++) { + if (clients[i].id == id) + return clients + i; + } + return 0; +} + +// Print TextMessage prettily +void +printTextMessage(TextMessage* message, Client* client, u8 wide) +{ + u8 timestamp[TIMESTAMP_LEN] = {0}; + formatTimestamp(timestamp, message->timestamp); + + if (wide) { + setlocale(LC_ALL, ""); + wprintf(L"TextMessage: %s [%s] %ls\n", timestamp, client->author, (wchar_t*)&message->text); + } else { + u8 str[message->len]; + wcstombs((char*)str, (wchar_t*)&message->text, message->len * sizeof(*message->text)); + loggingf("TextMessage: %s [%s] (%d)%s\n", timestamp, client->author, message->len, str); + } +} -// Send anyMessage to all clients in fds from fdsArena except for fds[i]. +// Send header and anyMessage to each connection in fds that is nfds number of connections except +// for connfd. +// Type will filter out only connections matching the type. void -sendToOthers(Arena* fdsArena, struct pollfd* fds, u32 i, HeaderMessage* header, void* anyMessage) +sendToOthers(struct pollfd* fds, u32 nfds, s32 connfd, ClientFD type, HeaderMessage* header, void* anyMessage) { s32 nsend; - for (u32 j = FDS_CLIENTS; j < FDS_SIZE; j++) { - if (fds[j].fd == fds[i].fd) continue; - if (fds[j].fd == -1) continue; - - // send header - u32 nsend_total = 0; - nsend = send(fds[j].fd, header, sizeof(*header), 0); - assert(nsend != -1); - assert(nsend == sizeof(*header)); - nsend_total += nsend; - - // send message - switch (header->type) { - case HEADER_TYPE_PRESENCE: { - PresenceMessage* message = (PresenceMessage*)anyMessage; - nsend = send(fds[j].fd, message, sizeof(*message), 0); - assert(nsend != -1); - assert(nsend == sizeof(*message)); - fprintf(stdout, " Notifying(%d->%d).\n", fds[i].fd, fds[j].fd); - break; - } - case HEADER_TYPE_TEXT: { - TextMessage* message = (TextMessage*)anyMessage; - nsend = send(fds[j].fd, message, TEXTMESSAGE_SIZE, 0); - assert(nsend != -1); - assert(nsend == TEXTMESSAGE_SIZE); - nsend_total += nsend; - nsend = send(fds[j].fd, &message->text, message->len * sizeof(*message->text), 0); - assert(nsend != -1); - assert(nsend == (message->len * sizeof(*message->text))); - nsend_total += nsend; - break; - } - default: - fprintf(stdout, " Cannot retransmit %s\n", headerTypeString(header->type)); - } + for (u32 i = FDS_CLIENTS + type; i < nfds; i += 2) { + if (fds[i].fd == connfd) continue; + if (fds[i].fd == -1) continue; - fprintf(stdout, " Retransmitted(%d->%d) %d bytes.\n", fds[i].fd, fds[j].fd, nsend_total); + nsend = sendAnyMessage(fds[i].fd, header, anyMessage); + loggingf("sendToOthers(%d)|[%s]->%d %d bytes\n", connfd, headerTypeString(header->type), fds[i].fd, nsend); } } +// Send header and anyMessage to each connection in fds that is nfds number of connections. +// Type will filter out only connections matching the type. void -disconnect(Arena* fdsArena, struct pollfd* fds, u32 i, Client* client) +sendToAll(struct pollfd* fds, u32 nfds, ClientFD type, HeaderMessage* header, void* anyMessage) { - fprintf(stdout, "Disconnected(%d). \n", fds[i].fd); - shutdown(fds[i].fd, SHUT_RDWR); - close(fds[i].fd); // send close to client - - // Send disconnection to other connected clients - HeaderMessage header = HEADER_PRESENCEMESSAGE; - PresenceMessage message = { - .type = PRESENCE_TYPE_DISCONNECTED - }; - memcpy(message.author, client->author, AUTHOR_LEN); - sendToOthers(fdsArena, fds, i, &header, &message); - - fds[i].fd = -1; // ignore in the future - client->initialized = False; // deinitialize client + for (u32 i = FDS_CLIENTS + type; i < nfds; i += 2) { + if (fds[i].fd == -1) continue; + s32 nsend = sendAnyMessage(fds[i].fd, header, anyMessage); + loggingf("sendToAll|[%s]->%d %d bytes\n", headerTypeString(header->type), fds[i].fd, nsend); + } } -// Initialize a client that connects for the first time or reconnects. -// Receive HeaderMessage and PresenceMessage from fd and set client with the data from -// PresenceMessage. -// Notify fds in fdsArena. -// TODO: handle wrong messages +// Disconnect a client by closing the matching file descriptors void -initClient(Arena* fdsArena, struct pollfd* fds, s32 fd, Client* client) +disconnect(struct pollfd* pollfd, Client* client) { - s32 nrecv = 0; - s32 nsend = 0; + loggingf("Disconnecting "CLIENT_FMT"\n", CLIENT_ARG((*client))); + if (pollfd[UNIFD].fd != -1) { + close(pollfd[UNIFD].fd); + } + if (pollfd[BIFD].fd != -1) { + close(pollfd[BIFD].fd); + } + pollfd[UNIFD].fd = -1; + pollfd[BIFD].fd = -1; + // TODO: mark as free + if (client) { + client->pollunifd = 0; + client->pollbifd = 0; + } +} - fprintf(stdout, " Adding to clients(%d).\n", fd); +// Disconnects fds+conn from fds with nfds connections, then send a PresenceMessage to other +// clients about disconnection. +void +disconnectAndNotify(Client* client, struct pollfd* fds, u32 nfds, u32 conn) +{ + disconnect(fds + conn, client); + + local_persist HeaderMessage header = HEADER_INIT(HEADER_TYPE_PRESENCE); + PresenceMessage message = {.id = client->id, .type = PRESENCE_TYPE_DISCONNECTED}; + sendToAll(fds, nfds, UNIFD, &header, &message); +} + +// Receive authentication from pollfd->fd and create client out of it. Look in +// clientsArena if it already exists. Otherwise push a new onto the arena and write its information +// to clients_file. +// See "Authentication" in chatty.h +Client* +authenticate(Arena* clientsArena, s32 clients_file, struct pollfd* clientfds) +{ + s32 nrecv = 0; + Client* clients = clientsArena->addr; HeaderMessage header; - nrecv = recv(fd, &header, sizeof(header), 0); - assert(nrecv != -1); - assert(nrecv == sizeof(header)); - if (header.type != HEADER_TYPE_PRESENCE) { - // reject connection - close(fd); - fprintf(stdout, " Got wrong header(%d).\n", fd); - return; + nrecv = recv(clientfds[BIFD].fd, &header, sizeof(header), 0); + if (nrecv != sizeof(header)) { + loggingf("authenticate(%d)|err: %d/%lu bytes\n", clientfds[BIFD].fd, nrecv, sizeof(header)); + return 0; } - fprintf(stdout, " Got header(%d).\n", fd); - - PresenceMessage message; - nrecv = recv(fd, &message, sizeof(message), 0); - assert(nrecv != -1); - assert(nrecv == sizeof(message)); - fprintf(stdout, " Got presence message(%d).\n", fd); - - // Copy author from PresenceMessage. - memcpy(client->author, message.author, AUTHOR_LEN); - - // Notify other clients from this new one - // Reuse header and message - for (u32 j = FDS_CLIENTS; j < FDS_SIZE; j++) { - if (fds[j].fd == fd) - continue; - if (fds[j].fd == -1) - continue; - fprintf(stdout, " Notifying(%d->%d).\n", fd, fds[j].fd); - nsend = send(fds[j].fd, &header, sizeof(header), 0); - assert(nsend != -1); - assert(nsend == sizeof(header)); - nsend = send(fds[j].fd, &message, sizeof(message), 0); - assert(nsend != -1); - assert(nsend == sizeof(message)); + loggingf("authenticate(%d)|" HEADER_FMT "\n", clientfds[BIFD].fd, HEADER_ARG(header)); + + Client* client = 0; + // Scenario 1: Search for existing client + if (header.type == HEADER_TYPE_ID) { + IDMessage message; + nrecv = recv(clientfds[BIFD].fd, &message, sizeof(message), 0); + if (nrecv != sizeof(message)) { + loggingf("authenticate(%d)|err: %d/%lu bytes\n", clientfds[BIFD].fd, nrecv, sizeof(message)); + return 0; + } + + client = getClientByID(clientsArena, message.id); + if (client) { + loggingf("authenticate(%d)|found [%s](%lu)\n", clientfds[BIFD].fd, client->author, client->id); + header.type = HEADER_TYPE_ERROR; + // TODO: allow multiple connections + if (client->pollunifd != 0 || client->pollbifd != 0) { + loggingf("authenticate(%d)|err: already connected\n", clientfds[BIFD].fd); + ErrorMessage error_message = ERROR_INIT(ERROR_TYPE_ALREADYCONNECTED); + sendAnyMessage(clientfds[BIFD].fd, &header, &error_message); + return 0; + } + ErrorMessage error_message = ERROR_INIT(ERROR_TYPE_SUCCESS); + sendAnyMessage(clientfds[BIFD].fd, &header, &error_message); + } else { + loggingf("authenticate(%d)|notfound\n", clientfds[BIFD].fd); + header.type = HEADER_TYPE_ERROR; + ErrorMessage error_message = ERROR_INIT(ERROR_TYPE_NOTFOUND); + sendAnyMessage(clientfds[BIFD].fd, &header, &error_message); + return 0; + } + // Scenario 2: Create a new client + } else if (header.type == HEADER_TYPE_INTRODUCTION) { + IntroductionMessage message; + nrecv = recv(clientfds[BIFD].fd, &message, sizeof(message), 0); + if (nrecv != sizeof(message)) { + loggingf("authenticate(%d)|err: %d/%lu bytes\n", clientfds[BIFD].fd, nrecv, sizeof(message)); + return 0; + } + + // Copy metadata from IntroductionMessage + client = ArenaPush(clientsArena, sizeof(*clients)); + memcpy(client->author, message.author, AUTHOR_LEN); + client->id = nclients; + nclients++; + + // Save client +#ifdef IMPORT_ID + write(clients_file, client, sizeof(*client)); +#endif + loggingf("authenticate(%d)|Added [%s](%lu)\n", clientfds[BIFD].fd, client->author, client->id); + + HeaderMessage header = HEADER_INIT(HEADER_TYPE_ID); + IDMessage id_message = {.id = client->id}; + sendAnyMessage(clientfds[BIFD].fd, &header, &id_message); + } else { + loggingf("authenticate(%d)|Wrong header expected %s or %s\n", clientfds[BIFD].fd, headerTypeString(HEADER_TYPE_INTRODUCTION), headerTypeString(HEADER_TYPE_ID)); + return 0; } + assert(client != 0); + + client->pollunifd = clientfds; + client->pollbifd = clientfds + 1; + + return client; } int -main(void) +main(int argc, char** argv) { - s32 err, serverfd, clientfd; - u32 on = 1; + signal(SIGPIPE, SIG_IGN); + + logfd = 2; + // optional logging + if (argc > 1) { + if (*argv[1] == '-') + if (argv[1][1] == 'l') { + logfd = open(LOGFILE, O_RDWR | O_CREAT | O_TRUNC, 0600); + assert(logfd != -1); + } + } + s32 serverfd; // Start listening on the socket { - serverfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP); + s32 err; + u32 on = 1; + serverfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); assert(serverfd > 2); err = setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, (u8*)&on, sizeof(on)); - assert(err == 0); + assert(!err); const struct sockaddr_in address = { AF_INET, htons(PORT), {0}, + {0}, }; err = bind(serverfd, (const struct sockaddr*)&address, sizeof(address)); - assert(err == 0); + assert(!err); err = listen(serverfd, MAX_CONNECTIONS); - assert(err == 0); + assert(!err); + loggingf("Listening on :%d\n", PORT); } - Arena* msgsArena = ArenaAlloc(Megabytes(128)); // storing received messages - // NOTE: sent messages? - s32 nrecv = 0; // number of bytes received - - Arena* clientsArena = ArenaAlloc(MAX_CONNECTIONS * sizeof(Client)); - Arena* fdsArena = ArenaAlloc(MAX_CONNECTIONS * sizeof(struct pollfd)); - struct pollfd* fds = fdsArena->addr; - Client* clients = clientsArena->addr; + Arena clientsArena; + Arena fdsArena; + Arena msgsArena; + ArenaAlloc(&clientsArena, MAX_CONNECTIONS * sizeof(Client)); + ArenaAlloc(&fdsArena, MAX_CONNECTIONS * 2 * sizeof(struct pollfd)); + ArenaAlloc(&msgsArena, Megabytes(128)); // storing received messages + struct pollfd* fds = fdsArena.addr; + Client* clients = clientsArena.addr; + // Initializing fds struct pollfd* fdsAddr; - struct pollfd newpollfd = {-1, POLLIN, 0}; - + struct pollfd newpollfd = {-1, POLLIN, 0}; // for copying with events already set // initialize fds structure newpollfd.fd = 0; - fdsAddr = ArenaPush(fdsArena, sizeof(*fds)); + fdsAddr = ArenaPush(&fdsArena, sizeof(*fds)); memcpy(fdsAddr, &newpollfd, sizeof(*fds)); // add serverfd newpollfd.fd = serverfd; - fdsAddr = ArenaPush(fdsArena, sizeof(*fds)); + fdsAddr = ArenaPush(&fdsArena, sizeof(*fds)); memcpy(fdsAddr, &newpollfd, sizeof(*fds)); newpollfd.fd = -1; +#ifdef IMPORT_ID + s32 clients_file = open(CLIENTS_FILE, O_RDWR | O_CREAT | O_APPEND, 0600); + assert(clients_file != -1); + struct stat statbuf; + assert(fstat(clients_file, &statbuf) != -1); + + read(clients_file, clients, statbuf.st_size); + if (statbuf.st_size > 0) { + ArenaPush(&clientsArena, statbuf.st_size); + loggingf("Imported %lu client(s)\n", statbuf.st_size / sizeof(*clients)); + nclients += statbuf.st_size / sizeof(*clients); + } + for (u32 i = 0; i < nclients; i++) + loggingf("Imported: " CLIENT_FMT "\n", CLIENT_ARG(clients[i])); +#endif + // Initialize the rest of the fds array for (u32 i = FDS_CLIENTS; i < MAX_CONNECTIONS; i++) fds[i] = newpollfd; + // Reset file descriptors on imported clients + for (u32 i = 0; i < CLIENTS_SIZE; i++) { + clients[i].pollunifd = 0; + clients[i].pollbifd = 0; + } while (1) { - err = poll(fds, FDS_SIZE, TIMEOUT); + s32 err = poll(fds, FDS_SIZE, TIMEOUT); assert(err != -1); if (fds[FDS_STDIN].revents & POLLIN) { - // helps for testing and exiting gracefully - break; + u8 c; // exit on ctrl-d + if (!read(fds[FDS_STDIN].fd, &c, 1)) + break; } else if (fds[FDS_SERVER].revents & POLLIN) { - clientfd = accept(serverfd, NULL, NULL); - assert(clientfd != -1); - assert(clientfd > serverfd); - fprintf(stdout, "New connection(%d).\n", clientfd); - - // If there is a slot in fds with fds[found].fd == -1 use it instead, otherwise allocate - // some space on the arena. - u8 found; - for (found = FDS_CLIENTS; found < FDS_SIZE; found++) - if (fds[found].fd == -1) - break; - if (found == MAX_CONNECTIONS) { - // TODO: reject connection - close(clientfd); - fprintf(stdout, "Max clients reached."); - } else if (found == FDS_SIZE) { - // no more space, allocate - struct pollfd* pollfd = ArenaPush(fdsArena, sizeof(*pollfd)); - pollfd->fd = clientfd; - pollfd->events = POLLIN; + // TODO: what if we are not aligned by 2 anymore? + s32 unifd = accept(serverfd, 0, 0); + s32 bifd = accept(serverfd, 0, 0); + + if (unifd == -1 || bifd == -1) { + loggingf("Error while accepting connection (%d,%d)\n", unifd, bifd); + if (unifd != -1) close(unifd); + if (bifd != -1) close(bifd); + continue; + } else + loggingf("New connection(%d,%d)\n", unifd, bifd); + + // TODO: find empty space in arena + if (nclients + 1 == MAX_CONNECTIONS) { + local_persist HeaderMessage header = HEADER_INIT(HEADER_TYPE_ERROR); + local_persist ErrorMessage message = ERROR_INIT(ERROR_TYPE_TOOMANYCONNECTIONS); + sendAnyMessage(unifd, &header, &message); + if (unifd != -1) + close(unifd); + if (bifd != -1) + close(bifd); + loggingf("Max clients reached. Rejected connection\n"); } else { - // hole found - fds[found].fd = clientfd; - fds[found].events = POLLIN; - fprintf(stdout, "Added pollfd(%d).\n", clientfd); + // no more space, allocate + struct pollfd* clientfds = ArenaPush(&fdsArena, 2 * sizeof(*clientfds)); + clientfds[UNIFD].fd = unifd; + clientfds[UNIFD].events = POLLIN; + clientfds[BIFD].fd = bifd; + clientfds[BIFD].events = POLLIN; + loggingf("Added pollfd(%d,%d)\n", unifd, bifd); } } - // Check for messages from clients - for (u32 i = FDS_CLIENTS; i < FDS_SIZE; i++) { - if (!(fds[i].revents & POLLIN)) - continue; - assert(fds[i].fd != -1); - fprintf(stdout, "Message(%d).\n", fds[i].fd); - Client* client = clients + i; - - // Initialize the client if this is the first time - if (!client->initialized) { - initClient(fdsArena, fds, fds[i].fd, client); - client->initialized = True; - fprintf(stdout, " Added to clients(%d): %s\n", fds[i].fd, client->author); + // Check for messages from clients in their unifd + for (u32 conn = FDS_CLIENTS; conn < FDS_SIZE; conn += 2) { + if (!(fds[conn].revents & POLLIN)) continue; + if (fds[conn].fd == -1) continue; + loggingf("Message unifd (%d)\n", fds[conn].fd); + + // Get client associated with connection + Client* client = 0; + for (u32 j = 0; j < CLIENTS_SIZE; j++) { + if (!clients[j].pollunifd) + continue; + if (clients[j].pollunifd == fds + conn) { + client = clients + j; + break; + } + } + if (!client) { + loggingf("No client associated(%d)\n", fds[conn].fd); + close(fds[conn].fd); continue; } + loggingf("Found client(%lu) [%s] (%d)\n", client->id, client->author, fds[conn].fd); // We received a message, try to parse the header HeaderMessage header; - nrecv = recv(fds[i].fd, &header, sizeof(header), 0); - assert(nrecv != -1); - + s32 nrecv = recv(fds[conn].fd, &header, sizeof(header), 0); if (nrecv == 0) { - disconnect(fdsArena, fds, i, (clients + i)); + disconnectAndNotify(client, fds, FDS_SIZE, conn); + loggingf("Disconnected(%lu) [%s]\n", client->id, client->author); + continue; + } else if (nrecv != sizeof(header)) { + disconnectAndNotify(client, fds, FDS_SIZE, conn); + loggingf("error(%lu) [%s] %d/%lu bytes\n", client->id, client->author, nrecv, sizeof(header)); continue; } - - assert(nrecv == sizeof(header)); - fprintf(stderr, " Received(%d): %d bytes -> " PH_FMT "\n", fds[i].fd, nrecv, PH_ARG(header)); + loggingf("Received(%d) -> " HEADER_FMT "\n", fds[conn].fd, HEADER_ARG(header)); switch (header.type) { - case HEADER_TYPE_TEXT:; - TextMessage* message; - nrecv = recvTextMessage(msgsArena, fds[i].fd, &message); - fprintf(stderr, " Received(%d): %d bytes -> ", fds[i].fd, nrecv); - printTextMessage(message, 0); + case HEADER_TYPE_TEXT: { + TextMessage* text_message = recvTextMessage(&msgsArena, fds[conn].fd); + loggingf("Received(%d)", fds[conn].fd); + printTextMessage(text_message, client, 0); + + sendToOthers(fds, FDS_SIZE, fds[conn].fd, UNIFD, &header, text_message); + } break; + // handle request for information about client id + default: + loggingf("Unhandled '%s' from client(%d)\n", headerTypeString(header.type), fds[conn].fd); + disconnectAndNotify(client, fds, FDS_SIZE, conn); + continue; + } + } - // Send message to all other clients - sendToOthers(fdsArena, fds, i, &header, message); + // Check for messages from clients in their bifd + for (u32 conn = FDS_CLIENTS + BIFD; conn < FDS_SIZE; conn += 2) { + if (!(fds[conn].revents & POLLIN)) continue; + if (fds[conn].fd == -1) continue; + loggingf("Message bifd (%d)\n", fds[conn].fd); + + // Get client associated with connection + Client* client = 0; + for (u32 j = 0; j < CLIENTS_SIZE; j++) { + if (!clients[j].pollbifd) + continue; + if (clients[j].pollbifd == fds + conn) { + client = clients + j; + break; + } + } + if (!client) { + loggingf("No client for connection(%d)\n", fds[conn].fd); +#ifdef IMPORT_ID + client = authenticate(&clientsArena, clients_file, fds + conn - 1); +#else + client = authenticate(&clientsArena, 0, fds + conn - 1); +#endif + // If the client sent an IDMessage but no ID was found authenticate() could return null + if (!client) { + loggingf("Could not initialize client\n"); + disconnect(fds + conn, 0); + } else { // client was added/connected + local_persist HeaderMessage header = HEADER_INIT(HEADER_TYPE_PRESENCE); + PresenceMessage message = {.id = client->id, .type = PRESENCE_TYPE_CONNECTED}; + sendToOthers(fds, FDS_SIZE, fds[conn - BIFD].fd, UNIFD, &header, &message); + } + continue; + } + loggingf("Found client(%lu) [%s] (%d)\n", client->id, client->author, fds[conn].fd); - break; + // We received a message, try to parse the header + HeaderMessage header; + s32 nrecv = recv(fds[conn].fd, &header, sizeof(header), 0); + if (nrecv == 0) { + disconnectAndNotify(client, fds, FDS_SIZE, conn); + loggingf("Disconnected(%lu) [%s]\n", client->id, client->author); + continue; + } else if (nrecv != sizeof(header)) { + disconnectAndNotify(client, fds, FDS_SIZE, conn); + loggingf("error(%lu) [%s] %d/%lu bytes\n", client->id, client->author, nrecv, sizeof(header)); + continue; + } + loggingf("Received(%d) -> " HEADER_FMT "\n", fds[conn].fd, HEADER_ARG(header)); + + switch (header.type) { + case HEADER_TYPE_ID: { + // handle request for information about client id + IDMessage id_message; + nrecv = recv(fds[conn].fd, &id_message, sizeof(id_message), 0); + + Client* client = getClientByID(&clientsArena, id_message.id); + if (!client) { + local_persist HeaderMessage header = HEADER_INIT(HEADER_TYPE_ERROR); + local_persist ErrorMessage error_message = ERROR_INIT(ERROR_TYPE_NOTFOUND); + sendAnyMessage(fds[conn].fd, &header, &error_message); + loggingf("Could not find %lu\n", id_message.id); + break; + } + HeaderMessage header = HEADER_INIT(HEADER_TYPE_INTRODUCTION); + IntroductionMessage introduction_message; + memcpy(introduction_message.author, client->author, AUTHOR_LEN); + + sendAnyMessage(fds[conn].fd, &header, &introduction_message); + } break; default: - fprintf(stdout, " Got unhandled message type '%s' from client %d", headerTypeString(header.type), fds[i].fd); + loggingf("Unhandled '%s' from client(%d)\n", headerTypeString(header.type), fds[conn].fd); + disconnectAndNotify(client, fds, FDS_SIZE, conn); continue; } } } - ArenaRelease(clientsArena); - ArenaRelease(fdsArena); - ArenaRelease(msgsArena); +#ifdef IMPORT_ID + close(clients_file); +#endif return 0; } |