112 lines
5.5 KiB
C
112 lines
5.5 KiB
C
#include "session_manager.h"
|
|
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
static bool valid_utf8_scalar(const unsigned char *text, size_t remaining, size_t *bytes) {
|
|
if (text[0] < 0x80U) { *bytes = 1; return text[0] >= 0x20U && text[0] != '<' && text[0] != '>'; }
|
|
if (text[0] >= 0xC2U && text[0] <= 0xDFU && remaining >= 2 && (text[1] & 0xC0U) == 0x80U) { *bytes = 2; return true; }
|
|
if (text[0] >= 0xE0U && text[0] <= 0xEFU && remaining >= 3 && (text[1] & 0xC0U) == 0x80U && (text[2] & 0xC0U) == 0x80U) { *bytes = 3; return true; }
|
|
if (text[0] >= 0xF0U && text[0] <= 0xF4U && remaining >= 4 && (text[1] & 0xC0U) == 0x80U && (text[2] & 0xC0U) == 0x80U && (text[3] & 0xC0U) == 0x80U) { *bytes = 4; return true; }
|
|
return false;
|
|
}
|
|
|
|
static bool sanitize_name(const char *name, char output[kDisplayNameBytes + 1]) {
|
|
if (name == NULL) return false;
|
|
size_t input = 0;
|
|
while (name[input] == ' ') ++input;
|
|
const size_t start = input;
|
|
size_t end = start;
|
|
while (name[end] != '\0') ++end;
|
|
while (end > start && name[end - 1U] == ' ') --end;
|
|
size_t written = 0;
|
|
uint8_t scalars = 0;
|
|
while (input < end) {
|
|
size_t bytes = 0;
|
|
if (!valid_utf8_scalar((const unsigned char *)&name[input], end - input, &bytes) || name[input] == '"' || name[input] == '\\') return false;
|
|
if (written + bytes > kDisplayNameBytes || ++scalars > 20U) return false;
|
|
memcpy(&output[written], &name[input], bytes);
|
|
input += bytes;
|
|
written += bytes;
|
|
}
|
|
output[written] = '\0';
|
|
return scalars != 0U;
|
|
}
|
|
|
|
static int8_t first_free(const session_manager_t *manager, uint8_t start, uint8_t end) {
|
|
for (uint8_t index = start; index < end; ++index) if (!manager->entries[index].occupied) return (int8_t)index;
|
|
return -1;
|
|
}
|
|
|
|
void session_manager_init(session_manager_t *manager) {
|
|
if (manager != NULL) memset(manager, 0, sizeof(*manager));
|
|
}
|
|
|
|
session_result_t session_manager_join(session_manager_t *manager, role_t requested_role, const char *name,
|
|
random_source_t random, uint8_t *session_index) {
|
|
if (manager == NULL || session_index == NULL || random.next_u32 == NULL) return SESSION_RESULT_UNAUTHORIZED;
|
|
char sanitized[kDisplayNameBytes + 1] = {0};
|
|
if (!sanitize_name(name, sanitized)) return SESSION_RESULT_INVALID_NAME;
|
|
int8_t selected = -1;
|
|
if (requested_role == ROLE_AUTO_PLAYER) selected = first_free(manager, 0, kPlayerCapacity);
|
|
else if (requested_role == ROLE_PLAYER_1) selected = first_free(manager, 0, 1);
|
|
else if (requested_role == ROLE_PLAYER_2) selected = first_free(manager, 1, 2);
|
|
else if (requested_role == ROLE_SPECTATOR) selected = first_free(manager, kPlayerCapacity, kSessionCapacity);
|
|
else return SESSION_RESULT_INVALID_ROLE;
|
|
if (selected < 0) return requested_role == ROLE_SPECTATOR ? SESSION_RESULT_NO_SPECTATOR_SLOT : SESSION_RESULT_NO_PLAYER_SLOT;
|
|
if (requested_role == ROLE_AUTO_PLAYER) requested_role = (role_t)selected;
|
|
session_t candidate = {.role = requested_role, .occupied = true, .connected = true};
|
|
memcpy(candidate.name, sanitized, sizeof(candidate.name));
|
|
for (uint8_t offset = 0; offset < kSessionTokenBytes; offset += 4) {
|
|
const uint32_t value = random.next_u32(random.context);
|
|
for (uint8_t byte = 0; byte < 4; ++byte) candidate.token[offset + byte] = (uint8_t)(value >> (byte * 8U));
|
|
}
|
|
manager->entries[selected] = candidate;
|
|
*session_index = (uint8_t)selected;
|
|
return SESSION_RESULT_OK;
|
|
}
|
|
|
|
session_result_t session_manager_resume(session_manager_t *manager, const uint8_t token[kSessionTokenBytes], uint8_t *session_index) {
|
|
if (manager == NULL || token == NULL || session_index == NULL) return SESSION_RESULT_UNAUTHORIZED;
|
|
for (uint8_t index = 0; index < kSessionCapacity; ++index) {
|
|
session_t *session = &manager->entries[index];
|
|
if (session->occupied && memcmp(session->token, token, kSessionTokenBytes) == 0) {
|
|
session->connected = true;
|
|
*session_index = index;
|
|
return SESSION_RESULT_OK;
|
|
}
|
|
}
|
|
return SESSION_RESULT_UNAUTHORIZED;
|
|
}
|
|
|
|
bool session_manager_find(const session_manager_t *manager, const uint8_t token[kSessionTokenBytes],
|
|
uint8_t *session_index) {
|
|
if (manager == NULL || token == NULL || session_index == NULL) return false;
|
|
for (uint8_t index = 0; index < kSessionCapacity; ++index) {
|
|
const session_t *session = &manager->entries[index];
|
|
if (session->occupied && memcmp(session->token, token, kSessionTokenBytes) == 0) {
|
|
*session_index = index;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool session_manager_disconnect(session_manager_t *manager, uint8_t session_index) {
|
|
if (manager == NULL || session_index >= kSessionCapacity || !manager->entries[session_index].occupied) return false;
|
|
if (manager->entries[session_index].role == ROLE_SPECTATOR) memset(&manager->entries[session_index], 0, sizeof(session_t));
|
|
else manager->entries[session_index].connected = false;
|
|
return true;
|
|
}
|
|
|
|
session_result_t session_manager_leave(session_manager_t *manager, uint8_t session_index, phase_t phase) {
|
|
if (manager == NULL || session_index >= kSessionCapacity || !manager->entries[session_index].occupied) return SESSION_RESULT_UNAUTHORIZED;
|
|
if (manager->entries[session_index].role != ROLE_SPECTATOR && phase != PHASE_LOBBY) return SESSION_RESULT_WRONG_PHASE;
|
|
memset(&manager->entries[session_index], 0, sizeof(session_t));
|
|
return SESSION_RESULT_OK;
|
|
}
|
|
|
|
bool session_manager_player_present(const session_manager_t *manager, uint8_t player_index) {
|
|
return manager != NULL && player_index < kPlayerCapacity && manager->entries[player_index].occupied;
|
|
}
|