Files
maze32/components/maze32_lobby/maze32_protocol.c
T

299 lines
9.9 KiB
C

#include "maze32_protocol.h"
#include <limits.h>
#include <string.h>
typedef struct {
const char* data;
size_t length;
size_t position;
} json_cursor_t;
static void skip_space(json_cursor_t* cursor) {
while (cursor->position < cursor->length) {
char character = cursor->data[cursor->position];
if (character != ' ' && character != '\t' && character != '\r' && character != '\n') {
return;
}
cursor->position++;
}
}
static int consume(json_cursor_t* cursor, char expected) {
skip_space(cursor);
if (cursor->position >= cursor->length || cursor->data[cursor->position] != expected) {
return 0;
}
cursor->position++;
return 1;
}
static int parse_string(json_cursor_t* cursor, char* output, size_t capacity) {
skip_space(cursor);
if (cursor->position >= cursor->length || cursor->data[cursor->position++] != '"') {
return 0;
}
size_t output_length = 0U;
while (cursor->position < cursor->length) {
unsigned char character = (unsigned char)cursor->data[cursor->position++];
if (character == '"') {
if (output_length >= capacity) {
return 0;
}
output[output_length] = '\0';
return 1;
}
if (character < 0x20U) {
return 0;
}
if (character == '\\') {
if (cursor->position >= cursor->length) {
return 0;
}
character = (unsigned char)cursor->data[cursor->position++];
if (character == '"' || character == '\\' || character == '/') {
/* The escaped byte is copied below. */
} else if (character == 'b') {
character = '\b';
} else if (character == 'f') {
character = '\f';
} else if (character == 'n') {
character = '\n';
} else if (character == 'r') {
character = '\r';
} else if (character == 't') {
character = '\t';
} else {
return 0;
}
}
if (output_length + 1U >= capacity) {
return 0;
}
output[output_length++] = (char)character;
}
return 0;
}
static int parse_uint32(json_cursor_t* cursor, uint32_t* value) {
skip_space(cursor);
if (cursor->position >= cursor->length || cursor->data[cursor->position] < '0' ||
cursor->data[cursor->position] > '9') {
return 0;
}
uint64_t parsed = 0U;
while (cursor->position < cursor->length && cursor->data[cursor->position] >= '0' &&
cursor->data[cursor->position] <= '9') {
parsed = parsed * 10U + (uint64_t)(cursor->data[cursor->position] - '0');
if (parsed > UINT32_MAX) {
return 0;
}
cursor->position++;
}
*value = (uint32_t)parsed;
return 1;
}
static int payload_field_allowed(maze32_message_type_t type, const char* key, char* value,
maze32_client_message_t* message, unsigned int* fields) {
if (type == MAZE32_MESSAGE_JOIN && strcmp(key, "role") == 0 && !(*fields & 1U)) {
if (strcmp(value, "player") == 0) {
message->role = MAZE32_ROLE_PLAYER;
} else if (strcmp(value, "observer") == 0) {
message->role = MAZE32_ROLE_OBSERVER;
} else {
return 0;
}
*fields |= 1U;
return 1;
}
if (type == MAZE32_MESSAGE_JOIN && strcmp(key, "name") == 0 && !(*fields & 2U)) {
if (strlen(value) > MAZE32_PLAYER_NAME_MAX_BYTES) {
return 0;
}
memcpy(message->name, value, strlen(value) + 1U);
*fields |= 2U;
return 1;
}
if (type == MAZE32_MESSAGE_RESTORE && strcmp(key, "token") == 0 && !(*fields & 1U)) {
if (strlen(value) != MAZE32_SESSION_TOKEN_HEX_LENGTH) {
return 0;
}
memcpy(message->token, value, MAZE32_SESSION_TOKEN_HEX_LENGTH + 1U);
*fields |= 1U;
return 1;
}
if (type == MAZE32_MESSAGE_MOVE && strcmp(key, "direction") == 0 && !(*fields & 1U)) {
if (strcmp(value, "north") != 0 && strcmp(value, "east") != 0 &&
strcmp(value, "south") != 0 && strcmp(value, "west") != 0) {
return 0;
}
memcpy(message->direction, value, strlen(value) + 1U);
*fields |= 1U;
return 1;
}
return 0;
}
static int parse_payload(json_cursor_t* cursor, maze32_client_message_t* message) {
if (!consume(cursor, '{')) {
return 0;
}
unsigned int fields = 0U;
skip_space(cursor);
if (cursor->position < cursor->length && cursor->data[cursor->position] == '}') {
cursor->position++;
} else {
for (;;) {
char key[16];
char value[MAZE32_PLAYER_NAME_MAX_BYTES + 1U];
if (!parse_string(cursor, key, sizeof(key)) || !consume(cursor, ':') ||
!parse_string(cursor, value, sizeof(value)) ||
!payload_field_allowed(message->type, key, value, message, &fields)) {
return 0;
}
skip_space(cursor);
if (consume(cursor, '}')) {
break;
}
if (!consume(cursor, ',')) {
return 0;
}
}
}
if (message->type == MAZE32_MESSAGE_JOIN) {
return message->role == MAZE32_ROLE_PLAYER ? fields == 3U : fields == 1U;
}
if (message->type == MAZE32_MESSAGE_RESTORE) {
return fields == 1U;
}
if (message->type == MAZE32_MESSAGE_MOVE) {
return fields == 1U;
}
return fields == 0U;
}
static maze32_message_type_t message_type(const char* value) {
if (strcmp(value, "join") == 0) {
return MAZE32_MESSAGE_JOIN;
}
if (strcmp(value, "restore") == 0) {
return MAZE32_MESSAGE_RESTORE;
}
if (strcmp(value, "start") == 0) {
return MAZE32_MESSAGE_START;
}
if (strcmp(value, "move") == 0) {
return MAZE32_MESSAGE_MOVE;
}
if (strcmp(value, "drop_key") == 0) {
return MAZE32_MESSAGE_DROP_KEY;
}
if (strcmp(value, "heartbeat") == 0) {
return MAZE32_MESSAGE_HEARTBEAT;
}
if (strcmp(value, "suggest_name") == 0) {
return MAZE32_MESSAGE_SUGGEST_NAME;
}
return MAZE32_MESSAGE_INVALID;
}
maze32_protocol_result_t maze32_protocol_parse(const char* data, size_t length,
maze32_client_message_t* message) {
if (data == NULL || message == NULL || length == 0U) {
return MAZE32_PROTOCOL_MALFORMED;
}
if (length > MAZE32_PROTOCOL_MESSAGE_LIMIT) {
return MAZE32_PROTOCOL_TOO_LARGE;
}
memset(message, 0, sizeof(*message));
json_cursor_t cursor = {.data = data, .length = length, .position = 0U};
if (!consume(&cursor, '{')) {
return MAZE32_PROTOCOL_MALFORMED;
}
unsigned int envelope_fields = 0U;
uint32_t version = 0U;
char type_value[24] = "";
for (;;) {
char key[24];
if (!parse_string(&cursor, key, sizeof(key)) || !consume(&cursor, ':')) {
return MAZE32_PROTOCOL_MALFORMED;
}
if (strcmp(key, "v") == 0 && !(envelope_fields & 1U)) {
if (!parse_uint32(&cursor, &version)) {
return MAZE32_PROTOCOL_MALFORMED;
}
envelope_fields |= 1U;
} else if (strcmp(key, "type") == 0 && !(envelope_fields & 2U)) {
if (!parse_string(&cursor, type_value, sizeof(type_value))) {
return MAZE32_PROTOCOL_MALFORMED;
}
message->type = message_type(type_value);
envelope_fields |= 2U;
} else if (strcmp(key, "seq") == 0 && !(envelope_fields & 4U)) {
if (!parse_uint32(&cursor, &message->sequence) || message->sequence == 0U) {
return MAZE32_PROTOCOL_MALFORMED;
}
envelope_fields |= 4U;
} else if (strcmp(key, "requestId") == 0 && !(envelope_fields & 8U)) {
if (!parse_string(&cursor, message->request_id, sizeof(message->request_id))) {
return MAZE32_PROTOCOL_MALFORMED;
}
envelope_fields |= 8U;
} else if (strcmp(key, "payload") == 0 && !(envelope_fields & 16U)) {
if (!(envelope_fields & 2U)) {
return MAZE32_PROTOCOL_MALFORMED;
}
if (message->type == MAZE32_MESSAGE_INVALID) {
return MAZE32_PROTOCOL_UNKNOWN_TYPE;
}
if (!parse_payload(&cursor, message)) {
return MAZE32_PROTOCOL_INVALID_PAYLOAD;
}
envelope_fields |= 16U;
} else {
return MAZE32_PROTOCOL_MALFORMED;
}
skip_space(&cursor);
if (consume(&cursor, '}')) {
break;
}
if (!consume(&cursor, ',')) {
return MAZE32_PROTOCOL_MALFORMED;
}
}
skip_space(&cursor);
if (cursor.position != cursor.length ||
(envelope_fields & (1U | 2U | 4U | 16U)) != (1U | 2U | 4U | 16U)) {
return MAZE32_PROTOCOL_MALFORMED;
}
if (version != MAZE32_PROTOCOL_VERSION) {
return MAZE32_PROTOCOL_UNSUPPORTED_VERSION;
}
if (message->type == MAZE32_MESSAGE_INVALID) {
return MAZE32_PROTOCOL_UNKNOWN_TYPE;
}
return MAZE32_PROTOCOL_OK;
}
const char* maze32_protocol_result_name(maze32_protocol_result_t result) {
switch (result) {
case MAZE32_PROTOCOL_OK:
return "ok";
case MAZE32_PROTOCOL_MALFORMED:
return "malformed";
case MAZE32_PROTOCOL_TOO_LARGE:
return "too_large";
case MAZE32_PROTOCOL_UNSUPPORTED_VERSION:
return "unsupported_version";
case MAZE32_PROTOCOL_UNKNOWN_TYPE:
return "unknown_type";
case MAZE32_PROTOCOL_INVALID_PAYLOAD:
return "invalid_payload";
default:
return "unknown_error";
}
}