diff --git a/src/server.c b/src/server.c index 84c3c9e..2fc2960 100644 --- a/src/server.c +++ b/src/server.c @@ -1,406 +1,144 @@ -#include +#include #include -#include - +#include +#include #include - -#include "common.h" - -#include "arena.h" -#define NOB_STRIP_PREFIX -#include "nob.h" -#include "cws.h" -#include "coroutine.h" -#define STB_DS_IMPLEMENTATION -#include "stb_ds.h" - -static Arena temp = {0}; - -// Forward declarations ////////////////////////////// - -extern int messages_recieved_within_tick; -extern int bytes_received_within_tick; -extern int message_sent_within_tick; -extern int bytes_sent_within_tick; - -// Connection Limits ////////////////////////////// +#include typedef struct { - Short_String key; // remote address - uint32_t value; // count -} Connection_Limit; - -Connection_Limit *connection_limits = NULL; - -uint32_t *connection_limits_get(Short_String remote_address) -{ - ptrdiff_t i = hmgeti(connection_limits, remote_address); - if (i < 0) return NULL; - return &connection_limits[i].value; -} - -void connection_limits_set(Short_String remote_address, uint count) -{ - hmput(connection_limits, remote_address, count); -} - -void connection_limits_remove(Short_String remote_address) -{ - int deleted = hmdel(connection_limits, remote_address); - UNUSED(deleted); + void **items; + size_t size; + size_t capacity; +} DynamicArray; + +void da_init(DynamicArray *da) { + da->items = NULL; + da->size = 0; + da->capacity = 0; +} + +void da_append(DynamicArray *da, void *item) { + if (da->size >= da->capacity) { + size_t new_capacity = da->capacity == 0 ? 1 : da->capacity * 2; + void **new_items = realloc(da->items, new_capacity * sizeof(*da->items)); + if (!new_items) { + perror("da_append: realloc failed"); + exit(1); + } + da->items = new_items; + da->capacity = new_capacity; + } + da->items[da->size++] = item; } -// Connections ////////////////////////////// - typedef struct { - uint32_t key; - Cws value; -} Connection; - -Connection *connections = NULL; -uint32_t idCounter = 0; + const char *method; + const char *path; +} Request; -void connections_remove(uint32_t player_id) -{ - int deleted = hmdel(connections, player_id); - UNUSED(deleted); -} - -Cws *connections_get_ref(uint32_t player_id) -{ - ptrdiff_t i = hmgeti(connections, player_id); - if (i < 0) return NULL; - return &connections[i].value; -} - -void connections_set(uint32_t player_id, Cws cws) -{ - hmput(connections, player_id, cws); -} - -// Messages ////////////////////////////// - -uint32_t send_message(uint32_t player_id, void *message_raw) -{ - Cws* cws = connections_get_ref(player_id); - if (cws == NULL) { - fprintf(stderr, "ERROR: unknown player id %d\n", player_id); - exit(69); - } - Message* message = message_raw; - int err = cws_send_message(cws, CWS_MESSAGE_BIN, message->bytes, message->byte_length - sizeof(message->byte_length)); - if (err < 0) { - fprintf(stderr, "ERROR: Could not send message to player %d: %s\n", player_id, cws_error_message(cws, (Cws_Error)err)); - exit(69); +typedef struct { + int fd; + DynamicArray connections; +} KoilServer; + +int koil_server_parse_request(const char *buffer, Request *req) { + char method[16]; + char path[256]; + + if (sscanf(buffer, "%15s %255s", method, path) != 2) { + return -1; } - return message->byte_length; -} -void send_message_and_update_stats(uint32_t player_id, void* message) -{ - uint32_t sent = send_message(player_id, message); - if (sent > 0) { - bytes_sent_within_tick += sent; - message_sent_within_tick += 1; - } + req->method = strdup(method); + req->path = strdup(path); + return 0; } -// Cws_Socket ////////////////////////////// - -int cws_socket_read(void *data, void *buffer, size_t len) -{ - while (true) { - int n = read((int)(uintptr_t)data, buffer, len); - if (n > 0) return (int)n; - if (n < 0 && errno != EWOULDBLOCK) return (int)CWS_ERROR_ERRNO; - if (n == 0) return (int)CWS_ERROR_CONNECTION_CLOSED; - coroutine_yield(); +int koil_server_init(KoilServer *server, int port) { + server->fd = socket(AF_INET, SOCK_STREAM, 0); + if (server->fd < 0) { + perror("socket"); + return -1; } -} -// peek: like read, but does not remove data from the buffer -// Usually implemented via MSG_PEEK flag of recv -int cws_socket_peek(void *data, void *buffer, size_t len) -{ - while (true) { - int n = recv((int)(uintptr_t)data, buffer, len, MSG_PEEK); - if (n > 0) return (int)n; - if (n < 0 && errno != EWOULDBLOCK) return (int)CWS_ERROR_ERRNO; - if (n == 0) return (int)CWS_ERROR_CONNECTION_CLOSED; - coroutine_yield(); - } -} + // Tsoding-style: no SO_REUSEADDR to keep it simple + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(port), + .sin_addr.s_addr = htonl(INADDR_ANY), + }; -int cws_socket_write(void *data, const void *buffer, size_t len) -{ - while (true) { - int n = write((int)(uintptr_t)data, buffer, len); - if (n > 0) return (int)n; - if (n < 0 && errno != EWOULDBLOCK) return (int)CWS_ERROR_ERRNO; - if (n == 0) return (int)CWS_ERROR_CONNECTION_CLOSED; - coroutine_yield(); + if (bind(server->fd, (struct sockaddr*)&addr, sizeof(addr)) { + perror("bind"); + close(server->fd); + return -1; } -} -int cws_socket_shutdown(void *data, Cws_Shutdown_How how) -{ - if (shutdown((int)(uintptr_t)data, (int)how) < 0) return (int)CWS_ERROR_ERRNO; - return 0; -} + if (listen(server->fd, 5)) { + perror("listen"); + close(server->fd); + return -1; + } -int cws_socket_close(void *data) -{ - if (close((int)(uintptr_t)data) < 0) return (int)CWS_ERROR_ERRNO; + da_init(&server->connections); return 0; } -Cws_Socket cws_socket_from_fd(int fd) -{ - return (Cws_Socket) { - .data = (void*)(uintptr_t)fd, - .read = cws_socket_read, - .peek = cws_socket_peek, - .write = cws_socket_write, - .shutdown = cws_socket_shutdown, - .close = cws_socket_close, - }; -} - -// Stats ////////////////////////////// - -#define AVERAGE_CAPACITY 30 - -typedef struct { - float items[AVERAGE_CAPACITY]; - size_t begin; - size_t count; -} Stat_Samples; - -// TODO: consider contributing the Ring Buffer operations to nob - -#define rb_capacity(rb) (sizeof((rb)->items)/sizeof((rb)->items[0])) - -#define rb_at(rb, i) \ - (rb)->items[ \ - ( \ - assert((i) < (rb)->count), \ - ((rb)->begin + (i))%rb_capacity(rb) \ - ) \ - ] - -#define rb_push(rb, sample) \ - do { \ - (rb)->items[((rb)->begin + (rb)->count)%rb_capacity(rb)] = (sample); \ - if ((rb)->count < rb_capacity(rb)) { \ - (rb)->count += 1; \ - } else { \ - (rb)->begin = ((rb)->begin + 1)%rb_capacity(rb); \ - } \ - } while (0) - -typedef enum { - SK_COUNTER, - SK_AVERAGE, - SK_TIMER, -} Stat_Kind; - -typedef struct { - int value; -} Stat_Counter; - -typedef struct { - Stat_Samples samples; -} Stat_Average; - -typedef struct { - uint started_at; -} Stat_Timer; - -typedef struct { - Stat_Kind kind; - const char *description; - - union { - Stat_Counter counter; - Stat_Average average; - Stat_Timer timer; - }; -} Stat; - -typedef enum { - SE_UPTIME = 0, - SE_TICKS_COUNT, - SE_TICK_TIMES, - SE_MESSAGES_SENT, - SE_MESSAGES_RECEIVED, - SE_TICK_MESSAGES_SENT, - SE_TICK_MESSAGES_RECEIVED, - SE_BYTES_SENT, - SE_BYTES_RECEIVED, - SE_TICK_BYTE_SENT, - SE_TICK_BYTE_RECEIVED, - SE_PLAYERS_CURRENTLY, - SE_PLAYERS_JOINED, - SE_PLAYERS_LEFT, - SE_BOGUS_AMOGUS_MESSAGES, - SE_PLAYERS_REJECTED, - NUMBER_OF_STAT_ENTRIES, -} Stat_Entry; - -static_assert(NUMBER_OF_STAT_ENTRIES == 16, "Number of Stat Enties has changed"); -static Stat stats[NUMBER_OF_STAT_ENTRIES] = { - [SE_UPTIME] = { - .kind = SK_TIMER, - .description = "Uptime" - }, - [SE_TICKS_COUNT] = { - .kind = SK_COUNTER, - .description = "Ticks count" - }, - [SE_TICK_TIMES] = { - .kind = SK_AVERAGE, - .description = "Average time to process a tick" - }, - [SE_MESSAGES_SENT] = { - .kind = SK_COUNTER, - .description = "Total messages sent" - }, - [SE_MESSAGES_RECEIVED] = { - .kind = SK_COUNTER, - .description = "Total messages received" - }, - [SE_TICK_MESSAGES_SENT] = { - .kind = SK_AVERAGE, - .description = "Average messages sent per tick" - }, - [SE_TICK_MESSAGES_RECEIVED] = { - .kind = SK_AVERAGE, - .description = "Average messages received per tick" - }, - [SE_BYTES_SENT] = { - .kind = SK_COUNTER, - .description = "Total bytes sent" - }, - [SE_BYTES_RECEIVED] = { - .kind = SK_COUNTER, - .description = "Total bytes received" - }, - [SE_TICK_BYTE_SENT] = { - .kind = SK_AVERAGE, - .description = "Average bytes sent per tick" - }, - [SE_TICK_BYTE_RECEIVED] = { - .kind = SK_AVERAGE, - .description = "Average bytes received per tick" - }, - [SE_PLAYERS_CURRENTLY] = { - .kind = SK_COUNTER, - .description = "Currently players" - }, - [SE_PLAYERS_JOINED] = { - .kind = SK_COUNTER, - .description = "Total players joined" - }, - [SE_PLAYERS_LEFT] = { - .kind = SK_COUNTER, - .description = "Total players left" - }, - [SE_BOGUS_AMOGUS_MESSAGES] = { - .kind = SK_COUNTER, - .description = "Total bogus-amogus messages" - }, - [SE_PLAYERS_REJECTED] = { - .kind = SK_COUNTER, - .description = "Total players rejected" - }, -}; - -static float stat_samples_average(Stat_Samples self) -{ - float sum = 0; - if (self.count == 0) return sum; - for (size_t i = 0; i < self.count; ++i) { - sum += rb_at(&self, i); +void koil_server_free(KoilServer *server) { + for (size_t i = 0; i < server->connections.size; ++i) { + close((int)(intptr_t)server->connections.items[i]); } - return sum/self.count; -} - -static const char *plural_number(int num, const char *singular, const char *plural) -{ - return num == 1 ? singular : plural; + free(server->connections.items); + close(server->fd); } -static const char *display_time_interval(uint32_t diff_msecs) -{ - const char* result[4]; - size_t result_count = 0; - - uint days = diff_msecs/1000/60/60/24; - if (days > 0) result[result_count++] = arena_sprintf(&temp, "%d %s", days, plural_number(days, "day", "days")); - uint hours = diff_msecs/1000/60/60%24; - if (hours > 0) result[result_count++] = arena_sprintf(&temp, "%d %s", hours, plural_number(hours, "hour", "hours")); - uint mins = diff_msecs/1000/60%60; - if (mins > 0) result[result_count++] = arena_sprintf(&temp, "%d %s", mins, plural_number(mins, "min", "mins")); - uint secs = diff_msecs/1000%60; - if (secs > 0) result[result_count++] = arena_sprintf(&temp, "%d %s", secs, plural_number(secs, "sec", "secs")); - - if (result_count == 0) return "0 secs"; - - String_Builder sb = {0}; - for (size_t i = 0; i < result_count; ++i) { - if (i > 0) arena_da_append(&temp, &sb, ' '); - arena_sb_append_cstr(&temp, &sb, result[i]); +void koil_server_handle_connection(int client_fd) { + char buffer[4096]; + ssize_t bytes_read = recv(client_fd, buffer, sizeof(buffer), 0); + + if (bytes_read <= 0) { + close(client_fd); + return; } - arena_sb_append_null(&temp, &sb); - return sb.items; -} -static const char *stat_display(Stat stat, uint32_t now_msecs) -{ - switch (stat.kind) { - case SK_COUNTER: return arena_sprintf(&temp, "%d", stat.counter.value); - case SK_AVERAGE: return arena_sprintf(&temp, "%f", stat_samples_average(stat.average.samples)); - case SK_TIMER: return display_time_interval(now_msecs - stat.timer.started_at); - default: UNREACHABLE("stat_display"); + Request req = {0}; + if (koil_server_parse_request(buffer, &req) == 0) { + const char *response = + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Connection: close\r\n" + "\r\n" + "Hello from Koil!"; + send(client_fd, response, strlen(response), 0); + free((void*)req.method); + free((void*)req.path); + } else { + const char *response = "HTTP/1.1 400 Bad Request\r\n\r\n"; + send(client_fd, response, strlen(response), 0); } + close(client_fd); } -void stat_push_sample(Stat_Entry entry, float sample) -{ - assert(entry < NUMBER_OF_STAT_ENTRIES); - Stat *stat = &stats[entry]; - assert(stat->kind == SK_AVERAGE); - rb_push(&stat->average.samples, sample); -} - -void stat_inc_counter(Stat_Entry entry, int delta) -{ - assert(entry < NUMBER_OF_STAT_ENTRIES); - Stat *stat = &stats[entry]; - assert(stat->kind == SK_COUNTER); - stat->counter.value += delta; -} +void koil_server_run(KoilServer *server) { + for (;;) { + int client_fd = accept(server->fd, NULL, NULL); + if (client_fd < 0) { + perror("accept"); + continue; + } -void stat_start_timer_at(Stat_Entry entry, uint32_t msecs) -{ - assert(entry < NUMBER_OF_STAT_ENTRIES); - Stat *stat = &stats[entry]; - assert(stat->kind == SK_TIMER); - stat->timer.started_at = msecs; + da_append(&server->connections, (void*)(intptr_t)client_fd); + koil_server_handle_connection(client_fd); + } } -void stat_print_per_n_ticks(int n, uint32_t now_msecs) -{ - if (stats[SE_TICKS_COUNT].counter.value%n == 0) { - printf("Stats:\n"); - for (size_t i = 0; i < ARRAY_LEN(stats); ++i) { - printf(" %s %s\n", stats[i].description, stat_display(stats[i], now_msecs)); - } - fflush(stdout); +int main(void) { + KoilServer server = {0}; + if (koil_server_init(&server, 8080) != 0) { + return 1; } -} -int messages_recieved_within_tick = 0; -int bytes_received_within_tick = 0; -int message_sent_within_tick = 0; -int bytes_sent_within_tick = 0; + koil_server_run(&server); + koil_server_free(&server); + return 0; +}