Files
segatools/common/y3io/impl/websockets/y3io.c

311 lines
10 KiB
C

#include <stdint.h>
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include "3rdparty/websockets/websocket.h"
#include "3rdparty/cjson/cJSON.h"
#include "util/dprintf.h"
#include "y3io/y3.h"
DWORD __stdcall y3_websocket_thread_proc(LPVOID ctx);
// TODO: make these settings configurable
#define Y3WS_DEBUG true
#define PORT 3497
#define WS_PATH "/y3io"
#define PROTOCOL_VERSION 1
static bool is_initialized = false;
static SOCKET ws_socket = 0;
static HANDLE server_thread = INVALID_HANDLE_VALUE;
static struct CardInfo card_info[32];
static int card_info_size = 0;
static CRITICAL_SECTION card_info_lock;
uint16_t y3_io_get_api_version() {
return 0x0100;
}
HRESULT y3_io_init() {
if (is_initialized) {
return S_FALSE;
}
memset(card_info, 0, sizeof(card_info));
InitializeCriticalSection(&card_info_lock);
ws_socket = socket(AF_INET, SOCK_STREAM, 0);
if (ws_socket == -1) {
dprintf("Y3WS: Failed to create socket\n");
return E_FAIL;
}
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_port = htons(PORT);
if (bind(ws_socket, (struct sockaddr *) &local, sizeof(local)) == -1) {
dprintf("Y3WS: Failed to bind socket\n");
return E_FAIL;
}
if (listen(ws_socket, 1) == -1) {
dprintf("Y3WS: Failed to listen on socket\n");
return E_FAIL;
}
server_thread = CreateThread(NULL, 0, y3_websocket_thread_proc, NULL, 0, NULL);
is_initialized = true;
dprintf("Y3WS: Started server on port %d\n", PORT);
return S_OK;
}
HRESULT y3_io_close() {
dprintf("Y3WS: Exiting\n");
if (ws_socket != 0) {
closesocket(ws_socket);
ws_socket = 0;
}
if (server_thread != NULL) {
WaitForSingleObject(server_thread, INFINITE);
server_thread = INVALID_HANDLE_VALUE;
}
is_initialized = false;
return S_OK;
}
int safeSend(SOCKET client, const uint8_t* buffer, size_t bufferSize) {
ssize_t written = send(client, (char *) buffer, (int) bufferSize, 0);
if (written == -1) {
closesocket(client);
dprintf("Y3WS: send failed");
return EXIT_FAILURE;
}
if (written != bufferSize) {
closesocket(client);
perror("Y3WS: connection interrupted");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
static void y3ws_make_error_packet(const char* message, uint8_t* output_data, size_t* output_size) {
dprintf("Y3WS: Error: %s\n", message);
*output_size = sprintf_s((char*)output_data, *output_size, "{\"version\":\"%d\", \"success\":false,\"error\":\"%s\"}", PROTOCOL_VERSION, message);
}
static void y3ws_make_success_packet(uint8_t* output_data, size_t* output_size) {
*output_size = sprintf_s((char*)output_data, *output_size, "{\"version\":\"%d\", \"success\":true}", PROTOCOL_VERSION);
}
static void y3ws_handle(const char* input_data, uint32_t input_length, uint8_t* output_data, size_t* output_size) {
#if Y3WS_DEBUG
dprintf("Y3WS: Message was: %s\n", input_data);
#endif
cJSON* json = cJSON_ParseWithLength(input_data, input_length);
if (json == NULL) {
const char *error_ptr = cJSON_GetErrorPtr();
dprintf("Y3WS: Invalid JSON received!\n");
dprintf("Y3WS: Message was: %s\n", input_data);
dprintf("Y3WS: Error at: %s\n", error_ptr);
}
const cJSON* ver = cJSON_GetObjectItemCaseSensitive(json, "version");
const cJSON* cmd = cJSON_GetObjectItemCaseSensitive(json, "command");
if (!cJSON_IsNumber(ver) || ver->valueint <= 0) {
y3ws_make_error_packet("Missing version attribute", output_data, output_size);
goto end;
}
if (ver->valueint != PROTOCOL_VERSION) {
y3ws_make_error_packet("Incompatible version", output_data, output_size);
goto end;
}
if (strcmp(cmd->valuestring, "get_game_id") == 0) {
// TODO: send current game id (SDGY, SDDD)
} else if (strcmp(cmd->valuestring, "get_cards") == 0) {
// TODO: get array of {image_filename, card_id}
} else if (strcmp(cmd->valuestring, "set_field") == 0) {
const cJSON* cards = cJSON_GetObjectItemCaseSensitive(json, "cards");
const cJSON* card = NULL;
card_info_size = 0;
EnterCriticalSection(&card_info_lock);
memset(card_info, 0, sizeof(card_info));
cJSON_ArrayForEach(card, cards) {
cJSON* x = cJSON_GetObjectItemCaseSensitive(card, "x");
cJSON* y = cJSON_GetObjectItemCaseSensitive(card, "y");
cJSON* r = cJSON_GetObjectItemCaseSensitive(card, "rotation");
cJSON* id = cJSON_GetObjectItemCaseSensitive(card, "id");
if (cJSON_IsNumber(x) && cJSON_IsNumber(y) && cJSON_IsNumber(r) && cJSON_IsNumber(id)) {
card_info[card_info_size].eCardStatus = VALID;
card_info[card_info_size].fX = (float)x->valuedouble;
card_info[card_info_size].fY = (float)y->valuedouble;
card_info[card_info_size].fAngle = (float)r->valuedouble;
card_info[card_info_size].uID = r->valueint;
}
card_info_size++;
}
LeaveCriticalSection(&card_info_lock);
y3ws_make_success_packet(output_data, output_size);
} else {
y3ws_make_error_packet("Unknown command", output_data, output_size);
}
end:
cJSON_Delete(json);
}
static void y3ws_handle_messages(SOCKET client) {
enum wsFrameType frameType = WS_INCOMPLETE_FRAME;
enum wsState state = WS_STATE_OPENING;
const int BUFFER_SIZE = 4096;
uint8_t in_buf[BUFFER_SIZE];
uint8_t out_buf[BUFFER_SIZE];
size_t out_size;
uint8_t* message = NULL;
size_t message_size = 0;
struct handshake hs;
nullHandshake(&hs);
while (frameType == WS_INCOMPLETE_FRAME) {
ssize_t read = recv(client, (char *) in_buf, BUFFER_SIZE, 0);
if (!read) {
dprintf("Y3WS: Accept failed\n");
break;
}
if (state == WS_STATE_OPENING) {
frameType = wsParseHandshake(in_buf, read, &hs);
} else {
frameType = wsParseInputFrame(in_buf, read, &message, &message_size);
}
if ((frameType == WS_INCOMPLETE_FRAME && read == BUFFER_SIZE) || frameType == WS_ERROR_FRAME) {
if (frameType == WS_INCOMPLETE_FRAME) {
dprintf("Y3WS: buffer too small");
} else {
dprintf("Y3WS: error in incoming frame\n");
}
if (state == WS_STATE_OPENING) {
memset(out_buf, 0, BUFFER_SIZE);
out_size = sprintf((char *) out_buf,
"HTTP/1.1 400 Bad Request\r\n"
"%s%s\r\n\r\n",
versionField,
version);
safeSend(client, out_buf, out_size);
break;
} else {
memset(out_buf, 0, BUFFER_SIZE);
wsMakeFrame(NULL, 0, out_buf, &out_size, WS_CLOSING_FRAME);
if (safeSend(client, out_buf, out_size) == EXIT_FAILURE) {
break;
}
state = WS_STATE_CLOSING;
frameType = WS_INCOMPLETE_FRAME;
}
}
if (state == WS_STATE_OPENING) {
assert(frameType == WS_OPENING_FRAME);
if (frameType == WS_OPENING_FRAME) {
if (strcmp(hs.resource, WS_PATH) != 0) {
dprintf("Y3WS: Invalid path: %s\n", hs.resource);
memset(out_buf, 0, BUFFER_SIZE);
out_size = sprintf((char *) out_buf, "HTTP/1.1 404 Not Found\r\n\r\n");
safeSend(client, out_buf, out_size);
break;
}
memset(out_buf, 0, BUFFER_SIZE);
wsGetHandshakeAnswer(&hs, out_buf, &out_size);
freeHandshake(&hs);
if (safeSend(client, out_buf, out_size) == EXIT_FAILURE)
break;
state = WS_STATE_NORMAL;
frameType = WS_INCOMPLETE_FRAME;
}
} else {
if (frameType == WS_CLOSING_FRAME) {
if (state == WS_STATE_CLOSING) {
break;
} else {
memset(out_buf, 0, BUFFER_SIZE);
wsMakeFrame(NULL, 0, out_buf, &out_size, WS_CLOSING_FRAME);
safeSend(client, out_buf, out_size);
break;
}
} else if (frameType == WS_TEXT_FRAME) {
uint8_t* input_data = malloc(message_size + 1);
memcpy(input_data, message, message_size);
input_data[message_size] = 0;
uint8_t* output_data = malloc(BUFFER_SIZE);
size_t output_size = BUFFER_SIZE;
y3ws_handle((const char*)input_data, message_size, output_data, &output_size);
wsMakeFrame(output_data, output_size, out_buf, &out_size, WS_TEXT_FRAME);
free(input_data);
if (safeSend(client, out_buf, out_size) == EXIT_FAILURE) {
break;
}
frameType = WS_INCOMPLETE_FRAME;
}
}
}
closesocket(client);
}
DWORD __stdcall y3_websocket_thread_proc(LPVOID ctx) {
while (true) {
struct sockaddr_in remote;
socklen_t sockaddr_len = sizeof(remote);
SOCKET client = accept(ws_socket, (struct sockaddr *) &remote, &sockaddr_len);
if (client == -1) {
break;
}
dprintf("Y3WS: Connected %s:%d\n", inet_ntoa(remote.sin_addr), ntohs(remote.sin_port));
y3ws_handle_messages(client);
dprintf("Y3WS: Disconnected\n");
}
closesocket(ws_socket);
return 0;
}
HRESULT y3_io_get_cards(struct CardInfo* pCardInfo, int* numCards) {
EnterCriticalSection(&card_info_lock);
for (int i = 0; i < card_info_size; i++) {
memcpy(&pCardInfo[i], &card_info[i], sizeof(struct CardInfo));
}
*numCards = card_info_size;
LeaveCriticalSection(&card_info_lock);
return S_OK;
}