#include #include #include #include #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; }