Program Listing for File client_process_buffer.c

Return to documentation for file (server/client/client_process_buffer.c)

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <unistd.h>

#include "utils/common_macros.h"
#include "client.h"
#include "event.h"
#include "game_events/names.h"
#include "ring_buffer.h"
#include "server.h"

static constexpr const size_t MAX_GUI_CMD_LEN = 4;

struct ai_lut_entry {
    char *command;
    uint64_t time_needed;
};

struct gui_lut_entry {
    char command[MAX_GUI_CMD_LEN];
};

static const struct ai_lut_entry AI_LUT[] = {
    {"Broadcast", 7},
    {"Connect_nbr", 0},
    {"Eject", 7},
    {"Fork", 42},
    {"Forward", 7},
    {"Incantation", 0},
    {PLAYER_END_INCANTATION, 300}, // Internal use only
    {"Inventory", 1},
    {"Left", 7},
    {"Look", 7},
    {"Right", 7},
    {"Set", 7},
    {"Take", 7},
};

static const struct gui_lut_entry GUI_LUT[] = {
    {"msz"},
    {"bct"},
    {"mct"},
    {"tna"},
    {"ppo"},
    {"plv"},
    {"pin"},
    {"sgt"},
    {"sst"},
};

static constexpr const size_t AI_LUT_SIZE = LENGTH_OF(AI_LUT);
static constexpr const size_t GUI_LUT_SIZE = LENGTH_OF(GUI_LUT);

static
bool is_in_ai_lut(const char *command)
{
    for (size_t i = 0; i < AI_LUT_SIZE; i++) {
        if (strcmp(AI_LUT[i].command, command) == 0)
            return true;
    }
    return false;
}

static
uint64_t get_late_event(server_t *srv, client_state_t *client, event_t *event)
{
    uint64_t late_event = get_timestamp();
    int idx = client - srv->cm.clients;
    int counter = 0;

    for (size_t i = 0; i < srv->events.nmemb; i++) {
        if (srv->events.buff[i].client_idx == idx
            && is_in_ai_lut(srv->events.buff[i].command[0])
            && srv->events.buff[i].timestamp > late_event) {
            late_event = srv->events.buff[i].timestamp;
            counter++;
        }
    }
    if (counter >= MAX_CONCURRENT_REQUESTS)
        event->command[0] = "ko";
    return late_event;
}

static
void event_create(server_t *srv, client_state_t *client,
    char *split[static COMMAND_WORD_COUNT], uint64_t time_needed)
{
    uint64_t interval = (time_needed * MICROSEC_IN_SEC) / srv->frequency;
    event_t event = {
        .client_idx = client - srv->cm.clients, .client_id = client->id
    };

    memcpy(event.command, split, sizeof(event.command));
    for (event.arg_count = 0; event.arg_count < COMMAND_WORD_COUNT
        && event.command[event.arg_count] != nullptr; event.arg_count++);
    if (client->team_id != TEAM_ID_GRAPHIC)
        event.timestamp = get_late_event(srv, client, &event) + interval;
    else
        event.timestamp = get_timestamp();
    DEBUG("Creating event for client %d: '%s' in %lu ms",
        client->fd, event.command[0],
        (event.timestamp - get_timestamp()) / MILISEC_IN_SEC);
    if (!event_heap_push(&srv->events, &event))
        srv->is_running = false;
    if (!strcmp(split[0], PLAYER_FORK))
        send_to_guis(srv, "pfk #%hu\n", client->id);
}

static
void unknown_command(server_t *srv, client_state_t *client,
    const char *command DEBUG_USED)
{
    size_t idx = client - srv->cm.clients;
    event_t event = {
        .client_idx = idx,
        .command = {client->team_id == TEAM_ID_GRAPHIC ? "suc" : "ko"},
        .client_id = client->id
    };

    if (client->team_id != TEAM_ID_GRAPHIC)
        event.timestamp = get_late_event(srv, client, &event);
    else
        event.timestamp = get_timestamp();
    DEBUG("Unknown command '%s' from client %d", command, client->fd);
    if (!event_heap_push(&srv->events, &event)) {
        srv->is_running = false;
        return;
    }
}

static
void handle_command(server_t *srv, client_state_t *client,
    char *split[static COMMAND_WORD_COUNT])
{
    if (client->team_id == TEAM_ID_UNASSIGNED) {
        if (!handle_team(srv, client, split))
            append_to_output(srv, client, "ko\n");
        return;
    }
    for (size_t i = 0; i < AI_LUT_SIZE
        && client->team_id != TEAM_ID_GRAPHIC; i++) {
        if (strcmp(AI_LUT[i].command, split[0]) == 0) {
            event_create(srv, client, split, AI_LUT[i].time_needed);
            return;
        }
    }
    for (size_t i = 0; i < GUI_LUT_SIZE
        && client->team_id == TEAM_ID_GRAPHIC; i++) {
        if (strcmp(GUI_LUT[i].command, split[0]) == 0) {
            event_create(srv, client, split, 0);
            return;
        }
    }
    unknown_command(srv, client, split[0]);
}

static
void process_sub_command(server_t *srv, client_state_t *client)
{
    size_t command_len = 0;
    char *split[COMMAND_WORD_COUNT] = {nullptr};

    for (; client->in_buff_idx < client->input.nmemb;) {
        command_len = strcspn(client->input.buff + client->in_buff_idx, "\n");
        (client->input.buff + client->in_buff_idx)[command_len] = '\0';
        command_split(
            client->input.buff + client->in_buff_idx,
            split,
            command_len
        );
        client->in_buff_idx += command_len + 1;
        handle_command(srv, client, split);
    }
}

void process_clients_buff(server_t *srv)
{
    client_state_t *client = nullptr;

    for (size_t i = 1; i < srv->cm.count; i++) {
        client = srv->cm.clients + i;
        if (client->input.buff == nullptr)
            continue;
        if (!(srv->cm.server_pfds[i].revents & POLLIN)
            || client->in_buff_idx >= client->input.nmemb)
            continue;
        process_sub_command(srv, client);
    }
}