diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bff2252..17a64d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,7 @@ To contribute, fork the repo (just the main branch), make changes in your fork, * Any additions to built-in patches (modifications / removals are OK if we have a good reason). * Changes to the software license terms. * Localization / translation for UI text. It becomes too difficult to manage by the maintainers. +* Bundling third party DLL binary; e.g., Discord Social SDK, PC light control integration, and so on. These should be built as hook DLLs instead, optionally with [Spice SDK](https://github.com/spice2x/spice2x.github.io/wiki/Spice-SDK). ### Avoiding regressions diff --git a/README.md b/README.md index 640d215..7e764ab 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,10 @@ We explicitly do **NOT** have a Discord server for dicussions - we try to do eve Please see [CONTRIBUTING page](https://github.com/spice2x/spice2x.github.io/blob/main/CONTRIBUTING.md) for a full list of guidelines when submitting code. +### AI-Use Policy + +Use AI tools at your discretion, but remember that a human will review every issue and pull request. Please ensure submissions are accurate, relevant, and worth the reviewer’s time. + ## Additional information Please read [README.md](https://github.com/spice2x/spice2x.github.io/blob/main/src/spice2x/README.md) inside `src/spice2x`. diff --git a/src/spice2x/CMakeLists.txt b/src/spice2x/CMakeLists.txt index d4fb10f..e280460 100644 --- a/src/spice2x/CMakeLists.txt +++ b/src/spice2x/CMakeLists.txt @@ -252,6 +252,7 @@ add_subdirectory(external/discord-rpc EXCLUDE_FROM_ALL) add_subdirectory(external/hash-library EXCLUDE_FROM_ALL) add_subdirectory(external/imgui EXCLUDE_FROM_ALL) add_subdirectory(external/minhook EXCLUDE_FROM_ALL) +add_subdirectory(external/wslay EXCLUDE_FROM_ALL) add_subdirectory(external/cpu_features EXCLUDE_FROM_ALL) # libjpeg-turbo, prebuilt into the deps image. The WinXP toolchains have their own @@ -530,6 +531,7 @@ set(SOURCE_FILES ${SOURCE_FILES} games/scotto/io.cpp games/drs/drs.cpp games/drs/io.cpp + games/drs/motion_cam.cpp games/drs/rgb_cam.cpp games/we/we.cpp games/we/io.cpp @@ -541,6 +543,7 @@ set(SOURCE_FILES ${SOURCE_FILES} games/otoca/p4io.cpp games/silentscope/silentscope.cpp games/silentscope/io.cpp + games/silentscope/projector.cpp games/pcm/pcm.cpp games/pcm/io.cpp games/onpara/onpara.cpp @@ -813,7 +816,7 @@ endfunction() add_library(spicetools_spice_objs OBJECT ${SOURCE_FILES}) target_link_libraries(spicetools_spice_objs PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp - PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features spice_jpeg spice_x264) + PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook wslay imm32 dwmapi CpuFeatures::cpu_features spice_jpeg spice_x264) target_link_libraries(spicetools_spice_objs PUBLIC winscard) if(NOT MSVC) @@ -853,7 +856,7 @@ set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/W add_executable(spicetools_spice_linux ${SOURCE_FILES} ${RESOURCE_FILES}) target_link_libraries(spicetools_spice_linux PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp - PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features spice_jpeg spice_x264) + PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook wslay imm32 dwmapi CpuFeatures::cpu_features spice_jpeg spice_x264) set_target_properties(spicetools_spice_linux PROPERTIES PREFIX "") set_target_properties(spicetools_spice_linux PROPERTIES OUTPUT_NAME "spice_linux") target_compile_definitions(spicetools_spice_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1) @@ -871,7 +874,7 @@ add_executable(spicetools_spice64 ${SOURCE_FILES} ${RESOURCE_FILES}) # do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break target_link_libraries(spicetools_spice64 PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2 - PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features spice_jpeg spice_x264) + PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook wslay imm32 dwmapi CpuFeatures::cpu_features spice_jpeg spice_x264) target_link_libraries(spicetools_spice64 PUBLIC winscard) set_target_properties(spicetools_spice64 PROPERTIES PREFIX "") set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64") @@ -894,7 +897,7 @@ add_executable(spicetools_spice64_linux ${SOURCE_FILES} ${RESOURCE_FILES}) # do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break target_link_libraries(spicetools_spice64_linux PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2 - PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features spice_jpeg spice_x264) + PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook wslay imm32 dwmapi CpuFeatures::cpu_features spice_jpeg spice_x264) set_target_properties(spicetools_spice64_linux PROPERTIES PREFIX "") set_target_properties(spicetools_spice64_linux PROPERTIES OUTPUT_NAME "spice64_linux") target_compile_definitions(spicetools_spice64_linux PRIVATE SPICE64=1) @@ -916,7 +919,8 @@ add_executable(spicetools_cfg WIN32 ${SOURCE_FILES} ${RESOURCE_FILES}) # the configurator serves neither the API nor the video stream, so it needs no codecs target_link_libraries(spicetools_cfg PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids - PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) + PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook wslay imm32 dwmapi CpuFeatures::cpu_features) +target_link_libraries(spicetools_cfg PUBLIC winscard) set_target_properties(spicetools_cfg PROPERTIES PREFIX "") set_target_properties(spicetools_cfg PROPERTIES OUTPUT_NAME "spicecfg") target_compile_definitions(spicetools_cfg PRIVATE SPICETOOLS_SPICECFG_STANDALONE=1) @@ -933,7 +937,7 @@ set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D. add_executable(spicetools_cfg_linux WIN32 ${SOURCE_FILES} ${RESOURCE_FILES}) target_link_libraries(spicetools_cfg_linux PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids - PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) + PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook wslay imm32 dwmapi CpuFeatures::cpu_features) set_target_properties(spicetools_cfg_linux PROPERTIES PREFIX "") set_target_properties(spicetools_cfg_linux PROPERTIES OUTPUT_NAME "spicecfg_linux") target_compile_definitions(spicetools_cfg_linux PRIVATE SPICETOOLS_SPICECFG_STANDALONE=1) diff --git a/src/spice2x/README.md b/src/spice2x/README.md index 11f5bfd..342ea7e 100644 --- a/src/spice2x/README.md +++ b/src/spice2x/README.md @@ -127,7 +127,30 @@ doesn't matter since the TCP protocol doesn't allow for out of order data, however this may change when/if support for UDP is being introduced. The only restriction is that the ID has to be a valid 64-bit unsigned integer. +#### Capture +- get_screens() + - returns the screen numbers the game has registered for capture +- get_jpg(screen: uint, quality: uint, divide: uint) + - returns the timestamp, width, height and base64 encoded JPEG of one screen + - all parameters are optional and default to screen 0, quality 70, divide 1 + - divide shrinks the image by that factor before encoding +- get_streams() + - returns a dict describing the HTTP video stream, or no data at all when + `-apistream` is not enabled and there is nothing to describe + - `port` is the stream server port + - `formats` lists the wire formats this build serves, each with a `name` + (`h264` or `mjpeg`) and the `path` to request them on + - `screens` lists every capturable screen with its `width`, `height`, and + `busy` + #### Card +- get_cards() + - returns the current card ID and source for each active card reader + - each entry contains `index`, `card_id`, and `source` + - `source` is `file` or `override` + - file entries also contain `file_name`, without the full configured path + - `-card0` and `-card1` overrides do not contain `file_name` + - this function only works when an API password is configured - insert(index: uint, card_id: hex) - inserts a card which gets read by the emulated card readers for the game - index has to be either 0 (for P1) or 1 (for P2) @@ -298,6 +321,10 @@ stream over plain HTTP. Enable it with `-apistream`. It listens on the API port plus two, in the same way the WebSocket server uses the API port plus one, so `-api 1337` puts the stream on 1339. This means `-api` has to be enabled too. +Rather than working the port out, clients should ask the JSON API for it with +`capture.get_streams()`, which also reports which of the formats below this +build serves, the size of each screen and whether one is already taken. + Two formats are served: http://host:1339/stream.mjpg JPEG frames, multipart/x-mixed-replace @@ -323,9 +350,10 @@ The stream is view only. Touch and other input still go through the JSON API, so a companion app needs both. There is no authentication on the stream port - anyone who can reach it can watch the screen. -WinXP builds have no video stream. Neither encoder is compiled in, so every -endpoint returns 404, and the JSON API's JPEG screen capture is unavailable for -the same reason. +WinXP builds have no video stream. Neither encoder is compiled in, so nothing +listens on the stream port even with `-apistream`, `capture.get_streams()` +returns no data, and the JSON API's JPEG screen capture is unavailable for the +same reason. ## Native wrapper libraries Spicetools provides wrapper libraries in: Arduino, C++, Dart, and Python. diff --git a/src/spice2x/acio/bmpu/bmpu.cpp b/src/spice2x/acio/bmpu/bmpu.cpp index e8ccb5f..6e183cf 100644 --- a/src/spice2x/acio/bmpu/bmpu.cpp +++ b/src/spice2x/acio/bmpu/bmpu.cpp @@ -1,5 +1,7 @@ #include "bmpu.h" +#include + #include "acio/icca/icca.h" #include "avs/game.h" #include "cfg/api.h" @@ -9,6 +11,7 @@ #include "games/ftt/io.h" #include "games/museca/io.h" #include "games/silentscope/io.h" +#include "hooks/graphics/graphics.h" #include "launcher/launcher.h" #include "misc/eamuse.h" @@ -530,15 +533,38 @@ static bool __cdecl ac_io_bmpu_update_control_status_buffer() { STATUS_BUFFER[4] |= 0x20; } - // joy stick raw input + // gun position, sent big endian; the game keeps the top 12 bits of each pair auto &analogs = games::silentscope::get_analogs(); + auto &gun_x = analogs.at(games::silentscope::Analogs::GUN_X); + auto &gun_y = analogs.at(games::silentscope::Analogs::GUN_Y); unsigned short joy_x = 0x7FFF; unsigned short joy_y = 0x7FFF; - if (analogs.at(games::silentscope::Analogs::GUN_X).isSet()) { - joy_x = (unsigned short) (Analogs::getState(RI_MGR, analogs.at(games::silentscope::Analogs::GUN_X)) * USHRT_MAX); - } - if (analogs.at(games::silentscope::Analogs::GUN_Y).isSet()) { - joy_y = (unsigned short) (Analogs::getState(RI_MGR, analogs.at(games::silentscope::Analogs::GUN_Y)) * USHRT_MAX); + + if (gun_x.isSet() || gun_y.isSet()) { + if (gun_x.isSet()) { + joy_x = (unsigned short) (Analogs::getState(RI_MGR, gun_x) * USHRT_MAX); + } + if (gun_y.isSet()) { + joy_y = (unsigned short) (Analogs::getState(RI_MGR, gun_y) * USHRT_MAX); + } + } else { + + // the gun reports where it is aimed, which a relative pointer cannot express - the + // calibration screen asks for the screen centre and two corners, so read the cursor + POINT cursor {}; + RECT client {}; + POINT origin {}; + if (NDD_MAIN_WINDOW != nullptr && + GetCursorPos(&cursor) && + GetClientRect(NDD_MAIN_WINDOW, &client) && + ClientToScreen(NDD_MAIN_WINDOW, &origin) && + client.right > 1 && client.bottom > 1) + { + const LONG x = std::clamp(cursor.x - origin.x, 0L, client.right - 1); + const LONG y = std::clamp(cursor.y - origin.y, 0L, client.bottom - 1); + joy_x = (unsigned short) (x * USHRT_MAX / (client.right - 1)); + joy_y = (unsigned short) (y * USHRT_MAX / (client.bottom - 1)); + } } // invert X axis diff --git a/src/spice2x/api/capture_pump.cpp b/src/spice2x/api/capture_pump.cpp index fe75e56..ecedcab 100644 --- a/src/spice2x/api/capture_pump.cpp +++ b/src/spice2x/api/capture_pump.cpp @@ -1,6 +1,7 @@ #include "capture_pump.h" #include +#include #include #include "hooks/graphics/graphics.h" @@ -11,8 +12,8 @@ namespace api::capture_pump { std::array CONSUMER_M; - std::mutex CLAIMED_M; - std::array CLAIMED {}; + // read once per capture from the present thread, so it stays lock free + std::atomic CLAIMED[GRAPHICS_CAPTURE_SCREEN_NO] {}; bool valid_screen(int screen) { return 0 <= screen && screen < static_cast(GRAPHICS_CAPTURE_SCREEN_NO); @@ -37,14 +38,7 @@ namespace api::capture_pump { return false; } - std::lock_guard lock(CLAIMED_M); - - if (CLAIMED[screen]) { - return false; - } - - CLAIMED[screen] = true; - return true; + return !CLAIMED[screen].exchange(true); } void release_screen(int screen) { @@ -52,7 +46,14 @@ namespace api::capture_pump { return; } - std::lock_guard lock(CLAIMED_M); CLAIMED[screen] = false; } + + bool screen_claimed(int screen) { + if (!valid_screen(screen)) { + return false; + } + + return CLAIMED[screen]; + } } diff --git a/src/spice2x/api/capture_pump.h b/src/spice2x/api/capture_pump.h index b8ae9d0..650fb38 100644 --- a/src/spice2x/api/capture_pump.h +++ b/src/spice2x/api/capture_pump.h @@ -21,4 +21,7 @@ namespace api::capture_pump { // a screen carries one stream at a time; false when another connection already holds it bool claim_screen(int screen); void release_screen(int screen); + + // true while a video stream client holds this screen + bool screen_claimed(int screen); } diff --git a/src/spice2x/api/controller.cpp b/src/spice2x/api/controller.cpp index 1fd41d5..1a6c4dd 100644 --- a/src/spice2x/api/controller.cpp +++ b/src/spice2x/api/controller.cpp @@ -337,9 +337,11 @@ bool Controller::process_request(ClientState *state, const char *in, size_t in_s if (module->name == request.module) { module_found = true; - // check password force - if (module->password_force && this->password.empty() && request.function != "session_refresh") { - Value err("Module requires the password to be set."); + // check password requirement + if (module->requires_password(request.function) + && this->password.empty() + && request.function != "session_refresh") { + Value err("Function requires the password to be set."); response.add_error(err); break; } diff --git a/src/spice2x/api/h264_stream.cpp b/src/spice2x/api/h264_stream.cpp index 7774220..b51c5cb 100644 --- a/src/spice2x/api/h264_stream.cpp +++ b/src/spice2x/api/h264_stream.cpp @@ -118,7 +118,13 @@ namespace api { param.i_height = height; param.i_fps_num = this->fps; param.i_fps_den = 1; - param.i_threads = 1; + + // sliced threading, which zerolatency already selected, so a frame is split + // across workers rather than held back to be reordered. deliberately not the + // automatic count: this shares a machine with the game it is capturing, and + // taking every core to encode would win back frames at the game's expense + param.i_threads = 4; + param.b_annexb = 1; // SPS/PPS ahead of every IDR, so a client can start decoding cold param.b_repeat_headers = 1; diff --git a/src/spice2x/api/module.cpp b/src/spice2x/api/module.cpp index 700a562..af2a0e8 100644 --- a/src/spice2x/api/module.cpp +++ b/src/spice2x/api/module.cpp @@ -16,6 +16,14 @@ namespace api { this->password_force = password_force; } + void Module::require_password(const std::string &function) { + this->password_force_functions.emplace(function); + } + + bool Module::requires_password(const std::string &function) const { + return this->password_force || this->password_force_functions.contains(function); + } + void Module::handle(Request &req, Response &res) { // log module access diff --git a/src/spice2x/api/module.h b/src/spice2x/api/module.h index d41b787..262a893 100644 --- a/src/spice2x/api/module.h +++ b/src/spice2x/api/module.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "response.h" @@ -26,6 +27,13 @@ namespace api { // default constructor explicit Module(std::string name, bool password_force=false); + void require_password(const std::string &function); + + private: + + // functions which expose sensitive data or actions + std::unordered_set password_force_functions; + public: // virtual deconstructor @@ -35,6 +43,8 @@ namespace api { std::string name; bool password_force; + bool requires_password(const std::string &function) const; + // the magic void handle(Request &req, Response &res); diff --git a/src/spice2x/api/modules/capture.cpp b/src/spice2x/api/modules/capture.cpp index b8ab2b1..644d944 100644 --- a/src/spice2x/api/modules/capture.cpp +++ b/src/spice2x/api/modules/capture.cpp @@ -1,8 +1,11 @@ #include "capture.h" +#include #include #include #include #include "api/capture_pump.h" +#include "api/stream_format.h" +#include "api/stream_server.h" #include "external/rapidjson/document.h" #include "hooks/graphics/graphics.h" #include "hooks/graphics/jpeg_encoder.h" @@ -71,6 +74,7 @@ namespace api::modules { Capture::Capture() : Module("capture") { functions["get_screens"] = std::bind(&Capture::get_screens, this, _1, _2); functions["get_jpg"] = std::bind(&Capture::get_jpg, this, _1, _2); + functions["get_streams"] = std::bind(&Capture::get_streams, this, _1, _2); } /** @@ -141,4 +145,98 @@ namespace api::modules { CAPTURE_BUFFER.clear(); try_cached_response(screen, res); } + + /** + * get_streams() + */ + void Capture::get_streams(Request &req, Response &res) { + + auto &alloc = res.doc()->GetAllocator(); + + // nothing is listening without -apistream, so there is no stream to describe + const unsigned short port = stream_server_port(); + if (port == 0) { + return; + } + + Value formats(kArrayType); + for (const auto &[name, path] : stream_formats()) { + Value entry(kObjectType); + entry.AddMember("name", Value(name.c_str(), alloc), alloc); + entry.AddMember("path", Value(path.c_str(), alloc), alloc); + formats.PushBack(entry, alloc); + } + + std::vector screen_numbers; + graphics_screens_get(screen_numbers); + + // measuring a screen nobody has captured yet waits for the game to present, which can + // take as long as the whole request is allowed, so only one screen is measured per + // call and the rest are reported null until a later one settles them. which screen + // gets the attempt rotates, otherwise one that never presents would take every + // request and the screens behind it would stay unmeasured forever + int probe_screen = -1; + { + std::vector unmeasured; + for (const auto screen : screen_numbers) { + if (screen < static_cast(GRAPHICS_CAPTURE_SCREEN_NO) + && !graphics_capture_last_size(screen, nullptr, nullptr) + && !capture_pump::screen_claimed(screen)) { + unmeasured.push_back(screen); + } + } + + if (!unmeasured.empty()) { + static std::atomic probe_cursor { 0 }; + probe_screen = unmeasured[probe_cursor.fetch_add(1) % unmeasured.size()]; + } + } + + Value screens(kArrayType); + for (const auto screen : screen_numbers) { + if (screen >= static_cast(GRAPHICS_CAPTURE_SCREEN_NO)) { + continue; + } + + int width = 0; + int height = 0; + bool known = graphics_capture_last_size(screen, &width, &height); + + // a probe holds the screen for as long as it waits, so a second caller arriving + // during one would queue behind it and then take a wait of its own; let it report + // the screen as unmeasured instead and pick the size up once the first is done + static std::atomic probe_running { false }; + if (!known && screen == probe_screen && !probe_running.exchange(true)) { + std::shared_ptr pixels; + known = capture_pump::capture_direct( + screen, pixels, 1, nullptr, &width, &height); + probe_running = false; + } + + // a screen of unknown size cannot be described, and a client told about it could + // not size its decoder anyway; leaving it out until it has been measured beats + // handing over an entry that has to be treated as absent + if (!known) { + continue; + } + + Value entry(kObjectType); + entry.AddMember("screen", screen, alloc); + entry.AddMember("width", width, alloc); + entry.AddMember("height", height, alloc); + + // a screen carries one viewer at a time, so this is what decides whether a client + // can connect at all; still racy by the time it does, only more honest than not + entry.AddMember("busy", capture_pump::screen_claimed(screen), alloc); + + screens.PushBack(entry, alloc); + } + + Value info(kObjectType); + info.AddMember("port", port, alloc); + info.AddMember("formats", formats, alloc); + info.AddMember("screens", screens, alloc); + + res.add_data(info); + } } diff --git a/src/spice2x/api/modules/capture.h b/src/spice2x/api/modules/capture.h index fb8c55d..b994f3a 100644 --- a/src/spice2x/api/modules/capture.h +++ b/src/spice2x/api/modules/capture.h @@ -19,5 +19,6 @@ namespace api::modules { // function definitions void get_screens(Request &req, Response &res); void get_jpg(Request &req, Response &res); + void get_streams(Request &req, Response &res); }; } diff --git a/src/spice2x/api/modules/card.cpp b/src/spice2x/api/modules/card.cpp index 5eb262a..ac65560 100644 --- a/src/spice2x/api/modules/card.cpp +++ b/src/spice2x/api/modules/card.cpp @@ -1,4 +1,5 @@ #include "card.h" +#include #include #include "external/rapidjson/document.h" #include "util/logging.h" @@ -11,8 +12,71 @@ using namespace rapidjson; namespace api::modules { + static bool normalize_card_id(const std::string &value, std::string &card_id) { + if (value.size() != 16) { + return false; + } + + uint8_t card_bin[8] {}; + if (!hex2bin(value.c_str(), card_bin)) { + return false; + } + + card_id = bin2hex(card_bin, std::size(card_bin)); + return true; + } + + static bool read_card_id(const std::filesystem::path &path, std::string &card_id) { + std::ifstream file(path); + char buffer[16] {}; + if (!file.read(buffer, std::size(buffer))) { + return false; + } + + return normalize_card_id(std::string(buffer, std::size(buffer)), card_id); + } + Card::Card() : Module("card") { + functions["get_cards"] = std::bind(&Card::get_cards, this, _1, _2); functions["insert"] = std::bind(&Card::insert, this, _1, _2); + require_password("get_cards"); + } + + /** + * get_cards() + */ + void Card::get_cards(Request &req, Response &res) { + auto &alloc = res.doc()->GetAllocator(); + + for (int index = 0; index < eamuse_get_game_keypads(); index++) { + std::string card_id; + std::string filename; + const auto card_override = eamuse_get_card_override(index); + const bool has_override = !card_override.empty(); + + if (has_override) { + if (!normalize_card_id(card_override, card_id)) { + continue; + } + } else { + const auto path = eamuse_get_card_path(index); + if (!read_card_id(path, card_id)) { + continue; + } + + const auto filename_u8 = path.filename().u8string(); + filename.assign(filename_u8.begin(), filename_u8.end()); + } + + Value card(kObjectType); + card.AddMember("index", index, alloc); + card.AddMember("card_id", Value(card_id.c_str(), alloc), alloc); + card.AddMember("source", Value(has_override ? "override" : "file", alloc), alloc); + if (!has_override) { + card.AddMember("file_name", Value(filename.c_str(), alloc), alloc); + } + res.add_data(card); + } } /** diff --git a/src/spice2x/api/modules/card.h b/src/spice2x/api/modules/card.h index 6012176..e90fbe3 100644 --- a/src/spice2x/api/modules/card.h +++ b/src/spice2x/api/modules/card.h @@ -12,6 +12,7 @@ namespace api::modules { private: // function definitions + void get_cards(Request &req, Response &res); void insert(Request &req, Response &res); }; } diff --git a/src/spice2x/api/resources/dart/spiceapi-websocket/spiceapi.dart b/src/spice2x/api/resources/dart/spiceapi-websocket/spiceapi.dart index 413db56..1491e74 100644 --- a/src/spice2x/api/resources/dart/spiceapi-websocket/spiceapi.dart +++ b/src/spice2x/api/resources/dart/spiceapi-websocket/spiceapi.dart @@ -11,6 +11,7 @@ part "src/exceptions.dart"; part "src/rc4.dart"; part "src/wrappers/analogs.dart"; part "src/wrappers/buttons.dart"; +part "src/wrappers/capture.dart"; part "src/wrappers/card.dart"; part "src/wrappers/coin.dart"; part "src/wrappers/control.dart"; diff --git a/src/spice2x/api/resources/dart/spiceapi-websocket/src/connection.dart b/src/spice2x/api/resources/dart/spiceapi-websocket/src/connection.dart index a27ec3f..089bbff 100644 --- a/src/spice2x/api/resources/dart/spiceapi-websocket/src/connection.dart +++ b/src/spice2x/api/resources/dart/spiceapi-websocket/src/connection.dart @@ -4,8 +4,8 @@ part of spiceapi; class Connection { // settings - static const _TIMEOUT = Duration(seconds: 2); - static const _BUFFER_SIZE = 1024 * 8; + static const _TIMEOUT = Duration(seconds: 3); + static const _BUFFER_SIZE = 1024 * 1024 * 8; // state final String host, pass; diff --git a/src/spice2x/api/resources/dart/spiceapi-websocket/src/wrappers/capture.dart b/src/spice2x/api/resources/dart/spiceapi-websocket/src/wrappers/capture.dart new file mode 100644 index 0000000..a6c3b88 --- /dev/null +++ b/src/spice2x/api/resources/dart/spiceapi-websocket/src/wrappers/capture.dart @@ -0,0 +1,47 @@ +part of spiceapi; + +class CaptureData { + int timestamp; + int width, height; + Uint8List data; +} + +var _base64DecoderInstance = Base64Decoder(); + +Future captureGetScreens(Connection con) { + var req = Request("capture", "get_screens"); + return con.request(req).then((res) { + return res.getData(); + }); +} + +Future captureGetJPG(Connection con, { + int screen = 0, + int quality = 70, + int divide = 1, +}) { + var req = Request("capture", "get_jpg"); + req.addParam(screen); + req.addParam(quality); + req.addParam(divide); + return con.request(req).then((res) { + var captureData = CaptureData(); + var data = res.getData(); + if (data.length > 0) captureData.timestamp = data[0]; + if (data.length > 1) captureData.width = data[1]; + if (data.length > 2) captureData.height = data[2]; + if (data.length > 3) { + captureData.data = _base64DecoderInstance.convert(data[3]); + } + return captureData; + }); +} + +/// Describes the HTTP video stream, or null when this spice2x serves none. +Future captureGetStreams(Connection con) { + var req = Request("capture", "get_streams"); + return con.request(req).then((res) { + var data = res.getData(); + return data.length > 0 ? data[0] : null; + }); +} diff --git a/src/spice2x/api/resources/dart/spiceapi-websocket/src/wrappers/card.dart b/src/spice2x/api/resources/dart/spiceapi-websocket/src/wrappers/card.dart index 2a42d63..1bd487d 100644 --- a/src/spice2x/api/resources/dart/spiceapi-websocket/src/wrappers/card.dart +++ b/src/spice2x/api/resources/dart/spiceapi-websocket/src/wrappers/card.dart @@ -1,5 +1,32 @@ part of spiceapi; +class CardInfo { + final int index; + final String cardID; + final String source; + final String? fileName; + + CardInfo(this.index, this.cardID, this.source, this.fileName); +} + +Future> cardGetCards(Connection con) { + var req = Request("card", "get_cards"); + return con.request(req).then((res) { + List cards = []; + for (var value in res.getData()) { + cards.add( + CardInfo( + value["index"], + value["card_id"], + value["source"], + value["file_name"], + ), + ); + } + return cards; + }); +} + Future cardInsert(Connection con, int unit, String cardID) { var req = Request("card", "insert"); req.addParam(unit); diff --git a/src/spice2x/api/resources/dart/spiceapi/src/wrappers/capture.dart b/src/spice2x/api/resources/dart/spiceapi/src/wrappers/capture.dart index be15f14..a6c3b88 100644 --- a/src/spice2x/api/resources/dart/spiceapi/src/wrappers/capture.dart +++ b/src/spice2x/api/resources/dart/spiceapi/src/wrappers/capture.dart @@ -17,7 +17,7 @@ Future captureGetScreens(Connection con) { Future captureGetJPG(Connection con, { int screen = 0, - int quality = 60, + int quality = 70, int divide = 1, }) { var req = Request("capture", "get_jpg"); @@ -36,3 +36,12 @@ Future captureGetJPG(Connection con, { return captureData; }); } + +/// Describes the HTTP video stream, or null when this spice2x serves none. +Future captureGetStreams(Connection con) { + var req = Request("capture", "get_streams"); + return con.request(req).then((res) { + var data = res.getData(); + return data.length > 0 ? data[0] : null; + }); +} diff --git a/src/spice2x/api/resources/dart/spiceapi/src/wrappers/card.dart b/src/spice2x/api/resources/dart/spiceapi/src/wrappers/card.dart index 2a42d63..1bd487d 100644 --- a/src/spice2x/api/resources/dart/spiceapi/src/wrappers/card.dart +++ b/src/spice2x/api/resources/dart/spiceapi/src/wrappers/card.dart @@ -1,5 +1,32 @@ part of spiceapi; +class CardInfo { + final int index; + final String cardID; + final String source; + final String? fileName; + + CardInfo(this.index, this.cardID, this.source, this.fileName); +} + +Future> cardGetCards(Connection con) { + var req = Request("card", "get_cards"); + return con.request(req).then((res) { + List cards = []; + for (var value in res.getData()) { + cards.add( + CardInfo( + value["index"], + value["card_id"], + value["source"], + value["file_name"], + ), + ); + } + return cards; + }); +} + Future cardInsert(Connection con, int unit, String cardID) { var req = Request("card", "insert"); req.addParam(unit); diff --git a/src/spice2x/api/resources/python/spiceapi/__init__.py b/src/spice2x/api/resources/python/spiceapi/__init__.py index f0da213..161201a 100644 --- a/src/spice2x/api/resources/python/spiceapi/__init__.py +++ b/src/spice2x/api/resources/python/spiceapi/__init__.py @@ -2,6 +2,7 @@ from .connection import Connection from .request import Request from .analogs import * from .buttons import * +from .capture import * from .card import * from .coin import * from .control import * diff --git a/src/spice2x/api/resources/python/spiceapi/capture.py b/src/spice2x/api/resources/python/spiceapi/capture.py new file mode 100644 index 0000000..f35b094 --- /dev/null +++ b/src/spice2x/api/resources/python/spiceapi/capture.py @@ -0,0 +1,33 @@ +import base64 + +from .connection import Connection +from .request import Request + + +def capture_get_screens(con: Connection): + res = con.request(Request("capture", "get_screens")) + return res.get_data() + + +def capture_get_jpg(con: Connection, screen: int = 0, quality: int = 70, divide: int = 1): + req = Request("capture", "get_jpg") + req.add_param(screen) + req.add_param(quality) + req.add_param(divide) + data = con.request(req).get_data() + + if len(data) < 4: + return None + + return { + "timestamp": data[0], + "width": data[1], + "height": data[2], + "data": base64.b64decode(data[3]), + } + + +def capture_get_streams(con: Connection): + """Describes the HTTP video stream, or None when this spice2x serves none.""" + data = con.request(Request("capture", "get_streams")).get_data() + return data[0] if data else None diff --git a/src/spice2x/api/resources/python/spiceapi/card.py b/src/spice2x/api/resources/python/spiceapi/card.py index d579ed8..3eff0d2 100644 --- a/src/spice2x/api/resources/python/spiceapi/card.py +++ b/src/spice2x/api/resources/python/spiceapi/card.py @@ -2,6 +2,10 @@ from .connection import Connection from .request import Request +def card_get_cards(con: Connection): + return con.request(Request("card", "get_cards")).get_data() + + def card_insert(con: Connection, unit: int, card_id: str): req = Request("card", "insert") req.add_param(unit) diff --git a/src/spice2x/api/stream_format.cpp b/src/spice2x/api/stream_format.cpp index 0fce3ce..4a06971 100644 --- a/src/spice2x/api/stream_format.cpp +++ b/src/spice2x/api/stream_format.cpp @@ -67,4 +67,18 @@ namespace api { return nullptr; } + + std::vector> stream_formats() { + std::vector> formats; + +#ifdef SPICE_JPEG + formats.emplace_back("mjpeg", "/stream.mjpg"); +#endif + +#ifdef SPICE_H264 + formats.emplace_back("h264", "/stream.h264"); +#endif + + return formats; + } } diff --git a/src/spice2x/api/stream_format.h b/src/spice2x/api/stream_format.h index ed04fb9..a0268de 100644 --- a/src/spice2x/api/stream_format.h +++ b/src/spice2x/api/stream_format.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "capture_pump.h" @@ -35,4 +37,7 @@ namespace api { // null when the path does not name a format this build supports std::unique_ptr make_stream_writer( const std::string &path, int quality, int fps); + + // name and path of every format compiled into this build, for clients to pick from + std::vector> stream_formats(); } diff --git a/src/spice2x/api/stream_server.cpp b/src/spice2x/api/stream_server.cpp index 99d720d..6c9ee2f 100644 --- a/src/spice2x/api/stream_server.cpp +++ b/src/spice2x/api/stream_server.cpp @@ -14,6 +14,7 @@ #include "capture_pump.h" #include "hooks/graphics/graphics.h" +#include "overlay/notifications.h" #include "stream_format.h" #include "util/logging.h" #include "util/utils.h" @@ -178,6 +179,13 @@ namespace api { // carry it too, or the client sees an opaque failure instead of the status. constexpr const char *cors_header = "Access-Control-Allow-Origin: *\r\n"; + // the port is unauthenticated, so a scanner hammering a busy/missing screen could + // otherwise flood the overlay; throttle failure toasts per distinct cause. kept under + // a second so it only swallows that, not a legitimate reconnect - substream itself + // switches screens with a 300ms gap, and only backs off to a full second once a + // retry has actually failed + constexpr double notification_throttle_seconds = 0.5; + void send_error(SOCKET socket, const char *status) { const std::string response = std::string("HTTP/1.0 ") + status + "\r\n" @@ -187,11 +195,25 @@ namespace api { "\r\n"; send_all(socket, response); } + + std::atomic LISTENING_PORT { 0 }; + } + + unsigned short stream_server_port() { + return LISTENING_PORT.load(); } StreamServer::StreamServer(unsigned short port) : port(port) { + // WinXP builds compile in neither encoder, so there would be nothing to serve and + // every request would 404; taking the port instead only invites confused clients + if (stream_formats().empty()) { + log_warning("api::stream", + "this build has no video encoders, the video stream is unavailable"); + return; + } + if (!this->open_listener()) { // the stream was asked for explicitly, so say plainly that it is not there log_warning("api::stream", @@ -204,6 +226,8 @@ namespace api { this->accept_worker(); }); + LISTENING_PORT = this->port; + // deliberately not logging a full URL; local IPs would leak into shared logs log_info("api::stream", "video stream is listening on port: {}", this->port); log_warning("api::stream", @@ -261,6 +285,7 @@ namespace api { StreamServer::~StreamServer() { this->running = false; + LISTENING_PORT = 0; if (this->listener != INVALID_SOCKET) { closesocket(this->listener); @@ -334,6 +359,11 @@ namespace api { if (slot < 0) { log_warning("api::stream", "client limit of {} hit", client_limit); + overlay::notifications::add_throttled( + overlay::notifications::Severity::Warning, + "api::stream.client_limit", + notification_throttle_seconds, + fmt::format("Video stream refused: client limit reached ({})", address)); send_error(client, "503 Service Unavailable"); closesocket(client); continue; @@ -410,16 +440,32 @@ namespace api { if (!streamable(screen)) { log_warning("api::stream", "screen {} is not available, refusing {}", screen, address); + overlay::notifications::add_throttled( + overlay::notifications::Severity::Warning, + fmt::format("api::stream.screen_unavailable.{}", screen), + notification_throttle_seconds, + fmt::format("Video stream refused: screen {} not available ({})", + screen, address)); send_error(socket, "404 Not Found"); } else if (!capture_pump::claim_screen(screen)) { log_warning("api::stream", "screen {} is already being streamed, refusing {}", screen, address); + overlay::notifications::add_throttled( + overlay::notifications::Severity::Warning, + fmt::format("api::stream.screen_claimed.{}", screen), + notification_throttle_seconds, + fmt::format("Video stream refused: screen {} already streaming ({})", + screen, address)); send_error(socket, "503 Service Unavailable"); } else { log_info("api::stream", "client connected: {} ({}, screen={}, fps={}, quality={})", address, request.path, screen, fps, quality); + overlay::notifications::add( + overlay::notifications::Severity::Success, + fmt::format("Video stream client connected ({}, screen {})", + address, screen)); const std::string header = "HTTP/1.0 200 OK\r\n" @@ -460,6 +506,9 @@ namespace api { capture_pump::release_screen(screen); log_info("api::stream", "client disconnected: {}", address); + overlay::notifications::add( + overlay::notifications::Severity::Info, + fmt::format("Video stream client disconnected ({})", address)); } } } diff --git a/src/spice2x/api/stream_server.h b/src/spice2x/api/stream_server.h index 0e2646e..d8747d0 100644 --- a/src/spice2x/api/stream_server.h +++ b/src/spice2x/api/stream_server.h @@ -11,6 +11,9 @@ namespace api { + // 0 while no stream server is listening, so the API can tell clients not to look for one + unsigned short stream_server_port(); + class StreamServer { public: diff --git a/src/spice2x/api/websocket.cpp b/src/spice2x/api/websocket.cpp index 18dec87..f530c12 100644 --- a/src/spice2x/api/websocket.cpp +++ b/src/spice2x/api/websocket.cpp @@ -1,205 +1,579 @@ -#define HEADSOCKET_IMPLEMENTATION -#include "external/headsocket.h" +#include +#include #include "websocket.h" -#include "util/utils.h" -#include "util/rc4.h" -#include "util/logging.h" -#include "overlay/notifications.h" -#include "controller.h" -using namespace headsocket; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "external/wslay/msvc_compat.h" +#include + +#include "controller.h" +#include "external/hash-library/sha1.h" +#include "overlay/notifications.h" +#include "util/crypt.h" +#include "util/logging.h" namespace api { namespace { - // how long a single handshake read may stall before the connection is dropped; - // headsocket reads the request a byte at a time, so this is an idle timeout between - // bytes rather than a deadline for the whole handshake - constexpr int handshake_timeout_ms = 5000; + constexpr int server_backlog = 4; + constexpr size_t client_limit = 8; - void set_recv_timeout(connection &conn, int milliseconds) { - DWORD timeout = static_cast(milliseconds); - setsockopt(conn.impl()->socket, SOL_SOCKET, SO_RCVTIMEO, + // a peer that connects and then says nothing must not hold a slot forever + constexpr int handshake_timeout_ms = 5000; + constexpr size_t request_size_limit = 8 * 1024; + constexpr uint64_t message_size_limit = 64 * 1024; + + // how long a quiet connection waits before the loop rechecks whether we are stopping + constexpr int idle_poll_ms = 500; + + // RFC 6455 appends this to the client key before hashing + constexpr const char *websocket_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + constexpr double notification_throttle_seconds = 0.5; + + std::string trim(const std::string &text) { + const auto begin = text.find_first_not_of(" \t"); + if (begin == std::string::npos) { + return ""; + } + return text.substr(begin, text.find_last_not_of(" \t") - begin + 1); + } + + std::string to_lower(std::string text) { + std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + return text; + } + + bool contains_token(const std::string &value, const std::string &expected) { + size_t pos = 0; + while (pos < value.size()) { + const size_t end = value.find(',', pos); + if (to_lower(trim(value.substr(pos, end - pos))) == expected) { + return true; + } + if (end == std::string::npos) { + break; + } + pos = end + 1; + } + return false; + } + + bool valid_websocket_key(const std::string &key) { + if (key.size() != 24 || key[22] != '=' || key[23] != '=') { + return false; + } + + const std::string alphabet = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + for (size_t i = 0; i < 22; i++) { + if (alphabet.find(key[i]) == std::string::npos) { + return false; + } + } + return true; + } + + bool send_all(SOCKET socket, const std::string &text) { + size_t remaining = text.size(); + const char *cursor = text.data(); + + while (remaining > 0) { + const int sent = send(socket, cursor, static_cast(remaining), 0); + if (sent <= 0) { + return false; + } + cursor += sent; + remaining -= static_cast(sent); + } + + return true; + } + + void set_recv_timeout(SOCKET socket, int milliseconds) { + const DWORD timeout = static_cast(milliseconds); + setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&timeout), sizeof(timeout)); } + + std::string accept_key(const std::string &client_key) { + SHA1 sha1; + sha1.add(client_key.data(), client_key.size()); + sha1.add(websocket_guid, strlen(websocket_guid)); + + unsigned char digest[SHA1::HashBytes] {}; + sha1.getHash(digest); + + return crypt::base64_encode(digest, sizeof(digest)); + } + + bool handshake(SOCKET socket) { + std::string request; + char byte = 0; + bool complete = false; + + while (request.size() < request_size_limit) { + const int read = recv(socket, &byte, 1, 0); + if (read <= 0) { + return false; + } + + request.push_back(byte); + if (request.size() >= 4 + && request.compare(request.size() - 4, 4, "\r\n\r\n") == 0) { + complete = true; + break; + } + } + + std::string key; + std::string version; + bool connection_upgrade = false; + bool upgrade = false; + const size_t request_line_end = request.find("\r\n"); + const std::string request_line = request.substr(0, request_line_end); + const size_t target_end = request_line.find(' ', 4); + const bool valid_request_line = request_line_end != std::string::npos + && request_line.compare(0, 4, "GET ") == 0 + && target_end != std::string::npos + && target_end > 4 + && target_end == request_line.rfind(' ') + && request_line.substr(target_end + 1) == "HTTP/1.1"; + size_t pos = request_line_end; + + while (pos != std::string::npos) { + const size_t end = request.find("\r\n", pos + 2); + if (end == std::string::npos || end == pos + 2) { + break; + } + + const std::string line = request.substr(pos + 2, end - pos - 2); + const size_t colon = line.find(':'); + if (colon != std::string::npos) { + const std::string name = to_lower(trim(line.substr(0, colon))); + const std::string value = trim(line.substr(colon + 1)); + + if (name == "sec-websocket-key") { + key = value; + } else if (name == "sec-websocket-version") { + version = value; + } else if (name == "connection") { + connection_upgrade = connection_upgrade + || contains_token(value, "upgrade"); + } else if (name == "upgrade") { + upgrade = upgrade || contains_token(value, "websocket"); + } + } + + pos = end; + } + + if (!complete || !valid_request_line || !connection_upgrade || !upgrade + || version != "13" || !valid_websocket_key(key)) { + send_all(socket, + "HTTP/1.1 400 Bad Request\r\n" + "Connection: close\r\n" + "Content-Length: 0\r\n" + "\r\n"); + return false; + } + + // deliberately echoes back no extension or subprotocol: naming one the client did + // not offer is a handshake failure, and none of them are wanted here + return send_all(socket, + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: " + accept_key(key) + "\r\n" + "\r\n"); + } + + // everything one connection needs; wslay hands this back to the callbacks + struct Session { + SOCKET socket = INVALID_SOCKET; + Controller *controller = nullptr; + ClientState *state = nullptr; + bool failed = false; + }; + + ssize_t recv_callback(wslay_event_context_ptr ctx, uint8_t *buffer, size_t length, + int flags, void *user_data) { + + (void) flags; + + auto *session = static_cast(user_data); + const int read = recv(session->socket, reinterpret_cast(buffer), + static_cast(length), 0); + + if (read > 0) { + return read; + } + + // the socket is non-blocking, so an empty one has to read as "nothing yet" + // rather than as a dead peer + if (read < 0 && WSAGetLastError() == WSAEWOULDBLOCK) { + wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK); + return -1; + } + + wslay_event_set_error(ctx, + read == 0 ? WSLAY_ERR_NO_MORE_MSG : WSLAY_ERR_CALLBACK_FAILURE); + return -1; + } + + ssize_t send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t length, + int flags, void *user_data) { + + (void) flags; + + auto *session = static_cast(user_data); + const int sent = send(session->socket, reinterpret_cast(data), + static_cast(length), 0); + + if (sent > 0) { + return sent; + } + + if (sent < 0 && WSAGetLastError() == WSAEWOULDBLOCK) { + wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK); + return -1; + } + + wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); + return -1; + } + + void on_msg_recv(wslay_event_context_ptr ctx, + const struct wslay_event_on_msg_recv_arg *arg, void *user_data) { + + auto *session = static_cast(user_data); + + // pings and closes are wslay's business, it answers them itself + if (wslay_is_ctrl_frame(arg->opcode)) { + return; + } + + if (arg->opcode != WSLAY_BINARY_FRAME) { + log_warning("api::websocket", "ignoring a non-binary message"); + return; + } + + std::vector in(arg->msg, arg->msg + arg->msg_length); + std::vector out; + + if (session->state->cipher) { + session->state->cipher->crypt( + reinterpret_cast(in.data()), in.size()); + } + + session->controller->process_request(session->state, &in, &out); + + if (session->state->cipher) { + session->state->cipher->crypt( + reinterpret_cast(out.data()), out.size()); + } + + wslay_event_msg reply {}; + reply.opcode = WSLAY_BINARY_FRAME; + reply.msg = reinterpret_cast(out.data()); + reply.msg_length = out.size(); + + if (wslay_event_queue_msg(ctx, &reply) != 0) { + session->failed = true; + return; + } + + Controller::process_password_change(session->state); + } } - /* - * Client class declaration - */ - class WebSocketClient : public web_socket_client { - - // required class header - HEADSOCKET_CLIENT(WebSocketClient, web_socket_client); - - private: - ClientState *state = nullptr; - - protected: - bool async_received_data(const data_block &db, uint8_t *ptr, size_t length) override; - - void on_accept() override; - void on_disconnect() override; - }; - - /* - * Server class declaration - */ - class WebSocketServer : public web_socket_server { - HEADSOCKET_SERVER(WebSocketServer, web_socket_server); - public: - WebSocketController *websocket; - - protected: - bool handshake(connection &conn) override { - - // headsocket runs the handshake on its single accept thread with a blocking - // recv, so a peer that connects and then says nothing would park that thread and - // leave every later connection sitting unaccepted in the backlog - set_recv_timeout(conn, handshake_timeout_ms); - const bool accepted = base_t::handshake(conn); - - // from here the client thread owns the socket and wants to block on reads - set_recv_timeout(conn, 0); - - return accepted; - } - }; - - void api::WebSocketServer::init() {} - - /* - * Controller state so we don't have to import headsocket stuff in our header - */ struct WebSocketControllerState { - std::shared_ptr server; + struct Client { + std::thread thread; + SOCKET socket = INVALID_SOCKET; + bool active = false; + }; + + Controller *controller = nullptr; + unsigned short port = 0; + SOCKET listener = INVALID_SOCKET; + bool wsa_started = false; + std::atomic_bool running { false }; + std::thread acceptor; + std::mutex clients_m; + std::array clients; + + bool open_listener(); + void accept_worker(); + void client_worker(int slot, SOCKET socket, std::string address); + void stop(); }; + bool WebSocketControllerState::open_listener() { + WSADATA wsa_data; + const int error = WSAStartup(MAKEWORD(2, 2), &wsa_data); + if (error != 0) { + log_warning("api::websocket", "WSAStartup() returned {}", error); + return false; + } + this->wsa_started = true; + + this->listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (this->listener == INVALID_SOCKET) { + log_warning("api::websocket", "socket() returned {}", WSAGetLastError()); + return false; + } + + sockaddr_in address {}; + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons(this->port); + + if (bind(this->listener, reinterpret_cast(&address), sizeof(address)) + == SOCKET_ERROR) { + log_warning("api::websocket", "bind() returned {}", WSAGetLastError()); + return false; + } + + if (listen(this->listener, server_backlog) == SOCKET_ERROR) { + log_warning("api::websocket", "listen() returned {}", WSAGetLastError()); + return false; + } + + return true; + } + + void WebSocketControllerState::accept_worker() { + + while (this->running) { + sockaddr_in client_address {}; + int client_address_size = sizeof(sockaddr_in); + + const SOCKET client = accept( + this->listener, reinterpret_cast(&client_address), + &client_address_size); + + if (client == INVALID_SOCKET) { + // on shutdown the listener is closed under us; otherwise do not spin + if (this->running) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + continue; + } + + if (!this->running) { + closesocket(client); + break; + } + + // formatted by hand rather than with inet_ntop, which needs a newer Windows than + // the XP toolchain targets, or inet_ntoa, which answers from a shared buffer + const uint32_t raw = ntohl(client_address.sin_addr.s_addr); + const std::string address = fmt::format("{}.{}.{}.{}", + (raw >> 24) & 0xff, (raw >> 16) & 0xff, (raw >> 8) & 0xff, raw & 0xff); + + int slot = -1; + { + std::lock_guard lock(this->clients_m); + for (size_t i = 0; i < this->clients.size(); i++) { + if (!this->clients[i].active) { + this->clients[i].active = true; + this->clients[i].socket = client; + slot = static_cast(i); + break; + } + } + } + + if (slot < 0) { + log_warning("api::websocket", "client limit of {} hit", client_limit); + overlay::notifications::add_throttled( + overlay::notifications::Severity::Warning, + "api::websocket.client_limit", + notification_throttle_seconds, + fmt::format("API websocket refused: client limit reached ({})", address)); + closesocket(client); + continue; + } + + // this thread is the only one that touches the thread objects, so the slot's + // previous occupant gets reaped here rather than being detached + if (this->clients[slot].thread.joinable()) { + this->clients[slot].thread.join(); + } + + // the handshake runs on the client thread on purpose: doing it here would put + // every later connection behind whatever this one is waiting for + this->clients[slot].thread = std::thread([this, slot, client, address] { + this->client_worker(slot, client, address); + }); + } + } + + void WebSocketControllerState::client_worker(int slot, SOCKET socket, std::string address) { + + // wslay writes a frame header and its payload as separate sends, so leaving Nagle on + // holds the payload back until the peer acknowledges the header, costing a delayed + // ack per message; requests here are small and latency sensitive + int nodelay = 1; + setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast(&nodelay), sizeof(nodelay)); + + set_recv_timeout(socket, handshake_timeout_ms); + + if (handshake(socket)) { + // wslay reads until the socket would block, so leaving it blocking would make + // every reply wait out a receive timeout before the send got a turn + u_long non_blocking = 1; + ioctlsocket(socket, FIONBIO, &non_blocking); + + Session session; + session.socket = socket; + session.controller = this->controller; + session.state = new ClientState(); + this->controller->init_state(session.state); + + log_info("api::websocket", "client connected: {}", address); + overlay::notifications::add( + overlay::notifications::Severity::Success, + fmt::format("API websocket client connected ({})", address)); + + wslay_event_callbacks callbacks {}; + callbacks.recv_callback = recv_callback; + callbacks.send_callback = send_callback; + callbacks.on_msg_recv_callback = on_msg_recv; + + wslay_event_context_ptr ctx = nullptr; + if (wslay_event_context_server_init(&ctx, &callbacks, &session) == 0) { + wslay_event_config_set_max_recv_msg_length(ctx, message_size_limit); + while (this->running && !session.failed + && (wslay_event_want_read(ctx) || wslay_event_want_write(ctx))) { + + fd_set read_set; + fd_set write_set; + FD_ZERO(&read_set); + FD_ZERO(&write_set); + + if (wslay_event_want_read(ctx)) { + FD_SET(socket, &read_set); + } + if (wslay_event_want_write(ctx)) { + FD_SET(socket, &write_set); + } + + // bounded so a silent connection still notices us shutting down + timeval timeout {}; + timeout.tv_usec = idle_poll_ms * 1000; + + if (select(0, &read_set, &write_set, nullptr, &timeout) < 0) { + break; + } + + if (FD_ISSET(socket, &read_set) && wslay_event_recv(ctx) != 0) { + break; + } + + // unconditional, so a reply queued by the read above goes out now + if (wslay_event_send(ctx) != 0) { + break; + } + } + + wslay_event_context_free(ctx); + } + + Controller::free_state(session.state); + delete session.state; + + log_info("api::websocket", "client disconnected: {}", address); + overlay::notifications::add( + overlay::notifications::Severity::Info, + fmt::format("API websocket client disconnected ({})", address)); + } + + closesocket(socket); + + std::lock_guard lock(this->clients_m); + this->clients[slot].socket = INVALID_SOCKET; + this->clients[slot].active = false; + } + + void WebSocketControllerState::stop() { + this->running = false; + + if (this->listener != INVALID_SOCKET) { + closesocket(this->listener); + this->listener = INVALID_SOCKET; + } + + // drops the client threads out of their blocking send/recv + { + std::lock_guard lock(this->clients_m); + for (auto &client : this->clients) { + if (client.socket != INVALID_SOCKET) { + ::shutdown(client.socket, SD_BOTH); + } + } + } + + if (this->acceptor.joinable()) { + this->acceptor.join(); + } + + // joining is what guarantees no client thread outlives this object + for (auto &client : this->clients) { + if (client.thread.joinable()) { + client.thread.join(); + } + } + + if (this->wsa_started) { + WSACleanup(); + this->wsa_started = false; + } + } + WebSocketController::WebSocketController(Controller *controller, uint16_t port) { this->controller = controller; - // create state this->state = new WebSocketControllerState(); + this->state->controller = controller; + this->state->port = port; - // start server - this->state->server = WebSocketServer::create(port); - this->state->server->websocket = this; - if (this->state->server->is_running()) { - log_info("api::websocket", "server listening on port: {}", port); - } else { + if (!this->state->open_listener()) { log_warning("api::websocket", "server failed to listen on port: {}", port); + return; } + + this->state->running = true; + this->state->acceptor = std::thread([this] { + this->state->accept_worker(); + }); + + log_info("api::websocket", "server listening on port: {}", port); } WebSocketController::~WebSocketController() { - - // stop server - this->state->server->stop(); - - // delete state + this->state->stop(); delete this->state; + this->state = nullptr; } void WebSocketController::free_socket() { - this->state->server->stop(); - } - - void WebSocketClient::on_accept() { - web_socket_client::on_accept(); - - // get pointer to server - auto srv = reinterpret_cast(server().get()); - if (!srv || !srv->websocket) { - log_fatal("api::websocket", "on_accept has no server"); - } - - // check for init - state = new ClientState(); - srv->websocket->controller->init_state(state); - - // log connection - log_info("api::websocket", "client connected"); - overlay::notifications::add( - overlay::notifications::Severity::Success, - "API websocket client connected"); - } - - void WebSocketClient::on_disconnect() { - - // log disconnection - log_info("api::websocket", "client disconnected"); - overlay::notifications::add( - overlay::notifications::Severity::Info, - "API websocket client disconnected"); - - // get pointer to server - auto srv = reinterpret_cast(server().get()); - if (!srv || !srv->websocket) { - log_fatal("api::websocket", "on_disconnect has no server"); - } - - // clean up state - srv->websocket->controller->free_state(state); - delete state; - state = nullptr; - - // call super - web_socket_client::on_disconnect(); - } - - /* - * This is where business actually happens, gets called on every datablock receive - */ - bool WebSocketClient::async_received_data(const data_block &db, uint8_t *ptr, size_t length) { - - // get pointer to server - auto srv = reinterpret_cast(server().get()); - if (!srv || !srv->websocket) { - log_fatal("api::websocket", "received datablock without server"); - } - - // check state - if (!state) { - log_fatal("api::websocket", "client with no state received datablock"); - } - - // check datablock type - switch (db.op) { - case opcode::binary: { - - // allocate buffers - std::vector in(ptr, ptr + length); - std::vector out; - - // crypt in-data - if (state->cipher) { - state->cipher->crypt(reinterpret_cast(in.data()), in.size()); - } - - // process request - srv->websocket->controller->process_request(state, &in, &out); - - // crypt out-data - if (state->cipher) { - state->cipher->crypt(reinterpret_cast(out.data()), out.size()); - } - - // send answer - push(out.data(), out.size()); - - // check for password change - srv->websocket->controller->process_password_change(state); - - break; - } - default: - log_warning("api::websocket", "datablock received with non-binary type"); - break; - } - - // always consume the datablock, nomnom - return true; + this->state->stop(); } } diff --git a/src/spice2x/external/headsocket.h b/src/spice2x/external/headsocket.h deleted file mode 100644 index e96a349..0000000 --- a/src/spice2x/external/headsocket.h +++ /dev/null @@ -1,2192 +0,0 @@ -/*///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -***** HeadSocket v0.1, created by Jan Pinter **** Minimalistic header only WebSocket server implementation in C++ ***** - Sources: https://github.com/P-i-N/HeadSocket, contact: Pinter.Jan@gmail.com - PUBLIC DOMAIN - no warranty implied or offered, use this at your own risk - ------------------------------------------------------------------------------------------------------------------------ - -Usage: -- use this as a regular header file, but in EXACTLY one of your C++ files (ie. main.cpp) you must define - HEADSOCKET_IMPLEMENTATION beforehand, like this: - - #define HEADSOCKET_IMPLEMENTATION - #include - -/*///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#ifndef __HEADSOCKET_H__ -#define __HEADSOCKET_H__ - -#include -#include -#include -#include - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace headsocket { - -/* Forward declarations */ -class connection; -class basic_tcp_server; -class basic_tcp_client; -class tcp_client; -class async_tcp_client; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -template using ptr = std::shared_ptr; -typedef size_t id_t; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace detail { - -/* Forward declarations */ -struct connection_impl; -struct basic_tcp_server_impl; -struct basic_tcp_client_impl; -struct async_tcp_client_impl; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -static bool handshake_websocket(connection &conn); - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -struct less_comparator -{ - bool operator()(const std::string &s1, const std::string &s2) const - { - return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), [](char c1, char c2) -> bool - { - return tolower(c1) < tolower(c2); - }); - } -}; - -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -class connection -{ -public: - connection(const detail::connection_impl &impl); - ~connection(); - - detail::connection_impl *impl() const { return _p.get(); } - - bool is_valid() const; - id_t id() const; - - size_t write(const void *ptr, size_t length); - size_t write(const std::string &text) { return write(text.c_str(), text.length()); } - size_t read(void *ptr, size_t length); - - bool force_write(const void *ptr, size_t length); - bool force_read(void *ptr, size_t length); - - bool read_line(std::string &output); - -private: - std::unique_ptr _p; -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -enum class opcode -{ - continuation = 0x00, - text = 0x01, - binary = 0x02, - connection_close = 0x08, - ping = 0x09, - pong = 0x0A -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -struct data_block -{ - opcode op; - size_t offset; - size_t length = 0; - bool is_completed = false; - - data_block(opcode opc, size_t off) - : op(opc) - , offset(off) - { - - } -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -class basic_tcp_server : public std::enable_shared_from_this -{ -public: - int port() const; - void stop(); - bool is_running() const; - bool disconnect(ptr client); - bool disconnect(id_t id); - -protected: - struct protected_tag { }; - - virtual void init() { } - - explicit basic_tcp_server(int port); - virtual ~basic_tcp_server(); - - virtual bool handshake(connection &conn) = 0; - virtual ptr accept(connection &conn) = 0; - virtual void client_connected(ptr client) = 0; - virtual void client_disconnected(ptr client) = 0; - - std::unique_ptr _p; - -private: - template friend class tcp_server; - - void remove_disconnected() const; - - size_t acquire_clients() const; - void release_clients() const; - - ptr client_at(size_t index) const; - size_t num_clients() const; - - void accept_thread(); - void disconnect_thread(); -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - class basic_tcp_client - { - public: - enum { is_basic_tcp_client }; - - static const size_t invalid_operation = static_cast(-1); - - virtual ~basic_tcp_client(); - - bool disconnect(); - bool is_connected() const; - - ptr server() const; - id_t id() const; - - protected: - struct protected_tag { }; - - friend class basic_tcp_server; - - virtual void on_accept() { } - virtual void on_disconnect() { } - - basic_tcp_client(const std::string &address, int port); - basic_tcp_client(ptr server, connection &conn); - - std::unique_ptr _p; - }; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -struct protected_tag { }; - -#define HEADSOCKET_SERVER(className, baseClassName) \ - protected: \ - explicit className(int port): baseClassName(port) { init(); } \ - public: \ - typedef baseClassName base_t; \ - className(const protected_tag &, int port): className(port) { } \ - static headsocket::ptr create(int port) { return std::make_shared(protected_tag{}, port); } \ - protected: \ - void init() - -template -class tcp_server : public basic_tcp_server -{ - HEADSOCKET_SERVER(tcp_server, basic_tcp_server) { } - -public: - typedef T client_t; - typedef ptr client_ptr; - - virtual ~tcp_server() - { - base_t::stop(); - } - - class enumerator - { - public: - explicit enumerator(const tcp_server &server) - : _server(server) - , _count(server.acquire_clients()) - { - - } - - ~enumerator() - { - _server.release_clients(); - } - - const tcp_server &server() const { return _server; } - size_t size() const { return _count; } - - struct iterator - { - enumerator &e; - size_t index; - - iterator(enumerator &enu, size_t idx) - : e(enu) - , index(idx) - { - - } - - bool operator==(const iterator &iter) const { return iter.index == index && &iter.e == &e; } - bool operator!=(const iterator &iter) const { return iter.index != index || &iter.e != &e; } - ptr operator*() const { return std::dynamic_pointer_cast(e.server().client_at(index)); } - - iterator &operator++() - { - ++index; - return *this; - } - }; - - iterator begin() { return iterator(*this, 0); } - iterator end() { return iterator(*this, _count); } - - private: - const tcp_server &_server; - size_t _count; - }; - - enumerator clients() const { return enumerator(*this); } - -protected: - bool handshake(connection &conn) override { return true; } - - virtual void client_connected(client_ptr client) { } - - virtual void client_disconnected(client_ptr client) { } - -private: - enum { needs_basic_tcp_client = T::is_basic_tcp_client }; - - ptr accept(connection &conn) override - { - ptr newClient = T::create(shared_from_this(), conn); - return newClient->is_connected() ? newClient : nullptr; - } - - void client_connected(ptr client) override - { - client_connected(std::dynamic_pointer_cast(client)); - } - - void client_disconnected(ptr client) override - { - client_disconnected(std::dynamic_pointer_cast(client)); - } -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#define __HEADSOCKET_CLIENT_STATIC_CTORS(className) \ - className(const protected_tag &, const std::string &address, int port): className(address, port) { } \ - className(const protected_tag &, headsocket::ptr server, headsocket::connection &conn): className(server, conn) { } \ - static headsocket::ptr create(const std::string &address, int port) { return std::make_shared(protected_tag{}, address, port); } \ - static headsocket::ptr create(headsocket::ptr server, headsocket::connection &conn) { return std::make_shared(protected_tag{}, server, conn); } - -#define HEADSOCKET_CLIENT_BASE(className) \ - protected: \ - className(const std::string &address, int port); \ - className(ptr server, connection &conn); \ - public: \ - __HEADSOCKET_CLIENT_STATIC_CTORS(className) - -#define HEADSOCKET_CLIENT(className, baseClassName) \ - protected: \ - className(const std::string &address, int port): baseClassName(address, port) { } \ - className(headsocket::ptr server, headsocket::connection &conn): baseClassName(server, conn) { } \ - public: \ - __HEADSOCKET_CLIENT_STATIC_CTORS(className) - -class tcp_client : public basic_tcp_client -{ - HEADSOCKET_CLIENT_BASE(tcp_client) - -public: - typedef basic_tcp_client base_t; - enum { is_tcp_client }; - - virtual ~tcp_client(); - - virtual size_t write(const void *ptr, size_t length); - virtual size_t read(void *ptr, size_t length); - - bool force_write(const void *ptr, size_t length); - bool force_read(void *ptr, size_t length); - - bool read_line(std::string &output); -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -class async_tcp_client : public basic_tcp_client -{ - HEADSOCKET_CLIENT_BASE(async_tcp_client) - -public: - typedef basic_tcp_client base_t; - enum { is_async_tcp_client }; - - virtual ~async_tcp_client(); - - void push(const void *ptr, size_t length); - void push(const std::string &text); - size_t peek() const; - size_t pop(void *ptr, size_t length); - -protected: - void on_accept() override { init_threads(); } - void on_disconnect() override { kill_threads(); } - - virtual void init_threads(); - - virtual size_t async_write_handler(uint8_t *ptr, size_t length); - virtual size_t async_read_handler(uint8_t *ptr, size_t length); - - virtual bool async_received_data(const data_block &db, uint8_t *ptr, size_t length) { return false; } - - virtual void push(const void *ptr, size_t length, opcode opcode); - - void kill_threads(); - - std::unique_ptr _ap; - -private: - void write_thread(); - void read_thread(); -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -class web_socket_client : public async_tcp_client -{ - HEADSOCKET_CLIENT_BASE(web_socket_client) - -public: - static const size_t frame_size_limit = 128 * 1024; - - typedef async_tcp_client base_t; - enum { is_web_socket_client }; - - virtual ~web_socket_client(); - - size_t peek(opcode *op) const; - -protected: - size_t async_write_handler(uint8_t *ptr, size_t length) override; - size_t async_read_handler(uint8_t *ptr, size_t length) override; - -private: - struct frame_header - { - bool fin; - opcode op; - bool masked; - size_t payload_length; - uint32_t masking_key; - - size_t write(uint8_t *ptr, size_t length) const; - size_t read(const uint8_t *ptr, size_t length); - }; - - size_t _payload_size = 0; - frame_header _current_header; -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -template -class web_socket_server : public tcp_server -{ - HEADSOCKET_SERVER(web_socket_server, tcp_server) { } - -public: - virtual ~web_socket_server() - { - base_t::stop(); - } - -protected: - bool handshake(connection &conn) override { return detail::handshake_websocket(conn); } - -private: - enum { needs_web_socket_client = T::is_web_socket_client }; -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -class http_server : public tcp_server -{ - HEADSOCKET_SERVER(http_server, tcp_server) { } - -public: - ~http_server() - { - stop(); - } - - struct response - { - std::string content_type = "text/html"; - std::string message = ""; - }; - - struct parameter - { - std::string name; - std::string value; - bool boolean; - int integer; - double real; - }; - - typedef std::map parameters_t; - -protected: - virtual bool request(const std::string &path, const parameters_t ¶ms, response &resp) { return false; } - -private: - bool handshake(connection &conn) final override; - - ptr accept(connection &conn) final override { return nullptr; } - void client_connected(client_ptr client) final override { } - void client_disconnected(client_ptr client) final override { } -}; - -} - -#endif // __HEADSOCKET_H__ - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#ifdef HEADSOCKET_IMPLEMENTATION -#ifndef __HEADSOCKET_H_IMPL__ -#define __HEADSOCKET_H_IMPL__ - -#include -#include -#include -#include -#include -#include -#include -#include - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#ifndef HEADSOCKET_PLATFORM_OVERRIDE -#ifdef _WIN32 -#define HEADSOCKET_PLATFORM_WINDOWS -#elif __ANDROID__ -#define HEADSOCKET_PLATFORM_ANDROID -#define HEADSOCKET_PLATFORM_NIX -#elif __APPLE__ -#include "TargetConditionals.h" -#ifdef TARGET_OS_MAC -#define HEADSOCKET_PLATFORM_MAC -#endif -#elif __linux -#define HEADSOCKET_PLATFORM_NIX -#elif __unix -#define HEADSOCKET_PLATFORM_NIX -#elif __posix -#define HEADSOCKET_PLATFORM_NIX -#else -#error Unsupported platform! -#endif -#endif - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#if defined(HEADSOCKET_PLATFORM_WINDOWS) -#pragma comment(lib, "ws2_32.lib") -#include -#include -#include -#include - -#elif defined(HEADSOCKET_PLATFORM_ANDROID) || defined(HEADSOCKET_PLATFORM_NIX) -#include -#include -#include -#include -#include -#endif - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#define HEADSOCKET_LOCK_SUFFIX(var, suffix) std::lock_guard __scope_lock##suffix(var); -#define HEADSOCKET_LOCK_SUFFIX2(var, suffix) HEADSOCKET_LOCK_SUFFIX(var, suffix) -#define HEADSOCKET_LOCK(var) HEADSOCKET_LOCK_SUFFIX2(var, __LINE__) - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace headsocket { - -namespace detail { -#if defined(HEADSOCKET_PLATFORM_WINDOWS) -typedef SOCKET socket_type; -static const int socket_error = SOCKET_ERROR; -static const SOCKET invalid_socket = INVALID_SOCKET; -void close_socket(socket_type s) { closesocket(s); } -#define HEADSOCKET_SPRINTF sprintf_s -#elif defined(HEADSOCKET_PLATFORM_ANDROID) || defined(HEADSOCKET_PLATFORM_NIX) -typedef int socket_type; -static const int socket_error = -1; -static const int invalid_socket = -1; -void close_socket(socket_type s) { close(s); } -#define HEADSOCKET_SPRINTF sprintf -#endif -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace detail { - -class sha1 -{ -public: - typedef uint32_t digest32_t[5]; - typedef uint8_t digest8_t[20]; - - inline static uint32_t rotate_left(uint32_t value, size_t count) { return (value << count) ^ (value >> (32 - count)); } - - sha1() - { - _digest[0] = 0x67452301; - _digest[1] = 0xEFCDAB89; - _digest[2] = 0x98BADCFE; - _digest[3] = 0x10325476; - _digest[4] = 0xC3D2E1F0; - } - - ~sha1() - { - - } - - void process_byte(uint8_t octet) - { - _block[_block_byte_index++] = octet; - ++_byte_count; - - if (_block_byte_index == 64) - { - _block_byte_index = 0; - process_block(); - } - } - - void process_block(const void *start, const void *end) - { - const uint8_t *begin = static_cast(start); - - while (begin != end) - process_byte(*begin++); - } - - void process_bytes(const void *data, size_t len) - { - process_block(data, static_cast(data) + len); - } - - const uint32_t *get_digest(digest32_t digest) - { - size_t bitCount = _byte_count * 8; - process_byte(0x80); - - if (_block_byte_index > 56) - { - while (_block_byte_index != 0) - process_byte(0); - - while (_block_byte_index < 56) - process_byte(0); - } - else - while (_block_byte_index < 56) - process_byte(0); - - process_byte(0); - process_byte(0); - process_byte(0); - process_byte(0); - - for (int i = 24; i >= 0; i -= 8) - process_byte(static_cast((bitCount >> i) & 0xFF)); - - memcpy(digest, _digest, 5 * sizeof(uint32_t)); - return digest; - } - - const uint8_t *get_digest_bytes(digest8_t digest) - { - digest32_t d32; - get_digest(d32); - size_t s[] = { 24, 16, 8, 0 }; - - for (size_t i = 0, j = 0; i < 20; ++i, j = i % 4) - digest[i] = ((d32[i >> 2] >> s[j]) & 0xFF); - - return digest; - } - -private: - void process_block() - { - uint32_t w[80], s[] = { 24, 16, 8, 0 }; - - for (size_t i = 0, j = 0; i < 64; ++i, j = i % 4) - w[i / 4] = j ? (w[i / 4] | (_block[i] << s[j])) : (_block[i] << s[j]); - - for (size_t i = 16; i < 80; i++) - w[i] = rotate_left((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1); - - digest32_t dig = { _digest[0], _digest[1], _digest[2], _digest[3], _digest[4] }; - - for (size_t f, k, i = 0; i < 80; ++i) - { - if (i < 20) - f = (dig[1] & dig[2]) | (~dig[1] & dig[3]), k = 0x5A827999; - else if (i < 40) - f = dig[1] ^ dig[2] ^ dig[3], k = 0x6ED9EBA1; - else if (i < 60) - f = (dig[1] & dig[2]) | (dig[1] & dig[3]) | (dig[2] & dig[3]), k = 0x8F1BBCDC; - else - f = dig[1] ^ dig[2] ^ dig[3], k = 0xCA62C1D6; - - uint32_t temp = static_cast(rotate_left(dig[0], 5) + f + dig[4] + k + w[i]); - dig[4] = dig[3]; - dig[3] = dig[2]; - dig[2] = rotate_left(dig[1], 30); - dig[1] = dig[0]; - dig[0] = temp; - } - - for (size_t i = 0; i < 5; ++i) - _digest[i] += dig[i]; - } - - digest32_t _digest; - uint8_t _block[64]; - size_t _block_byte_index = 0; - size_t _byte_count = 0; -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -struct utils -{ - static std::string base64_encode(const void *ptr, size_t length) - { - static const char *encoding_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - static size_t mod_table[] = { 0, 2, 1 }; - - std::string result(4 * ((length + 2) / 3), '='); - - if (ptr && length) - { - const uint8_t *input = reinterpret_cast(ptr); - - for (size_t i = 0, j = 0, triplet = 0; i < length; triplet = 0) - { - for (size_t k = 0; k < 3; ++k) - triplet = (triplet << 8) | (i < length ? static_cast(input[i++]) : 0); - - for (size_t k = 4; k--;) - result[j++] = encoding_table[(triplet >> k * 6) & 0x3F]; - } - - for (size_t i = 0; i < mod_table[length % 3]; i++) - result[result.length() - 1 - i] = '='; - } - - return result; - } - - static size_t xor32(uint32_t key, void *ptr, size_t length) - { - uint8_t *data = reinterpret_cast(ptr); - uint8_t *mask = reinterpret_cast(&key); - - for (size_t i = 0; i < length; ++i, ++data) - *data = (*data) ^ mask[i % 4]; - - return length; - } - - static std::string url_encode(const std::string &str) - { - std::ostringstream result; - result.fill('0'); - result << std::hex; - - for (std::string::const_iterator i = str.begin(), n = str.end(); i != n; ++i) - { - auto c = (*i); - - if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') - result << c; - else - result << '%' << std::setw(2) << static_cast(c); - } - - return result.str(); - } - - static std::string url_decode(const std::string &str) - { - std::ostringstream result; - - for (size_t i = 0, S = str.length(); i < S; ++i) - { - auto c = str[i]; - - if (c == '%') - { - char hexBuff[3] = { 0, 0, 0 }; - hexBuff[0] = str[++i]; - hexBuff[1] = str[++i]; - - int value; - sscanf(hexBuff, "%x", &value); - result << static_cast(value); - } - else if (c == '+') - result << ' '; - else - result << c; - } - - return result.str(); - } - - static uint16_t swap16bits(uint16_t x) { return ((x & 0x00FF) << 8) | ((x & 0xFF00) >> 8); } - - static uint32_t swap32bits(uint32_t x) - { - return ((x & 0x000000FF) << 24) | ((x & 0x0000FF00) << 8) | ((x & 0x00FF0000) >> 8) | ((x & 0xFF000000) >> 24); - } - - static uint64_t swap64bits(uint64_t x) - { - return - ((x & 0x00000000000000FFULL) << 56) | ((x & 0x000000000000FF00ULL) << 40) | ((x & 0x0000000000FF0000ULL) << 24) | - ((x & 0x00000000FF000000ULL) << 8) | ((x & 0x000000FF00000000ULL) >> 8) | ((x & 0x0000FF0000000000ULL) >> 24) | - ((x & 0x00FF000000000000ULL) >> 40) | ((x & 0xFF00000000000000ULL) >> 56); - } - - static std::string trim(const std::string &str) - { - size_t trimLeft = 0, trimRight = str.length() - 1; - - while (trimLeft < str.length() && isspace(str[trimLeft])) - ++trimLeft; - - while (trimRight < str.length() && isspace(str[trimRight])) - --trimRight; - - return (trimRight >= str.length() || trimLeft >= str.length() || trimRight < trimLeft) - ? std::string("") - : str.substr(trimLeft, trimRight - trimLeft + 1); - } - - static std::string cut_front(std::string &str, char delimiter = ' ', bool first = true, bool hungry = true) - { - std::string result; - - auto pos = first ? str.find(delimiter) : str.rfind(delimiter); - if (pos == std::string::npos) - { - if (hungry) - { - result = str; - str = ""; - } - } - else - { - result = str.substr(0, pos); - str = str.substr(pos + 1); - } - - return result; - } - - static std::string cut_back(std::string &str, char delimiter = ' ', bool first = true, bool hungry = true) - { - std::string result; - - auto pos = first ? str.rfind(delimiter) : str.find(delimiter); - if (pos == std::string::npos) - { - if (hungry) - { - result = str; - str = ""; - } - } - else - { - result = str.substr(pos + 1); - str = str.substr(0, pos); - } - - return result; - } -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -struct critical_section -{ - mutable std::atomic_bool consumer_lock; - - critical_section() - { - consumer_lock = false; - } - - void lock() const { while (consumer_lock.exchange(true)); } - void unlock() const { consumer_lock = false; } -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -template -struct lockable_value : M -{ - T value; - - T *operator->() { return &value; } - const T *operator->() const { return &value; } -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -struct semaphore -{ - mutable std::atomic_size_t count; - mutable std::mutex mutex; - mutable std::condition_variable cv; - - semaphore() - { - count = 0; - } - - void lock(size_t minCount = 0) const - { - std::unique_lock lock(mutex); - cv.wait(lock, [&]()->bool { return count > minCount; }); - lock.release(); - } - - void unlock() - { - mutex.unlock(); - } - - void notify() - { - { - std::lock_guard lock(mutex); - ++count; - } - - cv.notify_one(); - } - - void consume() const - { - if (count) - --count; - } -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -struct data_block_buffer -{ - std::vector blocks; - std::vector buffer; - - data_block_buffer() - { - buffer.reserve(65536); - } - - data_block &block_begin(opcode op) - { - blocks.emplace_back(op, buffer.size()); - return blocks.back(); - } - - data_block &block_end() - { - blocks.back().is_completed = true; - return blocks.back(); - } - - void block_remove() - { - if (blocks.empty()) - return; - - buffer.resize(blocks.back().offset); - blocks.pop_back(); - } - - void write(const void *ptr, size_t length) - { - if (!length) - return; - - buffer.resize(buffer.size() + length); - memcpy(buffer.data() + buffer.size() - length, reinterpret_cast(ptr), length); - blocks.back().length += length; - } - - size_t read(void *ptr, size_t length) - { - if (!ptr || blocks.empty() || !blocks.front().is_completed) - return 0; - - data_block &db = blocks.front(); - size_t result = db.length >= length ? length : db.length; - - if (result) - { - memcpy(ptr, buffer.data() + db.offset, result); - buffer.erase(buffer.begin(), buffer.begin() + result); - } - - if (!(db.length -= result)) - blocks.erase(blocks.begin()); - else - blocks.front().op = opcode::continuation; - - if (result) for (auto &block : blocks) if (block.offset > db.offset) - block.offset -= result; - - return result; - } - - size_t peek(opcode *op = nullptr) const - { - if (blocks.empty() || !blocks.front().is_completed) - return 0; - - if (op) - *op = blocks.front().op; - - return blocks.front().length; - } -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -//--------------------------------------------------------------------------------------------------------------------- -bool handshake_websocket(connection &conn) -{ - std::string line, key; - - while (conn.read_line(line)) - { - if (line.empty()) - break; - - if (!memcmp(line.c_str(), "Sec-WebSocket-Key: ", 19)) - key = line.substr(19); - } - - if (key.empty()) - return false; - - key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - - detail::sha1 sha; - detail::sha1::digest8_t digest; - sha.process_bytes(key.c_str(), key.length()); - - std::string response = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "; - response += detail::utils::base64_encode(sha.get_digest_bytes(digest), 20); - response += "\r\n\r\n"; - - return conn.force_write(response.c_str(), response.length()); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#ifdef HEADSOCKET_PLATFORM_WINDOWS -void set_thread_name(const char *name) -{ -} -#else -void set_thread_name(const char *name) -{ - -} -#endif - -} // namespace detail; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace detail { - -struct connection_impl -{ - detail::socket_type socket = detail::invalid_socket; - sockaddr_in from; - size_t id = 0; - - void assign(const connection_impl &impl) - { - socket = impl.socket; - from = impl.from; - id = impl.id; - } - - void close() - { - if (socket != detail::invalid_socket) - { - detail::close_socket(socket); - socket = detail::invalid_socket; - } - } -}; - -} - -//--------------------------------------------------------------------------------------------------------------------- -connection::connection(const detail::connection_impl &impl) - : _p(std::make_unique()) -{ - _p->assign(impl); -} - -//--------------------------------------------------------------------------------------------------------------------- -connection::~connection() -{ - -} - -//--------------------------------------------------------------------------------------------------------------------- -bool connection::is_valid() const { return _p->socket != detail::invalid_socket; } - -//--------------------------------------------------------------------------------------------------------------------- -size_t connection::id() const { return _p->id; } - -//--------------------------------------------------------------------------------------------------------------------- -size_t connection::write(const void *ptr, size_t length) -{ - if (!is_valid()) - return detail::socket_error; - - if (!ptr || !length) - return 0; - - int result = send(_p->socket, static_cast(ptr), static_cast(length), 0); - - if (!result || result == detail::socket_error) - return 0; - - return static_cast(result); -} - -//--------------------------------------------------------------------------------------------------------------------- -bool connection::force_write(const void *ptr, size_t length) -{ - if (!is_valid()) - return false; - - if (!ptr) - return true; - - const char *chPtr = static_cast(ptr); - - while (length) - { - int result = send(_p->socket, chPtr, static_cast(length), 0); - - if (!result || result == detail::socket_error) - return false; - - length -= static_cast(result); - chPtr += result; - } - - return true; -} - -//--------------------------------------------------------------------------------------------------------------------- -size_t connection::read(void *ptr, size_t length) -{ - if (!is_valid()) - return detail::socket_error; - - if (!ptr || !length) - return 0; - - int result = recv(_p->socket, static_cast(ptr), static_cast(length), 0); - - if (!result || result == detail::socket_error) - return 0; - - return static_cast(result); -} - -//--------------------------------------------------------------------------------------------------------------------- -bool connection::force_read(void *ptr, size_t length) -{ - if (!is_valid()) - return false; - - if (!ptr) - return true; - - char *chPtr = static_cast(ptr); - - while (length) - { - int result = recv(_p->socket, chPtr, static_cast(length), 0); - - if (!result || result == detail::socket_error) - return false; - - length -= static_cast(result); - chPtr += result; - } - - return true; -} - -//--------------------------------------------------------------------------------------------------------------------- -bool connection::read_line(std::string &output) -{ - if (!is_valid()) - return false; - - output = ""; - - while (true) - { - char ch; - int r = recv(_p->socket, &ch, 1, 0); - - if (!r || r == detail::socket_error) - return false; - - if (ch == '\n') - break; - else if (ch != '\r') - output += ch; - } - - return true; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace detail { - -struct basic_tcp_client_ref -{ - size_t refCount = 0; - ptr client; - - basic_tcp_client_ref(ptr c) - : client(c) - { - - } -}; - -struct basic_tcp_server_impl -{ - std::atomic_bool isRunning; - std::atomic_bool disconnectThreadQuit; - sockaddr_in local; - detail::lockable_value> connections; - detail::semaphore disconnectSemaphore; - int port = 0; - detail::socket_type serverSocket = invalid_socket; - std::unique_ptr acceptThread; - std::unique_ptr disconnectThread; - id_t nextClientID = 1; - - basic_tcp_server_impl() - { - isRunning = false; - disconnectThreadQuit = false; - } -}; - -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -//--------------------------------------------------------------------------------------------------------------------- -basic_tcp_server::basic_tcp_server(int port) - : _p(std::make_unique()) -{ -#ifdef HEADSOCKET_PLATFORM_WINDOWS - WSADATA wsaData; - WSAStartup(0x101, &wsaData); -#endif - - _p->local.sin_family = AF_INET; - _p->local.sin_addr.s_addr = INADDR_ANY; - _p->local.sin_port = htons(static_cast(port)); - - _p->serverSocket = socket(AF_INET, SOCK_STREAM, 0); - - int opt_enable = 1; - setsockopt(_p->serverSocket, SOL_SOCKET, SO_REUSEADDR, (const char*) &opt_enable, sizeof(int)); - - if (bind(_p->serverSocket, reinterpret_cast(&_p->local), sizeof(_p->local)) != 0) - return; - - if (listen(_p->serverSocket, 8)) - return; - - _p->isRunning = true; - _p->port = port; - _p->acceptThread = std::make_unique(std::bind(&basic_tcp_server::accept_thread, this)); - _p->disconnectThread = std::make_unique(std::bind(&basic_tcp_server::disconnect_thread, this)); -} - -//--------------------------------------------------------------------------------------------------------------------- -basic_tcp_server::~basic_tcp_server() -{ - stop(); - -#ifdef HEADSOCKET_PLATFORM_WINDOWS - WSACleanup(); -#endif -} - -//--------------------------------------------------------------------------------------------------------------------- -int basic_tcp_server::port() const { return _p->port; } - -//--------------------------------------------------------------------------------------------------------------------- -void basic_tcp_server::stop() -{ - if (_p->isRunning.exchange(false)) - { - detail::close_socket(_p->serverSocket); - - { - acquire_clients(); - - for (size_t i = 0, S = num_clients(); i < S; ++i) - client_at(i)->disconnect(); - - release_clients(); - } - - if (_p->acceptThread) - { - _p->acceptThread->join(); - _p->acceptThread = nullptr; - } - - if (_p->disconnectThread) - { - _p->disconnectThreadQuit = true; - _p->disconnectSemaphore.notify(); - - _p->disconnectThread->join(); - _p->disconnectThread = nullptr; - } - } -} - -//--------------------------------------------------------------------------------------------------------------------- -bool basic_tcp_server::is_running() const { return _p->isRunning; } - -//--------------------------------------------------------------------------------------------------------------------- -bool basic_tcp_server::disconnect(ptr client) -{ - bool found = false; - - if (client) - { - { - HEADSOCKET_LOCK(_p->connections); - for (size_t i = 0, S = _p->connections->size(); i < S; ++i) - if (_p->connections->at(i).client == client) - { - found = true; - break; - } - } - - if (found && !client->disconnect()) - { - client_disconnected(client); - _p->disconnectSemaphore.notify(); - } - } - - return found; -} - -//--------------------------------------------------------------------------------------------------------------------- -bool basic_tcp_server::disconnect(id_t id) -{ - bool found = false; - - if (id) - { - ptr client; - - { - HEADSOCKET_LOCK(_p->connections); - for (size_t i = 0, S = _p->connections->size(); i < S; ++i) - if (_p->connections->at(i).client->id() == id) - { - client = _p->connections->at(i).client; - found = true; - break; - } - } - - if (found && !client->disconnect()) - { - client_disconnected(client); - _p->disconnectSemaphore.notify(); - } - } - - return found; -} - -//--------------------------------------------------------------------------------------------------------------------- -ptr basic_tcp_server::client_at(size_t index) const -{ - HEADSOCKET_LOCK(_p->connections); - return index < _p->connections->size() ? _p->connections->at(index).client : nullptr; -} - -//--------------------------------------------------------------------------------------------------------------------- -size_t basic_tcp_server::num_clients() const -{ - HEADSOCKET_LOCK(_p->connections); - return _p->connections->size(); -} - -//--------------------------------------------------------------------------------------------------------------------- -size_t basic_tcp_server::acquire_clients() const -{ - HEADSOCKET_LOCK(_p->connections); - - for (auto &clientRef : _p->connections.value) - ++clientRef.refCount; - - return _p->connections->size(); -} - -//--------------------------------------------------------------------------------------------------------------------- -void basic_tcp_server::release_clients() const -{ - HEADSOCKET_LOCK(_p->connections); - - for (auto &clientRef : _p->connections.value) - --clientRef.refCount; - - remove_disconnected(); -} - -//--------------------------------------------------------------------------------------------------------------------- -void basic_tcp_server::remove_disconnected() const -{ - size_t i = 0; - - while (i < _p->connections->size()) - { - auto &clientRef = _p->connections.value[i]; - - if (!clientRef.client->is_connected() && clientRef.refCount == 0) - { - clientRef.client->on_disconnect(); - _p->connections->erase(_p->connections->begin() + i); - } - else - ++i; - } -} - -//--------------------------------------------------------------------------------------------------------------------- -void basic_tcp_server::accept_thread() -{ - detail::set_thread_name("BaseTcpServer::acceptThread"); - - while (_p->isRunning) - { - detail::connection_impl conn_impl; - conn_impl.socket = ::accept(_p->serverSocket, reinterpret_cast(&conn_impl.from), nullptr); - conn_impl.id = _p->nextClientID++; - - if (!_p->nextClientID) - ++_p->nextClientID; - - if (!_p->isRunning) - break; - - if (conn_impl.socket != detail::invalid_socket) - { - connection conn(conn_impl); - - ptr newClient; - bool failed = false; - - if (handshake(conn)) - { - if ((newClient = accept(conn))) - { - newClient->on_accept(); - - HEADSOCKET_LOCK(_p->connections); - _p->connections->push_back(newClient); - } - else { - failed = true; - } - } - else { - failed = true; - } - - if (failed) - { - conn_impl.close(); - --_p->nextClientID; - - if (!_p->nextClientID) - --_p->nextClientID; - } - else - client_connected(newClient); - } - } -} - -//--------------------------------------------------------------------------------------------------------------------- -void basic_tcp_server::disconnect_thread() -{ - detail::set_thread_name("BaseTcpServer::disconnectThread"); - - while (!_p->disconnectThreadQuit) - { - { - HEADSOCKET_LOCK(_p->disconnectSemaphore); - HEADSOCKET_LOCK(_p->connections); - - remove_disconnected(); - _p->disconnectSemaphore.consume(); - } - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace detail { - -struct basic_tcp_client_impl -{ - std::atomic_int refCount; - std::atomic_bool isConnected; - std::weak_ptr server; - connection conn = detail::connection_impl(); - std::string address = ""; - int port = 0; - - basic_tcp_client_impl() - { - refCount = 0; - isConnected = false; - } -}; - -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -//--------------------------------------------------------------------------------------------------------------------- -basic_tcp_client::basic_tcp_client(const std::string &address, int port) - : _p(std::make_unique()) -{ - struct addrinfo *result = nullptr, *ptr = nullptr, hints; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - char buff[16]; - HEADSOCKET_SPRINTF(buff, "%d", port); - - if (getaddrinfo(address.c_str(), buff, &hints, &result)) - return; - - for (ptr = result; ptr != nullptr; ptr = ptr->ai_next) - { - _p->conn.impl()->socket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); - - if (!_p->conn.is_valid()) - return; - - if (connect(_p->conn.impl()->socket, ptr->ai_addr, static_cast(ptr->ai_addrlen)) == detail::socket_error) - { - detail::close_socket(_p->conn.impl()->socket); - _p->conn.impl()->socket = detail::invalid_socket; - continue; - } - - break; - } - - freeaddrinfo(result); - - if (!_p->conn.is_valid()) - return; - - _p->address = address; - _p->port = port; - _p->isConnected = true; -} - -//--------------------------------------------------------------------------------------------------------------------- -basic_tcp_client::basic_tcp_client(ptr server, connection &conn) - : _p(std::make_unique()) -{ - _p->server = server; - _p->conn.impl()->assign(*(conn.impl())); - _p->isConnected = true; -} - -//--------------------------------------------------------------------------------------------------------------------- -basic_tcp_client::~basic_tcp_client() -{ - disconnect(); -} - -//--------------------------------------------------------------------------------------------------------------------- -bool basic_tcp_client::disconnect() -{ - bool wasConnected = _p->isConnected.exchange(false); - - if (wasConnected) - { - _p->conn.impl()->close(); - - ptr s = server(); - - if (s) - s->disconnect(_p->conn.id()); - } - - return wasConnected; -} - -//--------------------------------------------------------------------------------------------------------------------- -bool basic_tcp_client::is_connected() const { return _p->isConnected; } - -//--------------------------------------------------------------------------------------------------------------------- -ptr basic_tcp_client::server() const { return _p->server.lock(); } - -//--------------------------------------------------------------------------------------------------------------------- -id_t basic_tcp_client::id() const { return _p->conn.id(); } - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -//--------------------------------------------------------------------------------------------------------------------- -tcp_client::tcp_client(const std::string &address, int port) - : base_t(address, port) -{ - -} - -//--------------------------------------------------------------------------------------------------------------------- -tcp_client::tcp_client(ptr server, connection &conn) - : base_t(server, conn) -{ - -} - -//--------------------------------------------------------------------------------------------------------------------- -tcp_client::~tcp_client() -{ - -} - -//--------------------------------------------------------------------------------------------------------------------- -size_t tcp_client::write(const void *ptr, size_t length) { return _p->conn.write(ptr, length); } - -//--------------------------------------------------------------------------------------------------------------------- -size_t tcp_client::read(void *ptr, size_t length) { return _p->conn.read(ptr, length); } - -//--------------------------------------------------------------------------------------------------------------------- -bool tcp_client::force_write(const void *ptr, size_t length) { return _p->conn.force_write(ptr, length); } - -//--------------------------------------------------------------------------------------------------------------------- -bool tcp_client::force_read(void *ptr, size_t length) { return _p->conn.force_read(ptr, length); } - -//--------------------------------------------------------------------------------------------------------------------- -bool tcp_client::read_line(std::string &output) { return _p->conn.read_line(output); } - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace detail { - -struct async_tcp_client_impl -{ - detail::semaphore writeSemaphore; - detail::lockable_value writeBlocks; - detail::lockable_value readBlocks; - std::unique_ptr writeThread; - std::unique_ptr readThread; - std::atomic_int threadCounter = { 0 }; -}; - -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -//--------------------------------------------------------------------------------------------------------------------- -async_tcp_client::async_tcp_client(const std::string &address, int port) - : base_t(address, port) - , _ap(std::make_unique()) -{ - -} - -//--------------------------------------------------------------------------------------------------------------------- -async_tcp_client::async_tcp_client(ptr server, connection &conn) - : base_t(server, conn) - , _ap(new detail::async_tcp_client_impl()) -{ - -} - -//--------------------------------------------------------------------------------------------------------------------- -async_tcp_client::~async_tcp_client() -{ - disconnect(); - - _ap->writeSemaphore.notify(); - _ap->writeThread->join(); - _ap->readThread->join(); -} - -//--------------------------------------------------------------------------------------------------------------------- -void async_tcp_client::push(const void *ptr, size_t length, opcode opcode) -{ - if (!ptr) - return; - - { - HEADSOCKET_LOCK(_ap->writeBlocks); - _ap->writeBlocks->block_begin(opcode); - _ap->writeBlocks->write(ptr, length); - _ap->writeBlocks->block_end(); - } - - _ap->writeSemaphore.notify(); -} - -//--------------------------------------------------------------------------------------------------------------------- -void async_tcp_client::push(const void *ptr, size_t length) -{ - push(ptr, length, opcode::binary); -} - -//--------------------------------------------------------------------------------------------------------------------- -void async_tcp_client::push(const std::string &text) -{ - push(text.c_str(), text.length(), opcode::text); -} - -//--------------------------------------------------------------------------------------------------------------------- -size_t async_tcp_client::peek() const -{ - HEADSOCKET_LOCK(_ap->readBlocks); - return _ap->readBlocks->peek(nullptr); -} - -//--------------------------------------------------------------------------------------------------------------------- -size_t async_tcp_client::pop(void *ptr, size_t length) -{ - if (!ptr) - return invalid_operation; - - if (!length) - return 0; - - HEADSOCKET_LOCK(_ap->readBlocks); - return _ap->readBlocks->read(ptr, length); -} - -//--------------------------------------------------------------------------------------------------------------------- -void async_tcp_client::init_threads() -{ - _ap->threadCounter = 0; - _ap->writeThread = std::make_unique(std::bind(&async_tcp_client::write_thread, this)); - _ap->readThread = std::make_unique(std::bind(&async_tcp_client::read_thread, this)); -} - -//--------------------------------------------------------------------------------------------------------------------- -void async_tcp_client::write_thread() -{ - ++_ap->threadCounter; - detail::set_thread_name("AsyncTcpClient::writeThread"); - - std::vector buffer(1024 * 1024); - - while (_p->isConnected) - { - size_t written = 0; - { - HEADSOCKET_LOCK(_ap->writeSemaphore); - - if (!_p->isConnected) - break; - - written = async_write_handler(buffer.data(), buffer.size()); - } - - if (written == invalid_operation) - break; - - if (!written) - buffer.resize(buffer.size() * 2); - else - { - const char *cursor = reinterpret_cast(buffer.data()); - - while (written) - { - int result = send(_p->conn.impl()->socket, cursor, static_cast(written), 0); - - if (!result || result == detail::socket_error) - break; - - cursor += result; - written -= static_cast(result); - } - } - } - - kill_threads(); - --_ap->threadCounter; -} - -//--------------------------------------------------------------------------------------------------------------------- -size_t async_tcp_client::async_write_handler(uint8_t *ptr, size_t length) -{ - HEADSOCKET_LOCK(_ap->writeBlocks); - - size_t toWrite = _ap->writeBlocks->peek(nullptr); - size_t toConsume = length > toWrite ? toWrite : length; - _ap->writeBlocks->read(ptr, toConsume); - - if (toWrite == toConsume) - _ap->writeSemaphore.consume(); - - return toConsume; -} - -//--------------------------------------------------------------------------------------------------------------------- -size_t async_tcp_client::async_read_handler(uint8_t *ptr, size_t length) -{ - HEADSOCKET_LOCK(_ap->readBlocks); - - _ap->readBlocks->block_begin(opcode::binary); - _ap->readBlocks->write(ptr, length); - _ap->readBlocks->block_end(); - - return length; -} - -//--------------------------------------------------------------------------------------------------------------------- -void async_tcp_client::read_thread() -{ - ++_ap->threadCounter; - detail::set_thread_name("AsyncTcpClient::readThread"); - - std::vector buffer(1024 * 1024); - size_t bufferBytes = 0, consumed = 0; - - while (_p->isConnected) - { - while (true) - { - int result = static_cast(bufferBytes); - - if (!result || !consumed) - { - result = recv( - _p->conn.impl()->socket, - reinterpret_cast(buffer.data() + bufferBytes), - static_cast(buffer.size() - bufferBytes), - 0); - - if (!result || result == detail::socket_error) - { - consumed = invalid_operation; - break; - } - - bufferBytes += static_cast(result); - } - - consumed = async_read_handler(buffer.data(), bufferBytes); - - if (!consumed) - { - if (bufferBytes == buffer.size()) - buffer.resize(buffer.size() * 2); - } - else - break; - } - - if (consumed == invalid_operation) - break; - - bufferBytes -= consumed; - - if (bufferBytes) - memcpy(buffer.data(), buffer.data() + consumed, bufferBytes); - } - - kill_threads(); - --_ap->threadCounter; -} - -//--------------------------------------------------------------------------------------------------------------------- -void async_tcp_client::kill_threads() -{ - if (std::this_thread::get_id() == _ap->readThread->get_id()) - _ap->writeSemaphore.notify(); - - disconnect(); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -//--------------------------------------------------------------------------------------------------------------------- -web_socket_client::web_socket_client(const std::string &address, int port) - : base_t(address, port) -{ - -} - -//--------------------------------------------------------------------------------------------------------------------- -web_socket_client::web_socket_client(ptr server, connection &conn) - : base_t(server, conn) -{ - -} - -//--------------------------------------------------------------------------------------------------------------------- -web_socket_client::~web_socket_client() -{ - -} - -//--------------------------------------------------------------------------------------------------------------------- -size_t web_socket_client::peek(opcode *op) const -{ - HEADSOCKET_LOCK(_ap->readBlocks); - return _ap->readBlocks->peek(op); -} - -//--------------------------------------------------------------------------------------------------------------------- -size_t web_socket_client::async_write_handler(uint8_t *ptr, size_t length) -{ - uint8_t *cursor = ptr; - HEADSOCKET_LOCK(_ap->writeBlocks); - - while (length >= 16) - { - opcode op{}; - size_t toWrite = _ap->writeBlocks->peek(&op); - size_t toConsume = (length - 15) > frame_size_limit ? frame_size_limit : (length - 15); - toConsume = toConsume > toWrite ? toWrite : toConsume; - - frame_header header; - header.fin = (toWrite - toConsume) == 0; - header.op = op; - header.masked = false; - header.payload_length = toConsume; - - size_t headerSize = header.write(cursor, length); - cursor += headerSize; - length -= headerSize; - _ap->writeBlocks->read(cursor, toConsume); - cursor += toConsume; - length -= toConsume; - - if (header.fin) - _ap->writeSemaphore.consume(); - - if (!_ap->writeBlocks->peek(&op)) - break; - } - - return cursor - ptr; -} - -//--------------------------------------------------------------------------------------------------------------------- -size_t web_socket_client::async_read_handler(uint8_t *ptr, size_t length) -{ - uint8_t *cursor = ptr; - HEADSOCKET_LOCK(_ap->readBlocks); - - if (!_payload_size) - { - opcode prevOpcode = _current_header.op; - size_t headerSize = _current_header.read(cursor, length); - - if (!headerSize) - return 0; - else if (headerSize == invalid_operation) - return invalid_operation; - - _payload_size = _current_header.payload_length; - cursor += headerSize; - length -= headerSize; - - if (_current_header.op != opcode::continuation) - _ap->readBlocks->block_begin(_current_header.op); - else - _current_header.op = prevOpcode; - } - - if (_payload_size) - { - size_t toConsume = length >= _payload_size ? _payload_size : length; - - if (toConsume) - { - _ap->readBlocks->write(cursor, toConsume); - _payload_size -= toConsume; - cursor += toConsume; - length -= toConsume; - } - } - - if (!_payload_size) - { - if (_current_header.masked) - { - //data_block &db = _ap->readBlocks->blocks.back(); - size_t len = _current_header.payload_length; - detail::utils::xor32(_current_header.masking_key, _ap->readBlocks->buffer.data() + _ap->readBlocks->buffer.size() - len, len); - } - - if (_current_header.fin) - { - data_block &db = _ap->readBlocks->blocks.back(); - - switch (_current_header.op) - { - case opcode::ping: - push(_ap->readBlocks->buffer.data() + db.offset, db.length, opcode::pong); - break; - - case opcode::text: - _ap->readBlocks->buffer.push_back(0); - ++db.length; - break; - - case opcode::connection_close: - kill_threads(); - break; - - default: - break; - } - - if (_current_header.op == opcode::text || _current_header.op == opcode::binary) - { - _ap->readBlocks->block_end(); - if (async_received_data(db, _ap->readBlocks->buffer.data() + db.offset, db.length)) - _ap->readBlocks->block_remove(); - } - } - } - - return cursor - ptr; -} - -//--------------------------------------------------------------------------------------------------------------------- -#define HAVE_ENOUGH_BYTES(num) if (length < num) return 0; else length -= num; -size_t web_socket_client::frame_header::read(const uint8_t *ptr, size_t length) -{ - const uint8_t *cursor = ptr; - HAVE_ENOUGH_BYTES(2); - this->fin = ((*cursor) & 0x80) != 0; - this->op = static_cast((*cursor++) & 0x0F); - - this->masked = ((*cursor) & 0x80) != 0; - uint8_t byte = (*cursor++) & 0x7F; - - if (byte < 126) - this->payload_length = byte; - else if (byte == 126) - { - HAVE_ENOUGH_BYTES(2); - this->payload_length = detail::utils::swap16bits(*(reinterpret_cast(cursor))); - cursor += 2; - } - else if (byte == 127) - { - HAVE_ENOUGH_BYTES(8); - uint64_t length64 = detail::utils::swap64bits(*(reinterpret_cast(cursor))) & 0x7FFFFFFFFFFFFFFFULL; - this->payload_length = static_cast(length64); - cursor += 8; - } - - if (this->masked) - { - HAVE_ENOUGH_BYTES(4); - this->masking_key = *(reinterpret_cast(cursor)); - cursor += 4; - } - - return cursor - ptr; -} - -//--------------------------------------------------------------------------------------------------------------------- -size_t web_socket_client::frame_header::write(uint8_t *ptr, size_t length) const -{ - uint8_t *cursor = ptr; - HAVE_ENOUGH_BYTES(2); - *cursor = this->fin ? 0x80 : 0x00; - *cursor++ |= static_cast(this->op); - - *cursor = this->masked ? 0x80 : 0x00; - - if (this->payload_length < 126) - *cursor++ |= static_cast(this->payload_length); - else if (this->payload_length < 65536) - { - HAVE_ENOUGH_BYTES(2); - *cursor++ |= 126; - *reinterpret_cast(cursor) = detail::utils::swap16bits(static_cast(this->payload_length)); - cursor += 2; - } - else - { - HAVE_ENOUGH_BYTES(8); - *cursor++ |= 127; - *reinterpret_cast(cursor) = detail::utils::swap64bits(static_cast(this->payload_length)); - cursor += 8; - } - - if (this->masked) - { - HAVE_ENOUGH_BYTES(4); - *reinterpret_cast(cursor) = this->masking_key; - cursor += 4; - } - - return cursor - ptr; -} -#undef HAVE_ENOUGH_BYTES - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace detail { - -} - -//--------------------------------------------------------------------------------------------------------------------- -bool http_server::handshake(connection &conn) -{ - std::string requestLine; - - if (!conn.read_line(requestLine)) - return false; - - std::string headerLine; - while (conn.read_line(headerLine)) - { - if (headerLine.empty()) - break; - } - - std::string method = detail::utils::cut_front(requestLine); - std::string path = detail::utils::url_decode(detail::utils::cut_front(requestLine)); - - if (!path.empty() && path.front() == '/') path = path.substr(1); - if (!path.empty() && path.back() == '/') path = path.substr(0, path.length() - 1); - - std::string params_get = detail::utils::cut_back(path, '?', false, false); - std::string version = detail::utils::cut_front(requestLine); - - parameters_t params; - std::string param_str; - while (!(param_str = detail::utils::cut_front(params_get, '&')).empty()) - { - parameter param; - param.name = detail::utils::cut_front(param_str, '='); - param.value = param_str; - param.integer = atoi(param_str.c_str()); - param.real = atof(param_str.c_str()); - param.boolean = (param.integer != 0) || (param_str == "true"); - - params[param.name] = param; - } - - response resp; - if (path != "favicon.ico" && request(path, params, resp)) - { - std::stringstream ss; - ss << version << " 200 OK\r\n"; - ss << "Content-Type: " << resp.content_type << "\r\n"; - ss << "Content-Length: " << resp.message.length() << "\r\n\r\n"; - ss << resp.message; - - conn.write(ss.str()); - } - else - { - conn.write(version); - conn.write(" 404 Not Found\r\n"); - } - - return false; -} - -} -#endif -#endif diff --git a/src/spice2x/external/wslay/CMakeLists.txt b/src/spice2x/external/wslay/CMakeLists.txt new file mode 100644 index 0000000..fa4ae8a --- /dev/null +++ b/src/spice2x/external/wslay/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.12) +project(wslay C) + +set(WSLAY_HEADERS + msvc_compat.h + wslay_event.h + wslay_frame.h + wslay_macro.h + wslay_net.h + wslay_queue.h + includes/wslay/wslay.h + includes/wslay/wslayver.h +) +set(WSLAY_SOURCES + wslay_event.c + wslay_frame.c + wslay_net.c + wslay_queue.c +) + +add_library(wslay STATIC ${WSLAY_HEADERS} ${WSLAY_SOURCES}) + +# config.h here is checked in rather than generated, see the note in it +target_compile_definitions(wslay PRIVATE HAVE_CONFIG_H) +target_include_directories(wslay PRIVATE ${PROJECT_SOURCE_DIR}) +target_include_directories(wslay PUBLIC ${PROJECT_SOURCE_DIR}/includes) + +if(MSVC) + target_compile_options(wslay PRIVATE "/FI${PROJECT_SOURCE_DIR}/msvc_compat.h") +else() + # upstream code, and not ours to keep warning clean + target_compile_options(wslay PRIVATE -w) +endif() diff --git a/src/spice2x/external/wslay/LICENSE b/src/spice2x/external/wslay/LICENSE new file mode 100644 index 0000000..6080330 --- /dev/null +++ b/src/spice2x/external/wslay/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2011, 2012, 2015 Tatsuhiro Tsujikawa + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/spice2x/external/wslay/config.h b/src/spice2x/external/wslay/config.h new file mode 100644 index 0000000..bef9106 --- /dev/null +++ b/src/spice2x/external/wslay/config.h @@ -0,0 +1,9 @@ +/* Hand written stand-in for the autotools/CMake generated config.h. Spice only + builds this for Windows on x86, so the probes have single known answers. */ +#ifndef CONFIG_H +#define CONFIG_H + +#define HAVE_WINSOCK2_H +/* x86 and x86_64 are little endian, so WORDS_BIGENDIAN stays undefined */ + +#endif /* CONFIG_H */ diff --git a/src/spice2x/external/wslay/includes/wslay/wslay.h b/src/spice2x/external/wslay/includes/wslay/wslay.h new file mode 100644 index 0000000..7ab8501 --- /dev/null +++ b/src/spice2x/external/wslay/includes/wslay/wslay.h @@ -0,0 +1,841 @@ +/* + * Wslay - The WebSocket Library + * + * Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef WSLAY_H +#define WSLAY_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/* + * wslay/wslayver.h is generated from wslay/wslayver.h.in by + * configure. The projects which do not use autotools can set + * WSLAY_VERSION macro from outside to avoid to generating wslayver.h + */ +#ifndef WSLAY_VERSION +# include +#endif /* WSLAY_VERSION */ + +enum wslay_error { + WSLAY_ERR_WANT_READ = -100, + WSLAY_ERR_WANT_WRITE = -101, + WSLAY_ERR_PROTO = -200, + WSLAY_ERR_INVALID_ARGUMENT = -300, + WSLAY_ERR_INVALID_CALLBACK = -301, + WSLAY_ERR_NO_MORE_MSG = -302, + WSLAY_ERR_CALLBACK_FAILURE = -400, + WSLAY_ERR_WOULDBLOCK = -401, + WSLAY_ERR_NOMEM = -500 +}; + +/* + * Status codes defined in RFC6455 + */ +enum wslay_status_code { + WSLAY_CODE_NORMAL_CLOSURE = 1000, + WSLAY_CODE_GOING_AWAY = 1001, + WSLAY_CODE_PROTOCOL_ERROR = 1002, + WSLAY_CODE_UNSUPPORTED_DATA = 1003, + WSLAY_CODE_NO_STATUS_RCVD = 1005, + WSLAY_CODE_ABNORMAL_CLOSURE = 1006, + WSLAY_CODE_INVALID_FRAME_PAYLOAD_DATA = 1007, + WSLAY_CODE_POLICY_VIOLATION = 1008, + WSLAY_CODE_MESSAGE_TOO_BIG = 1009, + WSLAY_CODE_MANDATORY_EXT = 1010, + WSLAY_CODE_INTERNAL_SERVER_ERROR = 1011, + WSLAY_CODE_TLS_HANDSHAKE = 1015 +}; + +enum wslay_io_flags { + /* + * There is more data to send. + */ + WSLAY_MSG_MORE = 1 +}; + +/* + * Callback function used by wslay_frame_send() function when it needs + * to send data. The implementation of this function must send at most + * len bytes of data in data. flags is the bitwise OR of zero or more + * of the following flag: + * + * WSLAY_MSG_MORE + * There is more data to send + * + * It provides some hints to tune performance and behaviour. user_data + * is one given in wslay_frame_context_init() function. The + * implementation of this function must return the number of bytes + * sent. If there is an error, return -1. The return value 0 is also + * treated an error by the library. + */ +typedef ssize_t (*wslay_frame_send_callback)(const uint8_t *data, size_t len, + int flags, void *user_data); +/* + * Callback function used by wslay_frame_recv() function when it needs + * more data. The implementation of this function must fill at most + * len bytes of data into buf. The memory area of buf is allocated by + * library and not be freed by the application code. flags is always 0 + * in this version. user_data is one given in + * wslay_frame_context_init() function. The implementation of this + * function must return the number of bytes filled. If there is an + * error, return -1. The return value 0 is also treated an error by + * the library. + */ +typedef ssize_t (*wslay_frame_recv_callback)(uint8_t *buf, size_t len, + int flags, void *user_data); +/* + * Callback function used by wslay_frame_send() function when it needs + * new mask key. The implementation of this function must write + * exactly len bytes of mask key to buf. user_data is one given in + * wslay_frame_context_init() function. The implementation of this + * function return 0 on success. If there is an error, return -1. + */ +typedef int (*wslay_frame_genmask_callback)(uint8_t *buf, size_t len, + void *user_data); + +struct wslay_frame_callbacks { + wslay_frame_send_callback send_callback; + wslay_frame_recv_callback recv_callback; + wslay_frame_genmask_callback genmask_callback; +}; + +/* + * The opcode defined in RFC6455. + */ +enum wslay_opcode { + WSLAY_CONTINUATION_FRAME = 0x0u, + WSLAY_TEXT_FRAME = 0x1u, + WSLAY_BINARY_FRAME = 0x2u, + WSLAY_CONNECTION_CLOSE = 0x8u, + WSLAY_PING = 0x9u, + WSLAY_PONG = 0xau +}; + +/* + * Macro that returns 1 if opcode is control frame opcode, otherwise + * returns 0. + */ +#define wslay_is_ctrl_frame(opcode) ((opcode >> 3) & 1) + +/* + * Macros that represent and return reserved bits: RSV1, RSV2, RSV3. + * These macros assume that rsv is constructed by ((RSV1 << 2) | + * (RSV2 << 1) | RSV3) + */ +#define WSLAY_RSV_NONE ((uint8_t)0) +#define WSLAY_RSV1_BIT (((uint8_t)1) << 2) +#define WSLAY_RSV2_BIT (((uint8_t)1) << 1) +#define WSLAY_RSV3_BIT (((uint8_t)1) << 0) + +#define wslay_get_rsv1(rsv) ((rsv >> 2) & 1) +#define wslay_get_rsv2(rsv) ((rsv >> 1) & 1) +#define wslay_get_rsv3(rsv) (rsv & 1) + +struct wslay_frame_iocb { + /* 1 for fragmented final frame, 0 for otherwise */ + uint8_t fin; + /* + * reserved 3 bits. rsv = ((RSV1 << 2) | (RSV << 1) | RSV3). + * RFC6455 requires 0 unless extensions are negotiated. + */ + uint8_t rsv; + /* 4 bit opcode */ + uint8_t opcode; + /* payload length [0, 2**63-1] */ + uint64_t payload_length; + /* 1 for masked frame, 0 for unmasked */ + uint8_t mask; + /* part of payload data */ + const uint8_t *data; + /* bytes of data defined above */ + size_t data_length; +}; + +struct wslay_frame_context; +typedef struct wslay_frame_context *wslay_frame_context_ptr; + +/* + * Initializes ctx using given callbacks and user_data. This function + * allocates memory for struct wslay_frame_context and stores the + * result to *ctx. The callback functions specified in callbacks are + * copied to ctx. user_data is stored in ctx and it will be passed to + * callback functions. When the user code finished using ctx, it must + * call wslay_frame_context_free to deallocate memory. + */ +int wslay_frame_context_init(wslay_frame_context_ptr *ctx, + const struct wslay_frame_callbacks *callbacks, + void *user_data); + +/* + * Deallocates memory pointed by ctx. + */ +void wslay_frame_context_free(wslay_frame_context_ptr ctx); + +/* + * Send WebSocket frame specified in iocb. ctx must be initialized + * using wslay_frame_context_init() function. iocb->fin must be 1 if + * this is a fin frame, otherwise 0. iocb->rsv is reserved bits. + * iocb->opcode must be the opcode of this frame. iocb->mask must be + * 1 if this is masked frame, otherwise 0. iocb->payload_length is + * the payload_length of this frame. iocb->data must point to the + * payload data to be sent. iocb->data_length must be the length of + * the data. This function calls send_callback function if it needs + * to send bytes. This function calls gen_mask_callback function if + * it needs new mask key. This function returns the number of payload + * bytes sent. Please note that it does not include any number of + * header bytes. If it cannot send any single bytes of payload, it + * returns WSLAY_ERR_WANT_WRITE. If the library detects error in iocb, + * this function returns WSLAY_ERR_INVALID_ARGUMENT. If callback + * functions report a failure, this function returns + * WSLAY_ERR_INVALID_CALLBACK. This function does not always send all + * given data in iocb. If there are remaining data to be sent, adjust + * data and data_length in iocb accordingly and call this function + * again. + */ +ssize_t wslay_frame_send(wslay_frame_context_ptr ctx, + struct wslay_frame_iocb *iocb); + +/* + * Write WebSocket frame specified in iocb to buf of length + * buflen. ctx must be initialized using wslay_frame_context_init() + * function. iocb->fin must be 1 if this is a fin frame, otherwise 0. + * iocb->rsv is reserved bits. iocb->opcode must be the opcode of + * this frame. iocb->mask must be 1 if this is masked frame, + * otherwise 0. iocb->payload_length is the payload_length of this + * frame. iocb->data must point to the payload data to be + * sent. iocb->data_length must be the length of the data. Unlike + * wslay_frame_send, this function does not call send_callback + * function. This function calls gen_mask_callback function if it + * needs new mask key. This function returns the number of bytes + * written to a buffer. Unlike wslay_frame_send, it includes the + * number of header bytes. Instead, the number of payload bytes + * written is assigned to *pwpayloadlen if this function succeeds. If + * there is not enough space left in a buffer, it returns 0. If the + * library detects error in iocb, this function returns + * WSLAY_ERR_INVALID_ARGUMENT. If callback functions report a + * failure, this function returns WSLAY_ERR_INVALID_CALLBACK. This + * function does not always send all given data in iocb. If there are + * remaining data to be sent, adjust data and data_length in iocb + * accordingly and call this function again. + */ +ssize_t wslay_frame_write(wslay_frame_context_ptr ctx, + struct wslay_frame_iocb *iocb, uint8_t *buf, + size_t buflen, size_t *pwpayloadlen); + +/* + * Receives WebSocket frame and stores it in iocb. This function + * returns the number of payload bytes received. This does not + * include header bytes. In this case, iocb will be populated as + * follows: iocb->fin is 1 if received frame is fin frame, otherwise + * 0. iocb->rsv is reserved bits of received frame. iocb->opcode is + * opcode of received frame. iocb->mask is 1 if received frame is + * masked, otherwise 0. iocb->payload_length is the payload length of + * received frame. iocb->data is pointed to the buffer containing + * received payload data. This buffer is allocated by the library and + * must be read-only. iocb->data_length is the number of payload + * bytes recieved. This function calls recv_callback if it needs to + * receive additional bytes. If it cannot receive any single bytes of + * payload, it returns WSLAY_ERR_WANT_READ. If the library detects + * protocol violation in a received frame, this function returns + * WSLAY_ERR_PROTO. If callback functions report a failure, this + * function returns WSLAY_ERR_INVALID_CALLBACK. This function does + * not always receive whole frame in a single call. If there are + * remaining data to be received, call this function again. This + * function ensures frame alignment. + */ +ssize_t wslay_frame_recv(wslay_frame_context_ptr ctx, + struct wslay_frame_iocb *iocb); + +struct wslay_event_context; +/* Pointer to the event-based API context */ +typedef struct wslay_event_context *wslay_event_context_ptr; + +struct wslay_event_on_msg_recv_arg { + /* reserved bits: rsv = (RSV1 << 2) | (RSV2 << 1) | RSV3 */ + uint8_t rsv; + /* opcode */ + uint8_t opcode; + /* received message */ + const uint8_t *msg; + /* message length */ + size_t msg_length; + /* + * Status code iff opcode == WSLAY_CONNECTION_CLOSE. If no status + * code is included in the close control frame, it is set to 0. + */ + uint16_t status_code; +}; + +/* + * Callback function invoked by wslay_event_recv() when a message is + * completely received. + */ +typedef void (*wslay_event_on_msg_recv_callback)( + wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, + void *user_data); + +struct wslay_event_on_frame_recv_start_arg { + /* fin bit; 1 for final frame, or 0. */ + uint8_t fin; + /* reserved bits: rsv = (RSV1 << 2) | (RSV2 << 1) | RSV3 */ + uint8_t rsv; + /* opcode of the frame */ + uint8_t opcode; + /* payload length of ths frame */ + uint64_t payload_length; +}; + +/* + * Callback function invoked by wslay_event_recv() when a new frame + * starts to be received. This callback function is only invoked once + * for each frame. + */ +typedef void (*wslay_event_on_frame_recv_start_callback)( + wslay_event_context_ptr ctx, + const struct wslay_event_on_frame_recv_start_arg *arg, void *user_data); + +struct wslay_event_on_frame_recv_chunk_arg { + /* chunk of payload data */ + const uint8_t *data; + /* length of data */ + size_t data_length; +}; + +/* + * Callback function invoked by wslay_event_recv() when a chunk of + * frame payload is received. + */ +typedef void (*wslay_event_on_frame_recv_chunk_callback)( + wslay_event_context_ptr ctx, + const struct wslay_event_on_frame_recv_chunk_arg *arg, void *user_data); + +/* + * Callback function invoked by wslay_event_recv() when a frame is + * completely received. + */ +typedef void (*wslay_event_on_frame_recv_end_callback)( + wslay_event_context_ptr ctx, void *user_data); + +/* + * Callback function invoked by wslay_event_recv() when it wants to + * receive more data from peer. The implementation of this callback + * function must read data at most len bytes from peer and store them + * in buf and return the number of bytes read. flags is always 0 in + * this version. + * + * If there is an error, return -1 and set error code + * WSLAY_ERR_CALLBACK_FAILURE using wslay_event_set_error(). Wslay + * event-based API on the whole assumes non-blocking I/O. If the cause + * of error is EAGAIN or EWOULDBLOCK, set WSLAY_ERR_WOULDBLOCK + * instead. This is important because it tells wslay_event_recv() to + * stop receiving further data and return. + */ +typedef ssize_t (*wslay_event_recv_callback)(wslay_event_context_ptr ctx, + uint8_t *buf, size_t len, + int flags, void *user_data); + +/* + * Callback function invoked by wslay_event_send() when it wants to + * send more data to peer. The implementation of this callback + * function must send data at most len bytes to peer and return the + * number of bytes sent. flags is the bitwise OR of zero or more of + * the following flag: + * + * WSLAY_MSG_MORE + * There is more data to send + * + * It provides some hints to tune performance and behaviour. + * + * If there is an error, return -1 and set error code + * WSLAY_ERR_CALLBACK_FAILURE using wslay_event_set_error(). Wslay + * event-based API on the whole assumes non-blocking I/O. If the cause + * of error is EAGAIN or EWOULDBLOCK, set WSLAY_ERR_WOULDBLOCK + * instead. This is important because it tells wslay_event_send() to + * stop sending data and return. + */ +typedef ssize_t (*wslay_event_send_callback)(wslay_event_context_ptr ctx, + const uint8_t *data, size_t len, + int flags, void *user_data); + +/* + * Callback function invoked by wslay_event_send() when it wants new + * mask key. As described in RFC6455, only the traffic from WebSocket + * client is masked, so this callback function is only needed if an + * event-based API is initialized for WebSocket client use. + */ +typedef int (*wslay_event_genmask_callback)(wslay_event_context_ptr ctx, + uint8_t *buf, size_t len, + void *user_data); + +struct wslay_event_callbacks { + wslay_event_recv_callback recv_callback; + wslay_event_send_callback send_callback; + wslay_event_genmask_callback genmask_callback; + wslay_event_on_frame_recv_start_callback on_frame_recv_start_callback; + wslay_event_on_frame_recv_chunk_callback on_frame_recv_chunk_callback; + wslay_event_on_frame_recv_end_callback on_frame_recv_end_callback; + wslay_event_on_msg_recv_callback on_msg_recv_callback; +}; + +/* + * Initializes ctx as WebSocket Server. user_data is an arbitrary + * pointer, which is directly passed to each callback functions as + * user_data argument. + * + * On success, returns 0. On error, returns one of following negative + * values: + * + * WSLAY_ERR_NOMEM + * Out of memory. + */ +int wslay_event_context_server_init( + wslay_event_context_ptr *ctx, const struct wslay_event_callbacks *callbacks, + void *user_data); + +/* + * Initializes ctx as WebSocket client. user_data is an arbitrary + * pointer, which is directly passed to each callback functions as + * user_data argument. + * + * On success, returns 0. On error, returns one of following negative + * values: + * + * WSLAY_ERR_NOMEM + * Out of memory. + */ +int wslay_event_context_client_init( + wslay_event_context_ptr *ctx, const struct wslay_event_callbacks *callbacks, + void *user_data); + +/* + * Releases allocated resources for ctx. + */ +void wslay_event_context_free(wslay_event_context_ptr ctx); + +/* + * Sets a bit mask of allowed reserved bits. + * Currently only permitted values are WSLAY_RSV1_BIT to allow PMCE + * extension (see RFC-7692) or WSLAY_RSV_NONE to disable. + * + * Default: WSLAY_RSV_NONE + */ +void wslay_event_config_set_allowed_rsv_bits(wslay_event_context_ptr ctx, + uint8_t rsv); + +/* + * Enables or disables buffering of an entire message for non-control + * frames. If val is 0, buffering is enabled. Otherwise, buffering is + * disabled. If wslay_event_on_msg_recv_callback is invoked when + * buffering is disabled, the msg_length member of struct + * wslay_event_on_msg_recv_arg is set to 0. + * + * The control frames are always buffered regardless of this function call. + * + * This function must not be used after the first invocation of + * wslay_event_recv() function. + */ +void wslay_event_config_set_no_buffering(wslay_event_context_ptr ctx, int val); + +/* + * Sets maximum length of a message that can be received. The length + * of message is checked by wslay_event_recv() function. If the length + * of a message is larger than this value, reading operation is + * disabled (same effect with wslay_event_shutdown_read() call) and + * close control frame with WSLAY_CODE_MESSAGE_TOO_BIG is queued. If + * buffering for non-control frames is disabled, the library checks + * each frame payload length and does not check length of entire + * message. + * + * The default value is (1u << 31)-1. + */ +void wslay_event_config_set_max_recv_msg_length(wslay_event_context_ptr ctx, + uint64_t val); + +/* + * Sets callbacks to ctx. The callbacks previously set by this function + * or wslay_event_context_server_init() or + * wslay_event_context_client_init() are replaced with callbacks. + */ +void wslay_event_config_set_callbacks( + wslay_event_context_ptr ctx, const struct wslay_event_callbacks *callbacks); + +/* + * Receives messages from peer. When receiving + * messages, it uses wslay_event_recv_callback function. Single call + * of this function receives multiple messages until + * wslay_event_recv_callback function sets error code + * WSLAY_ERR_WOULDBLOCK. + * + * When close control frame is received, this function automatically + * queues close control frame. Also this function calls + * wslay_event_set_read_enabled() with second argument 0 to disable + * further read from peer. + * + * When ping control frame is received, this function automatically + * queues pong control frame. + * + * In case of a fatal errror which leads to negative return code, this + * function calls wslay_event_set_read_enabled() with second argument + * 0 to disable further read from peer. + * + * wslay_event_recv() returns 0 if it succeeds, or one of the + * following negative error codes: + * + * WSLAY_ERR_CALLBACK_FAILURE + * User defined callback function is failed. + * + * WSLAY_ERR_NOMEM + * Out of memory. + * + * When negative error code is returned, application must not make any + * further call of wslay_event_recv() and must close WebSocket + * connection. + */ +int wslay_event_recv(wslay_event_context_ptr ctx); + +/* + * Sends queued messages to peer. When sending a + * message, it uses wslay_event_send_callback function. Single call of + * wslay_event_send() sends multiple messages until + * wslay_event_send_callback sets error code WSLAY_ERR_WOULDBLOCK. + * + * If ctx is initialized for WebSocket client use, wslay_event_send() + * uses wslay_event_genmask_callback to get new mask key. + * + * When a message queued using wslay_event_queue_fragmented_msg() is + * sent, wslay_event_send() invokes + * wslay_event_fragmented_msg_callback for that message. + * + * After close control frame is sent, this function calls + * wslay_event_set_write_enabled() with second argument 0 to disable + * further transmission to peer. + * + * If there are any pending messages, wslay_event_want_write() returns + * 1, otherwise returns 0. + * + * In case of a fatal errror which leads to negative return code, this + * function calls wslay_event_set_write_enabled() with second argument + * 0 to disable further transmission to peer. + * + * wslay_event_send() returns 0 if it succeeds, or one of the + * following negative error codes: + * + * WSLAY_ERR_CALLBACK_FAILURE + * User defined callback function is failed. + * + * WSLAY_ERR_NOMEM + * Out of memory. + * + * When negative error code is returned, application must not make any + * further call of wslay_event_send() and must close WebSocket + * connection. + */ +int wslay_event_send(wslay_event_context_ptr ctx); + +/* + * Writes queued messages to a buffer. Unlike wslay_event_send(), this + * function writes messages into the given buffer. It does not use + * wslay_event_send_callback function. Single call of + * wslay_event_write() writes multiple messages until there is not + * enough space left in a buffer. + * + * If ctx is initialized for WebSocket client use, wslay_event_write() + * uses wslay_event_genmask_callback to get new mask key. + * + * buf is a pointer to buffer and its capacity is given in buflen. It + * should have at least 14 bytes. + * + * When a message queued using wslay_event_queue_fragmented_msg() is + * sent, wslay_event_write() invokes + * wslay_event_fragmented_msg_callback for that message. + * + * After close control frame is sent, this function calls + * wslay_event_set_write_enabled() with second argument 0 to disable + * further transmission to peer. + * + * If there are any pending messages, wslay_event_want_write() returns + * 1, otherwise returns 0. + * + * In case of a fatal errror which leads to negative return code, this + * function calls wslay_event_set_write_enabled() with second argument + * 0 to disable further transmission to peer. + * + * wslay_event_write() returns the number of bytes written to a buffer + * if it succeeds, or one of the following negative error codes: + * + * WSLAY_ERR_CALLBACK_FAILURE + * User defined callback function is failed. + * + * WSLAY_ERR_NOMEM + * Out of memory. + * + * When negative error code is returned, application must not make any + * further call of wslay_event_write() and must close WebSocket + * connection. + */ +ssize_t wslay_event_write(wslay_event_context_ptr ctx, uint8_t *buf, + size_t buflen); + +struct wslay_event_msg { + uint8_t opcode; + const uint8_t *msg; + size_t msg_length; +}; + +/* + * Queues message specified in arg. + * + * This function supports both control and non-control messages and + * the given message is sent without fragmentation. If fragmentation + * is needed, use wslay_event_queue_fragmented_msg() function instead. + * + * This function just queues a message and does not send + * it. wslay_event_send() function call sends these queued messages. + * + * wslay_event_queue_msg() returns 0 if it succeeds, or returns the + * following negative error codes: + * + * WSLAY_ERR_NO_MORE_MSG + * Could not queue given message. The one of possible reason is that + * close control frame has been queued/sent and no further queueing + * message is not allowed. + * + * WSLAY_ERR_INVALID_ARGUMENT + * The given message is invalid. + * + * WSLAY_ERR_NOMEM + * Out of memory. + */ +int wslay_event_queue_msg(wslay_event_context_ptr ctx, + const struct wslay_event_msg *arg); + +/* + * Extended version of wslay_event_queue_msg which allows to set reserved bits. + */ +int wslay_event_queue_msg_ex(wslay_event_context_ptr ctx, + const struct wslay_event_msg *arg, uint8_t rsv); + +/* + * Specify "source" to generate message. + */ +union wslay_event_msg_source { + int fd; + void *data; +}; + +/* + * Callback function called by wslay_event_send() to read message data + * from source. The implementation of + * wslay_event_fragmented_msg_callback must store at most len bytes of + * data to buf and return the number of stored bytes. If all data is + * read (i.e., EOF), set *eof to 1. If no data can be generated at the + * moment, return 0. If there is an error, return -1 and set error + * code WSLAY_ERR_CALLBACK_FAILURE using wslay_event_set_error(). + */ +typedef ssize_t (*wslay_event_fragmented_msg_callback)( + wslay_event_context_ptr ctx, uint8_t *buf, size_t len, + const union wslay_event_msg_source *source, int *eof, void *user_data); + +struct wslay_event_fragmented_msg { + /* opcode */ + uint8_t opcode; + /* "source" to generate message data */ + union wslay_event_msg_source source; + /* Callback function to read message data from source. */ + wslay_event_fragmented_msg_callback read_callback; +}; + +/* + * Queues a fragmented message specified in arg. + * + * This function supports non-control messages only. For control frames, + * use wslay_event_queue_msg() or wslay_event_queue_close(). + * + * This function just queues a message and does not send + * it. wslay_event_send() function call sends these queued messages. + * + * wslay_event_queue_fragmented_msg() returns 0 if it succeeds, or + * returns the following negative error codes: + * + * WSLAY_ERR_NO_MORE_MSG + * Could not queue given message. The one of possible reason is that + * close control frame has been queued/sent and no further queueing + * message is not allowed. + * + * WSLAY_ERR_INVALID_ARGUMENT + * The given message is invalid. + * + * WSLAY_ERR_NOMEM + * Out of memory. + */ +int wslay_event_queue_fragmented_msg( + wslay_event_context_ptr ctx, const struct wslay_event_fragmented_msg *arg); + +/* + * Extended version of wslay_event_queue_fragmented_msg which allows to set + * reserved bits. + */ +int wslay_event_queue_fragmented_msg_ex( + wslay_event_context_ptr ctx, const struct wslay_event_fragmented_msg *arg, + uint8_t rsv); + +/* + * Queues close control frame. This function is provided just for + * convenience. wslay_event_queue_msg() can queue a close control + * frame as well. status_code is the status code of close control + * frame. reason is the close reason encoded in UTF-8. reason_length + * is the length of reason in bytes. reason_length must be less than + * 123 bytes. + * + * If status_code is 0, reason and reason_length is not used and close + * control frame with zero-length payload will be queued. + * + * This function just queues a message and does not send + * it. wslay_event_send() function call sends these queued messages. + * + * wslay_event_queue_close() returns 0 if it succeeds, or returns the + * following negative error codes: + * + * WSLAY_ERR_NO_MORE_MSG + * Could not queue given message. The one of possible reason is that + * close control frame has been queued/sent and no further queueing + * message is not allowed. + * + * WSLAY_ERR_INVALID_ARGUMENT + * The given message is invalid. + * + * WSLAY_ERR_NOMEM + * Out of memory. + */ +int wslay_event_queue_close(wslay_event_context_ptr ctx, uint16_t status_code, + const uint8_t *reason, size_t reason_length); + +/* + * Sets error code to tell the library there is an error. This + * function is typically used in user defined callback functions. See + * the description of callback function to know which error code + * should be used. + */ +void wslay_event_set_error(wslay_event_context_ptr ctx, int val); + +/* + * Query whehter the library want to read more data from peer. + * + * wslay_event_want_read() returns 1 if the library want to read more + * data from peer, or returns 0. + */ +int wslay_event_want_read(wslay_event_context_ptr ctx); + +/* + * Query whehter the library want to send more data to peer. + * + * wslay_event_want_write() returns 1 if the library want to send more + * data to peer, or returns 0. + */ +int wslay_event_want_write(wslay_event_context_ptr ctx); + +/* + * Prevents the event-based API context from reading any further data + * from peer. + * + * This function may be used with wslay_event_queue_close() if the + * application detects error in the data received and wants to fail + * WebSocket connection. + */ +void wslay_event_shutdown_read(wslay_event_context_ptr ctx); + +/* + * Prevents the event-based API context from sending any further data + * to peer. + */ +void wslay_event_shutdown_write(wslay_event_context_ptr ctx); + +/* + * Returns 1 if the event-based API context allows read operation, or + * return 0. + * + * After wslay_event_shutdown_read() is called, + * wslay_event_get_read_enabled() returns 0. + */ +int wslay_event_get_read_enabled(wslay_event_context_ptr ctx); + +/* + * Returns 1 if the event-based API context allows write operation, or + * return 0. + * + * After wslay_event_shutdown_write() is called, + * wslay_event_get_write_enabled() returns 0. + */ +int wslay_event_get_write_enabled(wslay_event_context_ptr ctx); + +/* + * Returns 1 if a close control frame has been received from peer, or + * returns 0. + */ +int wslay_event_get_close_received(wslay_event_context_ptr ctx); + +/* + * Returns 1 if a close control frame has been sent to peer, or + * returns 0. + */ +int wslay_event_get_close_sent(wslay_event_context_ptr ctx); + +/* + * Returns status code received in close control frame. If no close + * control frame has not been received, returns + * WSLAY_CODE_ABNORMAL_CLOSURE. If received close control frame has no + * status code, returns WSLAY_CODE_NO_STATUS_RCVD. + */ +uint16_t wslay_event_get_status_code_received(wslay_event_context_ptr ctx); + +/* + * Returns status code sent in close control frame. If no close + * control frame has not been sent, returns + * WSLAY_CODE_ABNORMAL_CLOSURE. If sent close control frame has no + * status code, returns WSLAY_CODE_NO_STATUS_RCVD. + */ +uint16_t wslay_event_get_status_code_sent(wslay_event_context_ptr ctx); + +/* + * Returns the number of queued messages. + */ +size_t wslay_event_get_queued_msg_count(wslay_event_context_ptr ctx); + +/* + * Returns the sum of queued message length. It only counts the + * message length queued using wslay_event_queue_msg() or + * wslay_event_queue_close(). + */ +size_t wslay_event_get_queued_msg_length(wslay_event_context_ptr ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* WSLAY_H */ diff --git a/src/spice2x/external/wslay/includes/wslay/wslayver.h b/src/spice2x/external/wslay/includes/wslay/wslayver.h new file mode 100644 index 0000000..b783c19 --- /dev/null +++ b/src/spice2x/external/wslay/includes/wslay/wslayver.h @@ -0,0 +1,31 @@ +/* + * Wslay - The WebSocket Library + * + * Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef WSLAYVER_H +#define WSLAYVER_H + +/* normally substituted by the build system; pinned to the vendored release */ +#define WSLAY_VERSION "1.1.1" + +#endif /* WSLAYVER_H */ diff --git a/src/spice2x/external/wslay/msvc_compat.h b/src/spice2x/external/wslay/msvc_compat.h new file mode 100644 index 0000000..e8871e0 --- /dev/null +++ b/src/spice2x/external/wslay/msvc_compat.h @@ -0,0 +1,9 @@ +#pragma once + +#ifdef _MSC_VER +#include +#ifndef _SSIZE_T_DEFINED +#define _SSIZE_T_DEFINED +typedef SSIZE_T ssize_t; +#endif +#endif diff --git a/src/spice2x/external/wslay/wslay_event.c b/src/spice2x/external/wslay/wslay_event.c new file mode 100644 index 0000000..4c29fe4 --- /dev/null +++ b/src/spice2x/external/wslay/wslay_event.c @@ -0,0 +1,1082 @@ +/* + * Wslay - The WebSocket Library + * + * Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "wslay_event.h" + +#include +#include +#include + +#include "wslay_frame.h" +#include "wslay_net.h" +#include "wslay_macro.h" +/* Start of utf8 dfa */ +/* Copyright (c) 2008-2010 Bjoern Hoehrmann + * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + * + * Copyright (c) 2008-2009 Bjoern Hoehrmann + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#define UTF8_ACCEPT 0 +#define UTF8_REJECT 12 + +/* clang-format off */ +static const uint8_t utf8d[] = { + /* + * The first part of the table maps bytes to character classes that + * to reduce the size of the transition table and create bitmasks. + */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + /* + * The second part is a transition table that maps a combination + * of a state of the automaton and a character class to a state. + */ + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, +}; +/* clang-format on */ + +static uint32_t decode(uint32_t *state, uint32_t *codep, uint32_t byte) { + uint32_t type = utf8d[byte]; + + *codep = (*state != UTF8_ACCEPT) ? (byte & 0x3fu) | (*codep << 6) + : (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + +/* End of utf8 dfa */ + +static ssize_t wslay_event_frame_recv_callback(uint8_t *buf, size_t len, + int flags, void *user_data) { + struct wslay_event_frame_user_data *e = + (struct wslay_event_frame_user_data *)user_data; + return e->ctx->callbacks.recv_callback(e->ctx, buf, len, flags, e->user_data); +} + +static ssize_t wslay_event_frame_send_callback(const uint8_t *data, size_t len, + int flags, void *user_data) { + struct wslay_event_frame_user_data *e = + (struct wslay_event_frame_user_data *)user_data; + return e->ctx->callbacks.send_callback(e->ctx, data, len, flags, + e->user_data); +} + +static int wslay_event_frame_genmask_callback(uint8_t *buf, size_t len, + void *user_data) { + struct wslay_event_frame_user_data *e = + (struct wslay_event_frame_user_data *)user_data; + return e->ctx->callbacks.genmask_callback(e->ctx, buf, len, e->user_data); +} + +static int wslay_event_byte_chunk_init(struct wslay_event_byte_chunk **chunk, + size_t len) { + *chunk = malloc(sizeof(struct wslay_event_byte_chunk) + len); + if (*chunk == NULL) { + return WSLAY_ERR_NOMEM; + } + memset(*chunk, 0, sizeof(struct wslay_event_byte_chunk)); + if (len) { + (*chunk)->data = (uint8_t *)(*chunk) + sizeof(**chunk); + (*chunk)->data_length = len; + } + return 0; +} + +static void wslay_event_byte_chunk_free(struct wslay_event_byte_chunk *c) { + free(c); +} + +static void wslay_event_byte_chunk_copy(struct wslay_event_byte_chunk *c, + size_t off, const uint8_t *data, + size_t data_length) { + memcpy(c->data + off, data, data_length); +} + +static void wslay_event_imsg_set(struct wslay_event_imsg *m, uint8_t fin, + uint8_t rsv, uint8_t opcode) { + m->fin = fin; + m->rsv = rsv; + m->opcode = opcode; + m->msg_length = 0; +} + +static void wslay_event_imsg_chunks_free(struct wslay_event_imsg *m) { + while (!wslay_queue_empty(&m->chunks)) { + struct wslay_event_byte_chunk *chunk = wslay_struct_of( + wslay_queue_top(&m->chunks), struct wslay_event_byte_chunk, qe); + wslay_queue_pop(&m->chunks); + wslay_event_byte_chunk_free(chunk); + } +} + +static void wslay_event_imsg_reset(struct wslay_event_imsg *m) { + m->opcode = 0xffu; + m->utf8state = UTF8_ACCEPT; + wslay_event_imsg_chunks_free(m); +} + +static int wslay_event_imsg_append_chunk(struct wslay_event_imsg *m, + size_t len) { + if (len == 0) { + return 0; + } else { + int r; + struct wslay_event_byte_chunk *chunk; + if ((r = wslay_event_byte_chunk_init(&chunk, len)) != 0) { + return r; + } + wslay_queue_push(&m->chunks, &chunk->qe); + m->msg_length += len; + return 0; + } +} + +static int wslay_event_omsg_non_fragmented_init(struct wslay_event_omsg **m, + uint8_t opcode, uint8_t rsv, + const uint8_t *msg, + size_t msg_length) { + *m = malloc(sizeof(struct wslay_event_omsg) + msg_length); + if (!*m) { + return WSLAY_ERR_NOMEM; + } + memset(*m, 0, sizeof(struct wslay_event_omsg)); + (*m)->fin = 1; + (*m)->opcode = opcode; + (*m)->rsv = rsv; + (*m)->type = WSLAY_NON_FRAGMENTED; + if (msg_length) { + (*m)->data = (uint8_t *)(*m) + sizeof(**m); + memcpy((*m)->data, msg, msg_length); + (*m)->data_length = msg_length; + } + return 0; +} + +static int wslay_event_omsg_fragmented_init( + struct wslay_event_omsg **m, uint8_t opcode, uint8_t rsv, + const union wslay_event_msg_source source, + wslay_event_fragmented_msg_callback read_callback) { + *m = calloc(1, sizeof(struct wslay_event_omsg)); + if (!*m) { + return WSLAY_ERR_NOMEM; + } + (*m)->opcode = opcode; + (*m)->rsv = rsv; + (*m)->type = WSLAY_FRAGMENTED; + (*m)->source = source; + (*m)->read_callback = read_callback; + return 0; +} + +static void wslay_event_omsg_free(struct wslay_event_omsg *m) { free(m); } + +static uint8_t *wslay_event_flatten_queue(struct wslay_queue *queue, + size_t len) { + if (len == 0) { + return NULL; + } else { + size_t off = 0; + uint8_t *buf = malloc(len); + if (!buf) { + return NULL; + } + while (!wslay_queue_empty(queue)) { + struct wslay_event_byte_chunk *chunk = wslay_struct_of( + wslay_queue_top(queue), struct wslay_event_byte_chunk, qe); + wslay_queue_pop(queue); + memcpy(buf + off, chunk->data, chunk->data_length); + off += chunk->data_length; + wslay_event_byte_chunk_free(chunk); + assert(off <= len); + } + assert(len == off); + return buf; + } +} + +static int wslay_event_is_msg_queueable(wslay_event_context_ptr ctx) { + return ctx->write_enabled && (ctx->close_status & WSLAY_CLOSE_QUEUED) == 0; +} + +int wslay_event_queue_close(wslay_event_context_ptr ctx, uint16_t status_code, + const uint8_t *reason, size_t reason_length) { + if (!wslay_event_is_msg_queueable(ctx)) { + return WSLAY_ERR_NO_MORE_MSG; + } else if (reason_length > 123) { + return WSLAY_ERR_INVALID_ARGUMENT; + } else { + uint8_t msg[128]; + size_t msg_length; + struct wslay_event_msg arg; + uint16_t ncode; + int r; + if (status_code == 0) { + msg_length = 0; + } else { + ncode = htons(status_code); + memcpy(msg, &ncode, 2); + if (reason_length) { + memcpy(msg + 2, reason, reason_length); + } + msg_length = reason_length + 2; + } + arg.opcode = WSLAY_CONNECTION_CLOSE; + arg.msg = msg; + arg.msg_length = msg_length; + r = wslay_event_queue_msg(ctx, &arg); + if (r == 0) { + ctx->close_status |= WSLAY_CLOSE_QUEUED; + } + return r; + } +} + +static int wslay_event_queue_close_wrapper(wslay_event_context_ptr ctx, + uint16_t status_code, + const uint8_t *reason, + size_t reason_length) { + int r; + ctx->read_enabled = 0; + if ((r = wslay_event_queue_close(ctx, status_code, reason, reason_length)) && + r != WSLAY_ERR_NO_MORE_MSG) { + return r; + } + return 0; +} + +static int wslay_event_verify_rsv_bits(wslay_event_context_ptr ctx, + uint8_t rsv) { + return ((rsv & ~ctx->allowed_rsv_bits) == 0); +} + +int wslay_event_queue_msg(wslay_event_context_ptr ctx, + const struct wslay_event_msg *arg) { + return wslay_event_queue_msg_ex(ctx, arg, WSLAY_RSV_NONE); +} + +int wslay_event_queue_msg_ex(wslay_event_context_ptr ctx, + const struct wslay_event_msg *arg, uint8_t rsv) { + int r; + struct wslay_event_omsg *omsg; + if (!wslay_event_is_msg_queueable(ctx)) { + return WSLAY_ERR_NO_MORE_MSG; + } + /* RSV1 is not allowed for control frames */ + if ((wslay_is_ctrl_frame(arg->opcode) && + (arg->msg_length > 125 || wslay_get_rsv1(rsv))) || + !wslay_event_verify_rsv_bits(ctx, rsv)) { + return WSLAY_ERR_INVALID_ARGUMENT; + } + if ((r = wslay_event_omsg_non_fragmented_init( + &omsg, arg->opcode, rsv, arg->msg, arg->msg_length)) != 0) { + return r; + } + if (wslay_is_ctrl_frame(arg->opcode)) { + wslay_queue_push(&ctx->send_ctrl_queue, &omsg->qe); + } else { + wslay_queue_push(&ctx->send_queue, &omsg->qe); + } + ++ctx->queued_msg_count; + ctx->queued_msg_length += arg->msg_length; + return 0; +} + +int wslay_event_queue_fragmented_msg( + wslay_event_context_ptr ctx, const struct wslay_event_fragmented_msg *arg) { + return wslay_event_queue_fragmented_msg_ex(ctx, arg, WSLAY_RSV_NONE); +} + +int wslay_event_queue_fragmented_msg_ex( + wslay_event_context_ptr ctx, const struct wslay_event_fragmented_msg *arg, + uint8_t rsv) { + int r; + struct wslay_event_omsg *omsg; + if (!wslay_event_is_msg_queueable(ctx)) { + return WSLAY_ERR_NO_MORE_MSG; + } + if (wslay_is_ctrl_frame(arg->opcode) || + !wslay_event_verify_rsv_bits(ctx, rsv)) { + return WSLAY_ERR_INVALID_ARGUMENT; + } + if ((r = wslay_event_omsg_fragmented_init( + &omsg, arg->opcode, rsv, arg->source, arg->read_callback)) != 0) { + return r; + } + wslay_queue_push(&ctx->send_queue, &omsg->qe); + ++ctx->queued_msg_count; + return 0; +} + +void wslay_event_config_set_callbacks( + wslay_event_context_ptr ctx, + const struct wslay_event_callbacks *callbacks) { + ctx->callbacks = *callbacks; +} + +static int +wslay_event_context_init(wslay_event_context_ptr *ctx, + const struct wslay_event_callbacks *callbacks, + void *user_data) { + int i, r; + struct wslay_frame_callbacks frame_callbacks = { + wslay_event_frame_send_callback, wslay_event_frame_recv_callback, + wslay_event_frame_genmask_callback}; + *ctx = calloc(1, sizeof(struct wslay_event_context)); + if (!*ctx) { + return WSLAY_ERR_NOMEM; + } + wslay_event_config_set_callbacks(*ctx, callbacks); + (*ctx)->user_data = user_data; + (*ctx)->frame_user_data.ctx = *ctx; + (*ctx)->frame_user_data.user_data = user_data; + if ((r = wslay_frame_context_init(&(*ctx)->frame_ctx, &frame_callbacks, + &(*ctx)->frame_user_data)) != 0) { + wslay_event_context_free(*ctx); + return r; + } + (*ctx)->read_enabled = (*ctx)->write_enabled = 1; + wslay_queue_init(&(*ctx)->send_queue); + wslay_queue_init(&(*ctx)->send_ctrl_queue); + (*ctx)->queued_msg_count = 0; + (*ctx)->queued_msg_length = 0; + for (i = 0; i < 2; ++i) { + wslay_queue_init(&(*ctx)->imsgs[i].chunks); + wslay_event_imsg_reset(&(*ctx)->imsgs[i]); + } + (*ctx)->imsg = &(*ctx)->imsgs[0]; + (*ctx)->obufmark = (*ctx)->obuflimit = (*ctx)->obuf; + (*ctx)->status_code_sent = WSLAY_CODE_ABNORMAL_CLOSURE; + (*ctx)->status_code_recv = WSLAY_CODE_ABNORMAL_CLOSURE; + (*ctx)->max_recv_msg_length = (1u << 31) - 1; + return 0; +} + +int wslay_event_context_server_init( + wslay_event_context_ptr *ctx, const struct wslay_event_callbacks *callbacks, + void *user_data) { + int r; + if ((r = wslay_event_context_init(ctx, callbacks, user_data)) != 0) { + return r; + } + (*ctx)->server = 1; + return 0; +} + +int wslay_event_context_client_init( + wslay_event_context_ptr *ctx, const struct wslay_event_callbacks *callbacks, + void *user_data) { + int r; + if ((r = wslay_event_context_init(ctx, callbacks, user_data)) != 0) { + return r; + } + (*ctx)->server = 0; + return 0; +} + +void wslay_event_context_free(wslay_event_context_ptr ctx) { + int i; + if (!ctx) { + return; + } + for (i = 0; i < 2; ++i) { + wslay_event_imsg_chunks_free(&ctx->imsgs[i]); + wslay_queue_deinit(&ctx->imsgs[i].chunks); + } + + while (!wslay_queue_empty(&ctx->send_queue)) { + struct wslay_event_omsg *omsg = wslay_struct_of( + wslay_queue_top(&ctx->send_queue), struct wslay_event_omsg, qe); + wslay_queue_pop(&ctx->send_queue); + wslay_event_omsg_free(omsg); + } + wslay_queue_deinit(&ctx->send_queue); + + while (!wslay_queue_empty(&ctx->send_ctrl_queue)) { + struct wslay_event_omsg *omsg = wslay_struct_of( + wslay_queue_top(&ctx->send_ctrl_queue), struct wslay_event_omsg, qe); + wslay_queue_pop(&ctx->send_ctrl_queue); + wslay_event_omsg_free(omsg); + } + wslay_queue_deinit(&ctx->send_ctrl_queue); + + wslay_frame_context_free(ctx->frame_ctx); + wslay_event_omsg_free(ctx->omsg); + free(ctx); +} + +static void wslay_event_call_on_frame_recv_start_callback( + wslay_event_context_ptr ctx, const struct wslay_frame_iocb *iocb) { + if (ctx->callbacks.on_frame_recv_start_callback) { + struct wslay_event_on_frame_recv_start_arg arg; + arg.fin = iocb->fin; + arg.rsv = iocb->rsv; + arg.opcode = iocb->opcode; + arg.payload_length = iocb->payload_length; + ctx->callbacks.on_frame_recv_start_callback(ctx, &arg, ctx->user_data); + } +} + +static void wslay_event_call_on_frame_recv_chunk_callback( + wslay_event_context_ptr ctx, const struct wslay_frame_iocb *iocb) { + if (ctx->callbacks.on_frame_recv_chunk_callback) { + struct wslay_event_on_frame_recv_chunk_arg arg; + arg.data = iocb->data; + arg.data_length = iocb->data_length; + ctx->callbacks.on_frame_recv_chunk_callback(ctx, &arg, ctx->user_data); + } +} + +static void +wslay_event_call_on_frame_recv_end_callback(wslay_event_context_ptr ctx) { + if (ctx->callbacks.on_frame_recv_end_callback) { + ctx->callbacks.on_frame_recv_end_callback(ctx, ctx->user_data); + } +} + +static int wslay_event_is_valid_status_code(uint16_t status_code) { + return (1000 <= status_code && status_code <= 1011 && status_code != 1004 && + status_code != 1005 && status_code != 1006) || + (3000 <= status_code && status_code <= 4999); +} + +static int wslay_event_config_get_no_buffering(wslay_event_context_ptr ctx) { + return (ctx->config & WSLAY_CONFIG_NO_BUFFERING) > 0; +} + +int wslay_event_recv(wslay_event_context_ptr ctx) { + struct wslay_frame_iocb iocb; + ssize_t r; + while (ctx->read_enabled) { + memset(&iocb, 0, sizeof(iocb)); + r = wslay_frame_recv(ctx->frame_ctx, &iocb); + if (r >= 0) { + int new_frame = 0; + /* RSV1 is not allowed on control and continuation frames */ + if ((!wslay_event_verify_rsv_bits(ctx, iocb.rsv)) || + (wslay_get_rsv1(iocb.rsv) && + (wslay_is_ctrl_frame(iocb.opcode) || + iocb.opcode == WSLAY_CONTINUATION_FRAME)) || + (ctx->server && !iocb.mask) || (!ctx->server && iocb.mask)) { + if ((r = wslay_event_queue_close_wrapper(ctx, WSLAY_CODE_PROTOCOL_ERROR, + NULL, 0)) != 0) { + return (int)r; + } + break; + } + if (ctx->imsg->opcode == 0xffu) { + if (iocb.opcode == WSLAY_TEXT_FRAME || + iocb.opcode == WSLAY_BINARY_FRAME || + iocb.opcode == WSLAY_CONNECTION_CLOSE || + iocb.opcode == WSLAY_PING || iocb.opcode == WSLAY_PONG) { + wslay_event_imsg_set(ctx->imsg, iocb.fin, iocb.rsv, iocb.opcode); + new_frame = 1; + } else { + if ((r = wslay_event_queue_close_wrapper( + ctx, WSLAY_CODE_PROTOCOL_ERROR, NULL, 0)) != 0) { + return (int)r; + } + break; + } + } else if (ctx->ipayloadlen == 0 && ctx->ipayloadoff == 0) { + if (iocb.opcode == WSLAY_CONTINUATION_FRAME) { + ctx->imsg->fin = iocb.fin; + } else if (iocb.opcode == WSLAY_CONNECTION_CLOSE || + iocb.opcode == WSLAY_PING || iocb.opcode == WSLAY_PONG) { + ctx->imsg = &ctx->imsgs[1]; + wslay_event_imsg_set(ctx->imsg, iocb.fin, iocb.rsv, iocb.opcode); + } else { + if ((r = wslay_event_queue_close_wrapper( + ctx, WSLAY_CODE_PROTOCOL_ERROR, NULL, 0)) != 0) { + return (int)r; + } + break; + } + new_frame = 1; + } + if (new_frame) { + if (ctx->imsg->msg_length + iocb.payload_length > + ctx->max_recv_msg_length) { + if ((r = wslay_event_queue_close_wrapper( + ctx, WSLAY_CODE_MESSAGE_TOO_BIG, NULL, 0)) != 0) { + return (int)r; + } + break; + } + ctx->ipayloadlen = iocb.payload_length; + wslay_event_call_on_frame_recv_start_callback(ctx, &iocb); + if (!wslay_event_config_get_no_buffering(ctx) || + wslay_is_ctrl_frame(iocb.opcode)) { + if ((r = wslay_event_imsg_append_chunk(ctx->imsg, + iocb.payload_length)) != 0) { + ctx->read_enabled = 0; + return (int)r; + } + } + } + /* If RSV1 bit is set then it is too early for utf-8 validation */ + if ((!wslay_get_rsv1(ctx->imsg->rsv) && + ctx->imsg->opcode == WSLAY_TEXT_FRAME) || + ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) { + size_t i; + if (ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) { + i = 2; + } else { + i = 0; + } + for (; i < iocb.data_length; ++i) { + uint32_t codep; + if (decode(&ctx->imsg->utf8state, &codep, iocb.data[i]) == + UTF8_REJECT) { + if ((r = wslay_event_queue_close_wrapper( + ctx, WSLAY_CODE_INVALID_FRAME_PAYLOAD_DATA, NULL, 0)) != + 0) { + return (int)r; + } + break; + } + } + } + if (ctx->imsg->utf8state == UTF8_REJECT) { + break; + } + wslay_event_call_on_frame_recv_chunk_callback(ctx, &iocb); + if (iocb.data_length > 0) { + if (!wslay_event_config_get_no_buffering(ctx) || + wslay_is_ctrl_frame(iocb.opcode)) { + struct wslay_event_byte_chunk *chunk; + chunk = wslay_struct_of(wslay_queue_tail(&ctx->imsg->chunks), + struct wslay_event_byte_chunk, qe); + wslay_event_byte_chunk_copy(chunk, ctx->ipayloadoff, iocb.data, + iocb.data_length); + } + ctx->ipayloadoff += iocb.data_length; + } + if (ctx->ipayloadoff == ctx->ipayloadlen) { + if (ctx->imsg->fin && + (ctx->imsg->opcode == WSLAY_TEXT_FRAME || + ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) && + ctx->imsg->utf8state != UTF8_ACCEPT) { + if ((r = wslay_event_queue_close_wrapper( + ctx, WSLAY_CODE_INVALID_FRAME_PAYLOAD_DATA, NULL, 0)) != 0) { + return (int)r; + } + break; + } + wslay_event_call_on_frame_recv_end_callback(ctx); + if (ctx->imsg->fin) { + if (ctx->callbacks.on_msg_recv_callback || + ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE || + ctx->imsg->opcode == WSLAY_PING) { + struct wslay_event_on_msg_recv_arg arg; + uint16_t status_code = 0; + uint8_t *msg = NULL; + size_t msg_length = 0; + if (!wslay_event_config_get_no_buffering(ctx) || + wslay_is_ctrl_frame(iocb.opcode)) { + msg = wslay_event_flatten_queue(&ctx->imsg->chunks, + ctx->imsg->msg_length); + if (ctx->imsg->msg_length && !msg) { + ctx->read_enabled = 0; + return WSLAY_ERR_NOMEM; + } + msg_length = ctx->imsg->msg_length; + } + if (ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) { + const uint8_t *reason; + size_t reason_length; + if (ctx->imsg->msg_length >= 2) { + memcpy(&status_code, msg, 2); + status_code = ntohs(status_code); + if (!wslay_event_is_valid_status_code(status_code)) { + free(msg); + if ((r = wslay_event_queue_close_wrapper( + ctx, WSLAY_CODE_PROTOCOL_ERROR, NULL, 0)) != 0) { + return (int)r; + } + break; + } + reason = msg + 2; + reason_length = ctx->imsg->msg_length - 2; + } else { + reason = NULL; + reason_length = 0; + } + ctx->close_status |= WSLAY_CLOSE_RECEIVED; + ctx->status_code_recv = + status_code == 0 ? WSLAY_CODE_NO_STATUS_RCVD : status_code; + if ((r = wslay_event_queue_close_wrapper(ctx, status_code, reason, + reason_length)) != 0) { + free(msg); + return (int)r; + } + } else if (ctx->imsg->opcode == WSLAY_PING) { + struct wslay_event_msg pong_arg; + pong_arg.opcode = WSLAY_PONG; + pong_arg.msg = msg; + pong_arg.msg_length = ctx->imsg->msg_length; + if ((r = wslay_event_queue_msg(ctx, &pong_arg)) && + r != WSLAY_ERR_NO_MORE_MSG) { + ctx->read_enabled = 0; + free(msg); + return (int)r; + } + } + if (ctx->callbacks.on_msg_recv_callback) { + arg.rsv = ctx->imsg->rsv; + arg.opcode = ctx->imsg->opcode; + arg.msg = msg; + arg.msg_length = msg_length; + arg.status_code = status_code; + ctx->error = 0; + ctx->callbacks.on_msg_recv_callback(ctx, &arg, ctx->user_data); + } + free(msg); + } + wslay_event_imsg_reset(ctx->imsg); + if (ctx->imsg == &ctx->imsgs[1]) { + ctx->imsg = &ctx->imsgs[0]; + } + } + ctx->ipayloadlen = ctx->ipayloadoff = 0; + } + } else { + if (r != WSLAY_ERR_WANT_READ || + (ctx->error != WSLAY_ERR_WOULDBLOCK && ctx->error != 0)) { + if ((r = wslay_event_queue_close_wrapper(ctx, 0, NULL, 0)) != 0) { + return (int)r; + } + return WSLAY_ERR_CALLBACK_FAILURE; + } + break; + } + } + return 0; +} + +static void +wslay_event_on_non_fragmented_msg_popped(wslay_event_context_ptr ctx) { + ctx->omsg->fin = 1; + ctx->opayloadlen = ctx->omsg->data_length; + ctx->opayloadoff = 0; +} + +static struct wslay_event_omsg * +wslay_event_send_ctrl_queue_pop(wslay_event_context_ptr ctx) { + /* + * If Close control frame is queued, we don't send any control frame + * other than Close. + */ + if (ctx->close_status & WSLAY_CLOSE_QUEUED) { + while (!wslay_queue_empty(&ctx->send_ctrl_queue)) { + struct wslay_event_omsg *msg = wslay_struct_of( + wslay_queue_top(&ctx->send_ctrl_queue), struct wslay_event_omsg, qe); + wslay_queue_pop(&ctx->send_ctrl_queue); + if (msg->opcode == WSLAY_CONNECTION_CLOSE) { + return msg; + } else { + wslay_event_omsg_free(msg); + } + } + return NULL; + } else { + struct wslay_event_omsg *msg = wslay_struct_of( + wslay_queue_top(&ctx->send_ctrl_queue), struct wslay_event_omsg, qe); + wslay_queue_pop(&ctx->send_ctrl_queue); + return msg; + } +} + +int wslay_event_send(wslay_event_context_ptr ctx) { + struct wslay_frame_iocb iocb; + ssize_t r; + while (ctx->write_enabled && + (!wslay_queue_empty(&ctx->send_queue) || + !wslay_queue_empty(&ctx->send_ctrl_queue) || ctx->omsg)) { + if (!ctx->omsg) { + if (wslay_queue_empty(&ctx->send_ctrl_queue)) { + ctx->omsg = wslay_struct_of(wslay_queue_top(&ctx->send_queue), + struct wslay_event_omsg, qe); + wslay_queue_pop(&ctx->send_queue); + } else { + ctx->omsg = wslay_event_send_ctrl_queue_pop(ctx); + if (ctx->omsg == NULL) { + break; + } + } + if (ctx->omsg->type == WSLAY_NON_FRAGMENTED) { + wslay_event_on_non_fragmented_msg_popped(ctx); + } + } else if (!wslay_is_ctrl_frame(ctx->omsg->opcode) && + ctx->frame_ctx->ostate == PREP_HEADER && + !wslay_queue_empty(&ctx->send_ctrl_queue)) { + wslay_queue_push_front(&ctx->send_queue, &ctx->omsg->qe); + ctx->omsg = wslay_event_send_ctrl_queue_pop(ctx); + if (ctx->omsg == NULL) { + break; + } + /* ctrl message has WSLAY_NON_FRAGMENTED */ + wslay_event_on_non_fragmented_msg_popped(ctx); + } + if (ctx->omsg->type == WSLAY_NON_FRAGMENTED) { + memset(&iocb, 0, sizeof(iocb)); + iocb.fin = 1; + iocb.opcode = ctx->omsg->opcode; + iocb.rsv = ctx->omsg->rsv; + iocb.mask = ctx->server ^ 1; + iocb.data = ctx->omsg->data; + iocb.data_length = ctx->opayloadlen; + if (ctx->opayloadoff) { + iocb.data += ctx->opayloadoff; + iocb.data_length -= ctx->opayloadoff; + } + iocb.payload_length = ctx->opayloadlen; + r = wslay_frame_send(ctx->frame_ctx, &iocb); + if (r >= 0) { + ctx->opayloadoff += (uint64_t)r; + if (ctx->opayloadoff == ctx->opayloadlen) { + --ctx->queued_msg_count; + ctx->queued_msg_length -= ctx->omsg->data_length; + if (ctx->omsg->opcode == WSLAY_CONNECTION_CLOSE) { + uint16_t status_code = 0; + ctx->write_enabled = 0; + ctx->close_status |= WSLAY_CLOSE_SENT; + if (ctx->omsg->data_length >= 2) { + memcpy(&status_code, ctx->omsg->data, 2); + status_code = ntohs(status_code); + } + ctx->status_code_sent = + status_code == 0 ? WSLAY_CODE_NO_STATUS_RCVD : status_code; + } + wslay_event_omsg_free(ctx->omsg); + ctx->omsg = NULL; + } else { + break; + } + } else { + if (r != WSLAY_ERR_WANT_WRITE || + (ctx->error != WSLAY_ERR_WOULDBLOCK && ctx->error != 0)) { + ctx->write_enabled = 0; + return WSLAY_ERR_CALLBACK_FAILURE; + } + break; + } + } else { + if (ctx->omsg->fin == 0 && ctx->obuflimit == ctx->obufmark) { + int eof = 0; + r = ctx->omsg->read_callback(ctx, ctx->obuf, sizeof(ctx->obuf), + &ctx->omsg->source, &eof, ctx->user_data); + if (r == 0 && eof == 0) { + break; + } else if (r < 0) { + ctx->write_enabled = 0; + return WSLAY_ERR_CALLBACK_FAILURE; + } + ctx->obuflimit = ctx->obuf + r; + if (eof) { + ctx->omsg->fin = 1; + } + ctx->opayloadlen = (uint64_t)r; + ctx->opayloadoff = 0; + } + memset(&iocb, 0, sizeof(iocb)); + iocb.fin = ctx->omsg->fin; + iocb.opcode = ctx->omsg->opcode; + iocb.rsv = ctx->omsg->rsv; + iocb.mask = ctx->server ? 0 : 1; + iocb.data = ctx->obufmark; + iocb.data_length = (size_t)(ctx->obuflimit - ctx->obufmark); + iocb.payload_length = ctx->opayloadlen; + r = wslay_frame_send(ctx->frame_ctx, &iocb); + if (r >= 0) { + ctx->obufmark += r; + if (ctx->obufmark == ctx->obuflimit) { + ctx->obufmark = ctx->obuflimit = ctx->obuf; + if (ctx->omsg->fin) { + --ctx->queued_msg_count; + wslay_event_omsg_free(ctx->omsg); + ctx->omsg = NULL; + } else { + ctx->omsg->opcode = WSLAY_CONTINUATION_FRAME; + /* RSV1 is not set on continuation frames */ + ctx->omsg->rsv = (uint8_t)(ctx->omsg->rsv & ~WSLAY_RSV1_BIT); + } + } else { + break; + } + } else { + if (r != WSLAY_ERR_WANT_WRITE || + (ctx->error != WSLAY_ERR_WOULDBLOCK && ctx->error != 0)) { + ctx->write_enabled = 0; + return WSLAY_ERR_CALLBACK_FAILURE; + } + break; + } + } + } + return 0; +} + +ssize_t wslay_event_write(wslay_event_context_ptr ctx, uint8_t *buf, + size_t buflen) { + struct wslay_frame_iocb iocb; + ssize_t r; + uint8_t *buf_last = buf; + size_t wpayloadlen; + while (ctx->write_enabled && + (!wslay_queue_empty(&ctx->send_queue) || + !wslay_queue_empty(&ctx->send_ctrl_queue) || ctx->omsg)) { + if (!ctx->omsg) { + if (wslay_queue_empty(&ctx->send_ctrl_queue)) { + ctx->omsg = wslay_struct_of(wslay_queue_top(&ctx->send_queue), + struct wslay_event_omsg, qe); + wslay_queue_pop(&ctx->send_queue); + } else { + ctx->omsg = wslay_event_send_ctrl_queue_pop(ctx); + if (ctx->omsg == NULL) { + break; + } + } + if (ctx->omsg->type == WSLAY_NON_FRAGMENTED) { + wslay_event_on_non_fragmented_msg_popped(ctx); + } + } else if (!wslay_is_ctrl_frame(ctx->omsg->opcode) && + ctx->frame_ctx->ostate == PREP_HEADER && + !wslay_queue_empty(&ctx->send_ctrl_queue)) { + wslay_queue_push_front(&ctx->send_queue, &ctx->omsg->qe); + ctx->omsg = wslay_event_send_ctrl_queue_pop(ctx); + if (ctx->omsg == NULL) { + break; + } + /* ctrl message has WSLAY_NON_FRAGMENTED */ + wslay_event_on_non_fragmented_msg_popped(ctx); + } + if (ctx->omsg->type == WSLAY_NON_FRAGMENTED) { + memset(&iocb, 0, sizeof(iocb)); + iocb.fin = 1; + iocb.opcode = ctx->omsg->opcode; + iocb.rsv = ctx->omsg->rsv; + iocb.mask = ctx->server ^ 1; + iocb.data = ctx->omsg->data; + iocb.data_length = ctx->opayloadlen; + if (ctx->opayloadoff) { + iocb.data += ctx->opayloadoff; + iocb.data_length -= ctx->opayloadoff; + } + iocb.payload_length = ctx->opayloadlen; + r = wslay_frame_write(ctx->frame_ctx, &iocb, buf_last, buflen, + &wpayloadlen); + if (r > 0) { + assert((size_t)r <= buflen); + + buf_last += r; + buflen -= (size_t)r; + + ctx->opayloadoff += wpayloadlen; + if (ctx->opayloadoff == ctx->opayloadlen) { + --ctx->queued_msg_count; + ctx->queued_msg_length -= ctx->omsg->data_length; + if (ctx->omsg->opcode == WSLAY_CONNECTION_CLOSE) { + uint16_t status_code = 0; + ctx->write_enabled = 0; + ctx->close_status |= WSLAY_CLOSE_SENT; + if (ctx->omsg->data_length >= 2) { + memcpy(&status_code, ctx->omsg->data, 2); + status_code = ntohs(status_code); + } + ctx->status_code_sent = + status_code == 0 ? WSLAY_CODE_NO_STATUS_RCVD : status_code; + } + wslay_event_omsg_free(ctx->omsg); + ctx->omsg = NULL; + } else { + break; + } + } else if (r == 0) { + return buf_last - buf; + } else { + return WSLAY_ERR_CALLBACK_FAILURE; + } + } else { + if (ctx->omsg->fin == 0 && ctx->obuflimit == ctx->obufmark) { + int eof = 0; + r = ctx->omsg->read_callback(ctx, ctx->obuf, sizeof(ctx->obuf), + &ctx->omsg->source, &eof, ctx->user_data); + if (r == 0 && eof == 0) { + break; + } else if (r < 0) { + ctx->write_enabled = 0; + return WSLAY_ERR_CALLBACK_FAILURE; + } + ctx->obuflimit = ctx->obuf + r; + if (eof) { + ctx->omsg->fin = 1; + } + ctx->opayloadlen = (uint64_t)r; + ctx->opayloadoff = 0; + } + memset(&iocb, 0, sizeof(iocb)); + iocb.fin = ctx->omsg->fin; + iocb.opcode = ctx->omsg->opcode; + iocb.rsv = ctx->omsg->rsv; + iocb.mask = ctx->server ? 0 : 1; + iocb.data = ctx->obufmark; + iocb.data_length = (size_t)(ctx->obuflimit - ctx->obufmark); + iocb.payload_length = ctx->opayloadlen; + r = wslay_frame_write(ctx->frame_ctx, &iocb, buf_last, buflen, + &wpayloadlen); + if (r > 0) { + assert((size_t)r <= buflen); + + buf_last += r; + buflen -= (size_t)r; + + ctx->obufmark += wpayloadlen; + if (ctx->obufmark == ctx->obuflimit) { + ctx->obufmark = ctx->obuflimit = ctx->obuf; + if (ctx->omsg->fin) { + --ctx->queued_msg_count; + wslay_event_omsg_free(ctx->omsg); + ctx->omsg = NULL; + } else { + ctx->omsg->opcode = WSLAY_CONTINUATION_FRAME; + /* RSV1 is not set on continuation frames */ + ctx->omsg->rsv = (uint8_t)(ctx->omsg->rsv & ~WSLAY_RSV1_BIT); + } + } else { + break; + } + } else if (r == 0) { + return buf_last - buf; + } else { + return WSLAY_ERR_CALLBACK_FAILURE; + } + } + } + return buf_last - buf; +} + +void wslay_event_set_error(wslay_event_context_ptr ctx, int val) { + ctx->error = val; +} + +int wslay_event_want_read(wslay_event_context_ptr ctx) { + return ctx->read_enabled; +} + +int wslay_event_want_write(wslay_event_context_ptr ctx) { + return ctx->write_enabled && + (!wslay_queue_empty(&ctx->send_queue) || + !wslay_queue_empty(&ctx->send_ctrl_queue) || ctx->omsg); +} + +void wslay_event_shutdown_read(wslay_event_context_ptr ctx) { + ctx->read_enabled = 0; +} + +void wslay_event_shutdown_write(wslay_event_context_ptr ctx) { + ctx->write_enabled = 0; +} + +int wslay_event_get_read_enabled(wslay_event_context_ptr ctx) { + return ctx->read_enabled; +} + +int wslay_event_get_write_enabled(wslay_event_context_ptr ctx) { + return ctx->write_enabled; +} + +int wslay_event_get_close_received(wslay_event_context_ptr ctx) { + return (ctx->close_status & WSLAY_CLOSE_RECEIVED) > 0; +} + +int wslay_event_get_close_sent(wslay_event_context_ptr ctx) { + return (ctx->close_status & WSLAY_CLOSE_SENT) > 0; +} + +void wslay_event_config_set_allowed_rsv_bits(wslay_event_context_ptr ctx, + uint8_t rsv) { + /* We currently only allow WSLAY_RSV1_BIT or WSLAY_RSV_NONE */ + ctx->allowed_rsv_bits = rsv & WSLAY_RSV1_BIT; +} + +void wslay_event_config_set_no_buffering(wslay_event_context_ptr ctx, int val) { + if (val) { + ctx->config |= WSLAY_CONFIG_NO_BUFFERING; + } else { + ctx->config &= (uint32_t)~WSLAY_CONFIG_NO_BUFFERING; + } +} + +void wslay_event_config_set_max_recv_msg_length(wslay_event_context_ptr ctx, + uint64_t val) { + ctx->max_recv_msg_length = val; +} + +uint16_t wslay_event_get_status_code_received(wslay_event_context_ptr ctx) { + return ctx->status_code_recv; +} + +uint16_t wslay_event_get_status_code_sent(wslay_event_context_ptr ctx) { + return ctx->status_code_sent; +} + +size_t wslay_event_get_queued_msg_count(wslay_event_context_ptr ctx) { + return ctx->queued_msg_count; +} + +size_t wslay_event_get_queued_msg_length(wslay_event_context_ptr ctx) { + return ctx->queued_msg_length; +} diff --git a/src/spice2x/external/wslay/wslay_event.h b/src/spice2x/external/wslay/wslay_event.h new file mode 100644 index 0000000..e30c3d1 --- /dev/null +++ b/src/spice2x/external/wslay/wslay_event.h @@ -0,0 +1,138 @@ +/* + * Wslay - The WebSocket Library + * + * Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef WSLAY_EVENT_H +#define WSLAY_EVENT_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#include "wslay_queue.h" + +struct wslay_event_byte_chunk { + struct wslay_queue_entry qe; + uint8_t *data; + size_t data_length; +}; + +struct wslay_event_imsg { + uint8_t fin; + uint8_t rsv; + uint8_t opcode; + uint32_t utf8state; + struct wslay_queue chunks; + size_t msg_length; +}; + +enum wslay_event_msg_type { WSLAY_NON_FRAGMENTED, WSLAY_FRAGMENTED }; + +struct wslay_event_omsg { + struct wslay_queue_entry qe; + uint8_t fin; + uint8_t opcode; + uint8_t rsv; + enum wslay_event_msg_type type; + + uint8_t *data; + size_t data_length; + + union wslay_event_msg_source source; + wslay_event_fragmented_msg_callback read_callback; +}; + +struct wslay_event_frame_user_data { + wslay_event_context_ptr ctx; + void *user_data; +}; + +enum wslay_event_close_status { + WSLAY_CLOSE_RECEIVED = 1 << 0, + WSLAY_CLOSE_QUEUED = 1 << 1, + WSLAY_CLOSE_SENT = 1 << 2 +}; + +enum wslay_event_config { WSLAY_CONFIG_NO_BUFFERING = 1 << 0 }; + +struct wslay_event_context { + /* config status, bitwise OR of enum wslay_event_config values*/ + uint32_t config; + /* maximum message length that can be received */ + uint64_t max_recv_msg_length; + /* 1 if initialized for server, otherwise 0 */ + uint8_t server; + /* bitwise OR of enum wslay_event_close_status values */ + uint8_t close_status; + /* status code in received close control frame */ + uint16_t status_code_recv; + /* status code in sent close control frame */ + uint16_t status_code_sent; + wslay_frame_context_ptr frame_ctx; + /* 1 if reading is enabled, otherwise 0. Upon receiving close + control frame this value set to 0. If any errors in read + operation will also set this value to 0. */ + uint8_t read_enabled; + /* 1 if writing is enabled, otherwise 0 Upon completing sending + close control frame, this value set to 0. If any errors in write + opration will also set this value to 0. */ + uint8_t write_enabled; + /* imsg buffer to allow interleaved control frame between + non-control frames. */ + struct wslay_event_imsg imsgs[2]; + /* Pointer to imsgs to indicate current used buffer. */ + struct wslay_event_imsg *imsg; + /* payload length of frame currently being received. */ + uint64_t ipayloadlen; + /* next byte offset of payload currently being received. */ + uint64_t ipayloadoff; + /* error value set by user callback */ + int error; + /* Pointer to the message currently being sent. NULL if no message + is currently sent. */ + struct wslay_event_omsg *omsg; + /* Queue for non-control frames */ + struct wslay_queue /**/ send_queue; + /* Queue for control frames */ + struct wslay_queue /**/ send_ctrl_queue; + /* Size of send_queue + size of send_ctrl_queue */ + size_t queued_msg_count; + /* The sum of message length in send_queue */ + size_t queued_msg_length; + /* Buffer used for fragmented messages */ + uint8_t obuf[4096]; + uint8_t *obuflimit; + uint8_t *obufmark; + /* payload length of frame currently being sent. */ + uint64_t opayloadlen; + /* next byte offset of payload currently being sent. */ + uint64_t opayloadoff; + struct wslay_event_callbacks callbacks; + struct wslay_event_frame_user_data frame_user_data; + void *user_data; + uint8_t allowed_rsv_bits; +}; + +#endif /* WSLAY_EVENT_H */ diff --git a/src/spice2x/external/wslay/wslay_frame.c b/src/spice2x/external/wslay/wslay_frame.c new file mode 100644 index 0000000..fa065ee --- /dev/null +++ b/src/spice2x/external/wslay/wslay_frame.c @@ -0,0 +1,438 @@ +/* + * Wslay - The WebSocket Library + * + * Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "wslay_frame.h" + +#include +#include +#include + +#include "wslay_net.h" + +#define wslay_min(A, B) (((A) < (B)) ? (A) : (B)) + +int wslay_frame_context_init(wslay_frame_context_ptr *ctx, + const struct wslay_frame_callbacks *callbacks, + void *user_data) { + *ctx = malloc(sizeof(struct wslay_frame_context)); + if (*ctx == NULL) { + return -1; + } + memset(*ctx, 0, sizeof(struct wslay_frame_context)); + (*ctx)->istate = RECV_HEADER1; + (*ctx)->ireqread = 2; + (*ctx)->ostate = PREP_HEADER; + (*ctx)->user_data = user_data; + (*ctx)->ibufmark = (*ctx)->ibuflimit = (*ctx)->ibuf; + (*ctx)->callbacks = *callbacks; + return 0; +} + +void wslay_frame_context_free(wslay_frame_context_ptr ctx) { free(ctx); } + +ssize_t wslay_frame_send(wslay_frame_context_ptr ctx, + struct wslay_frame_iocb *iocb) { + if (iocb->data_length > iocb->payload_length) { + return WSLAY_ERR_INVALID_ARGUMENT; + } + if (ctx->ostate == PREP_HEADER) { + uint8_t *hdptr = ctx->oheader; + memset(ctx->oheader, 0, sizeof(ctx->oheader)); + *hdptr |= (uint8_t)((uint8_t)(iocb->fin << 7) & 0x80u); + *hdptr |= (uint8_t)((uint8_t)(iocb->rsv << 4) & 0x70u); + /* Suppress stubborn gcc-10 warning */ + *hdptr |= (uint8_t)((uint8_t)(iocb->opcode << 0) & 0xfu); + ++hdptr; + *hdptr |= (uint8_t)((uint8_t)(iocb->mask << 7) & 0x80u); + if (wslay_is_ctrl_frame(iocb->opcode) && iocb->payload_length > 125) { + return WSLAY_ERR_INVALID_ARGUMENT; + } + if (iocb->payload_length < 126) { + *hdptr |= (uint8_t)iocb->payload_length; + ++hdptr; + } else if (iocb->payload_length < (1 << 16)) { + uint16_t len = htons((uint16_t)iocb->payload_length); + *hdptr |= 126; + ++hdptr; + memcpy(hdptr, &len, 2); + hdptr += 2; + } else if (iocb->payload_length < (1ull << 63)) { + uint64_t len = hton64(iocb->payload_length); + *hdptr |= 127; + ++hdptr; + memcpy(hdptr, &len, 8); + hdptr += 8; + } else { + /* Too large payload length */ + return WSLAY_ERR_INVALID_ARGUMENT; + } + if (iocb->mask) { + if (ctx->callbacks.genmask_callback(ctx->omaskkey, 4, ctx->user_data) != + 0) { + return WSLAY_ERR_INVALID_CALLBACK; + } else { + ctx->omask = 1; + memcpy(hdptr, ctx->omaskkey, 4); + hdptr += 4; + } + } + ctx->ostate = SEND_HEADER; + ctx->oheadermark = ctx->oheader; + ctx->oheaderlimit = hdptr; + ctx->opayloadlen = iocb->payload_length; + ctx->opayloadoff = 0; + } + if (ctx->ostate == SEND_HEADER) { + ptrdiff_t len = ctx->oheaderlimit - ctx->oheadermark; + ssize_t r; + int flags = 0; + if (iocb->data_length > 0) { + flags |= WSLAY_MSG_MORE; + } + r = ctx->callbacks.send_callback(ctx->oheadermark, (size_t)len, flags, + ctx->user_data); + if (r > 0) { + if (r > len) { + return WSLAY_ERR_INVALID_CALLBACK; + } else { + ctx->oheadermark += r; + if (ctx->oheadermark == ctx->oheaderlimit) { + ctx->ostate = SEND_PAYLOAD; + } else { + return WSLAY_ERR_WANT_WRITE; + } + } + } else { + return WSLAY_ERR_WANT_WRITE; + } + } + if (ctx->ostate == SEND_PAYLOAD) { + size_t totallen = 0; + if (iocb->data_length > 0) { + if (ctx->omask) { + uint8_t temp[4096]; + const uint8_t *datamark = iocb->data, + *datalimit = iocb->data + iocb->data_length; + while (datamark < datalimit) { + size_t datalen = (size_t)(datalimit - datamark); + const uint8_t *writelimit = + datamark + wslay_min(sizeof(temp), datalen); + size_t writelen = (size_t)(writelimit - datamark); + ssize_t r; + size_t i; + for (i = 0; i < writelen; ++i) { + temp[i] = datamark[i] ^ ctx->omaskkey[(ctx->opayloadoff + i) % 4]; + } + r = ctx->callbacks.send_callback(temp, writelen, 0, ctx->user_data); + if (r > 0) { + if ((size_t)r > writelen) { + return WSLAY_ERR_INVALID_CALLBACK; + } else { + datamark += r; + ctx->opayloadoff += (uint64_t)r; + totallen += (size_t)r; + } + } else { + if (totallen > 0) { + break; + } else { + return WSLAY_ERR_WANT_WRITE; + } + } + } + } else { + ssize_t r; + r = ctx->callbacks.send_callback(iocb->data, iocb->data_length, 0, + ctx->user_data); + if (r > 0) { + if ((size_t)r > iocb->data_length) { + return WSLAY_ERR_INVALID_CALLBACK; + } else { + ctx->opayloadoff += (uint64_t)r; + totallen = (size_t)r; + } + } else { + return WSLAY_ERR_WANT_WRITE; + } + } + } + if (ctx->opayloadoff == ctx->opayloadlen) { + ctx->ostate = PREP_HEADER; + } + return (ssize_t)totallen; + } + return WSLAY_ERR_INVALID_ARGUMENT; +} + +ssize_t wslay_frame_write(wslay_frame_context_ptr ctx, + struct wslay_frame_iocb *iocb, uint8_t *buf, + size_t buflen, size_t *pwpayloadlen) { + uint8_t *buf_last = buf; + size_t i; + size_t hdlen; + + *pwpayloadlen = 0; + + if (iocb->data_length > iocb->payload_length) { + return WSLAY_ERR_INVALID_ARGUMENT; + } + + switch (ctx->ostate) { + case PREP_HEADER: + case PREP_HEADER_NOBUF: + hdlen = 2; + if (iocb->payload_length < 126) { + /* nothing to do */ + } else if (iocb->payload_length < (1 << 16)) { + hdlen += 2; + } else if (iocb->payload_length < (1ull << 63)) { + hdlen += 8; + } + if (iocb->mask) { + hdlen += 4; + } + + if (buflen < hdlen) { + ctx->ostate = PREP_HEADER_NOBUF; + return 0; + } + + memset(buf_last, 0, hdlen); + *buf_last |= (uint8_t)((uint8_t)(iocb->fin << 7) & 0x80u); + *buf_last |= (uint8_t)((uint8_t)(iocb->rsv << 4) & 0x70u); + /* Suppress stubborn gcc-10 warning */ + *buf_last |= (uint8_t)((uint8_t)(iocb->opcode << 0) & 0xfu); + ++buf_last; + *buf_last |= (uint8_t)((uint8_t)(iocb->mask << 7) & 0x80u); + if (wslay_is_ctrl_frame(iocb->opcode) && iocb->payload_length > 125) { + return WSLAY_ERR_INVALID_ARGUMENT; + } + if (iocb->payload_length < 126) { + *buf_last |= (uint8_t)iocb->payload_length; + ++buf_last; + } else if (iocb->payload_length < (1 << 16)) { + uint16_t len = htons((uint16_t)iocb->payload_length); + *buf_last |= 126; + ++buf_last; + memcpy(buf_last, &len, 2); + buf_last += 2; + } else if (iocb->payload_length < (1ull << 63)) { + uint64_t len = hton64(iocb->payload_length); + *buf_last |= 127; + ++buf_last; + memcpy(buf_last, &len, 8); + buf_last += 8; + } else { + /* Too large payload length */ + return WSLAY_ERR_INVALID_ARGUMENT; + } + if (iocb->mask) { + if (ctx->callbacks.genmask_callback(ctx->omaskkey, 4, ctx->user_data) != + 0) { + return WSLAY_ERR_INVALID_CALLBACK; + } else { + ctx->omask = 1; + memcpy(buf_last, ctx->omaskkey, 4); + buf_last += 4; + } + } + ctx->ostate = SEND_PAYLOAD; + ctx->opayloadlen = iocb->payload_length; + ctx->opayloadoff = 0; + + buflen -= (size_t)(buf_last - buf); + /* fall through */ + case SEND_PAYLOAD: + if (iocb->data_length > 0) { + size_t writelen = wslay_min(buflen, iocb->data_length); + + if (ctx->omask) { + for (i = 0; i < writelen; ++i) { + *buf_last++ = + iocb->data[i] ^ ctx->omaskkey[(ctx->opayloadoff + i) % 4]; + } + } else { + memcpy(buf_last, iocb->data, writelen); + buf_last += writelen; + } + + ctx->opayloadoff += writelen; + *pwpayloadlen = writelen; + } + + if (ctx->opayloadoff == ctx->opayloadlen) { + ctx->ostate = PREP_HEADER; + } + + return buf_last - buf; + default: + return WSLAY_ERR_INVALID_ARGUMENT; + } +} + +static void wslay_shift_ibuf(wslay_frame_context_ptr ctx) { + ptrdiff_t len = ctx->ibuflimit - ctx->ibufmark; + memmove(ctx->ibuf, ctx->ibufmark, (size_t)len); + ctx->ibuflimit = ctx->ibuf + len; + ctx->ibufmark = ctx->ibuf; +} + +static ssize_t wslay_recv(wslay_frame_context_ptr ctx) { + ssize_t r; + if (ctx->ibufmark != ctx->ibuf) { + wslay_shift_ibuf(ctx); + } + r = ctx->callbacks.recv_callback( + ctx->ibuflimit, (size_t)(ctx->ibuf + sizeof(ctx->ibuf) - ctx->ibuflimit), + 0, ctx->user_data); + if (r > 0) { + ctx->ibuflimit += r; + } else { + r = WSLAY_ERR_WANT_READ; + } + return r; +} + +#define WSLAY_AVAIL_IBUF(ctx) ((size_t)(ctx->ibuflimit - ctx->ibufmark)) + +ssize_t wslay_frame_recv(wslay_frame_context_ptr ctx, + struct wslay_frame_iocb *iocb) { + ssize_t r; + if (ctx->istate == RECV_HEADER1) { + uint8_t fin, opcode, rsv, payloadlen; + if (WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) { + if ((r = wslay_recv(ctx)) <= 0) { + return r; + } + } + if (WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) { + return WSLAY_ERR_WANT_READ; + } + fin = (ctx->ibufmark[0] >> 7) & 1; + rsv = (ctx->ibufmark[0] >> 4) & 7; + opcode = ctx->ibufmark[0] & 0xfu; + ctx->iom.opcode = opcode; + ctx->iom.fin = fin; + ctx->iom.rsv = rsv; + ++ctx->ibufmark; + ctx->imask = (ctx->ibufmark[0] >> 7) & 1; + payloadlen = ctx->ibufmark[0] & 0x7fu; + ++ctx->ibufmark; + if (wslay_is_ctrl_frame(opcode) && (payloadlen > 125 || !fin)) { + return WSLAY_ERR_PROTO; + } + if (payloadlen == 126) { + ctx->istate = RECV_EXT_PAYLOADLEN; + ctx->ireqread = 2; + } else if (payloadlen == 127) { + ctx->istate = RECV_EXT_PAYLOADLEN; + ctx->ireqread = 8; + } else { + ctx->ipayloadlen = payloadlen; + ctx->ipayloadoff = 0; + if (ctx->imask) { + ctx->istate = RECV_MASKKEY; + ctx->ireqread = 4; + } else { + ctx->istate = RECV_PAYLOAD; + } + } + } + if (ctx->istate == RECV_EXT_PAYLOADLEN) { + if (WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) { + if ((r = wslay_recv(ctx)) <= 0) { + return r; + } + if (WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) { + return WSLAY_ERR_WANT_READ; + } + } + ctx->ipayloadlen = 0; + ctx->ipayloadoff = 0; + memcpy((uint8_t *)&ctx->ipayloadlen + (8 - ctx->ireqread), ctx->ibufmark, + ctx->ireqread); + ctx->ipayloadlen = ntoh64(ctx->ipayloadlen); + ctx->ibufmark += ctx->ireqread; + if (ctx->ireqread == 8) { + if (ctx->ipayloadlen < (1 << 16) || ctx->ipayloadlen & (1ull << 63)) { + return WSLAY_ERR_PROTO; + } + } else if (ctx->ipayloadlen < 126) { + return WSLAY_ERR_PROTO; + } + if (ctx->imask) { + ctx->istate = RECV_MASKKEY; + ctx->ireqread = 4; + } else { + ctx->istate = RECV_PAYLOAD; + } + } + if (ctx->istate == RECV_MASKKEY) { + if (WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) { + if ((r = wslay_recv(ctx)) <= 0) { + return r; + } + if (WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) { + return WSLAY_ERR_WANT_READ; + } + } + memcpy(ctx->imaskkey, ctx->ibufmark, 4); + ctx->ibufmark += 4; + ctx->istate = RECV_PAYLOAD; + } + if (ctx->istate == RECV_PAYLOAD) { + uint8_t *readlimit, *readmark; + uint64_t rempayloadlen = ctx->ipayloadlen - ctx->ipayloadoff; + if (WSLAY_AVAIL_IBUF(ctx) == 0 && rempayloadlen > 0) { + if ((r = wslay_recv(ctx)) <= 0) { + return r; + } + } + readmark = ctx->ibufmark; + readlimit = WSLAY_AVAIL_IBUF(ctx) < rempayloadlen + ? ctx->ibuflimit + : ctx->ibufmark + rempayloadlen; + if (ctx->imask) { + for (; ctx->ibufmark != readlimit; ++ctx->ibufmark, ++ctx->ipayloadoff) { + ctx->ibufmark[0] ^= ctx->imaskkey[ctx->ipayloadoff % 4]; + } + } else { + ctx->ibufmark = readlimit; + ctx->ipayloadoff += (uint64_t)(readlimit - readmark); + } + iocb->fin = ctx->iom.fin; + iocb->rsv = ctx->iom.rsv; + iocb->opcode = ctx->iom.opcode; + iocb->payload_length = ctx->ipayloadlen; + iocb->mask = ctx->imask; + iocb->data = readmark; + iocb->data_length = (size_t)(ctx->ibufmark - readmark); + if (ctx->ipayloadlen == ctx->ipayloadoff) { + ctx->istate = RECV_HEADER1; + ctx->ireqread = 2; + } + return (ssize_t)iocb->data_length; + } + return WSLAY_ERR_INVALID_ARGUMENT; +} diff --git a/src/spice2x/external/wslay/wslay_frame.h b/src/spice2x/external/wslay/wslay_frame.h new file mode 100644 index 0000000..3ac9811 --- /dev/null +++ b/src/spice2x/external/wslay/wslay_frame.h @@ -0,0 +1,77 @@ +/* + * Wslay - The WebSocket Library + * + * Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef WSLAY_FRAME_H +#define WSLAY_FRAME_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +enum wslay_frame_state { + PREP_HEADER, + PREP_HEADER_NOBUF, + SEND_HEADER, + SEND_PAYLOAD, + RECV_HEADER1, + RECV_PAYLOADLEN, + RECV_EXT_PAYLOADLEN, + RECV_MASKKEY, + RECV_PAYLOAD +}; + +struct wslay_frame_opcode_memo { + uint8_t fin; + uint8_t opcode; + uint8_t rsv; +}; + +struct wslay_frame_context { + uint8_t ibuf[4096]; + uint8_t *ibufmark; + uint8_t *ibuflimit; + struct wslay_frame_opcode_memo iom; + uint64_t ipayloadlen; + uint64_t ipayloadoff; + uint8_t imask; + uint8_t imaskkey[4]; + enum wslay_frame_state istate; + size_t ireqread; + + uint8_t oheader[14]; + uint8_t *oheadermark; + uint8_t *oheaderlimit; + uint64_t opayloadlen; + uint64_t opayloadoff; + uint8_t omask; + uint8_t omaskkey[4]; + enum wslay_frame_state ostate; + + struct wslay_frame_callbacks callbacks; + void *user_data; +}; + +#endif /* WSLAY_FRAME_H */ diff --git a/src/spice2x/external/wslay/wslay_macro.h b/src/spice2x/external/wslay/wslay_macro.h new file mode 100644 index 0000000..a06cff9 --- /dev/null +++ b/src/spice2x/external/wslay/wslay_macro.h @@ -0,0 +1,39 @@ +/* + * Wslay - The WebSocket Library + * + * Copyright (c) 2020 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef WSLAY_MACRO_H +#define WSLAY_MACRO_H + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include + +#include + +#define wslay_struct_of(ptr, type, member) \ + ((type *)(void *)((char *)(ptr)-offsetof(type, member))) + +#endif /* WSLAY_MACRO_H */ diff --git a/src/spice2x/external/wslay/wslay_net.c b/src/spice2x/external/wslay/wslay_net.c new file mode 100644 index 0000000..e256158 --- /dev/null +++ b/src/spice2x/external/wslay/wslay_net.c @@ -0,0 +1,35 @@ +/* + * Wslay - The WebSocket Library + * + * Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "wslay_net.h" + +#ifndef WORDS_BIGENDIAN + +uint64_t wslay_byteswap64(uint64_t x) { + uint64_t u = ntohl(x & 0xffffffffllu); + uint64_t l = ntohl((uint32_t)(x >> 32)); + return (u << 32) | l; +} + +#endif /* !WORDS_BIGENDIAN */ diff --git a/src/spice2x/external/wslay/wslay_net.h b/src/spice2x/external/wslay/wslay_net.h new file mode 100644 index 0000000..2310870 --- /dev/null +++ b/src/spice2x/external/wslay/wslay_net.h @@ -0,0 +1,54 @@ +/* + * Wslay - The WebSocket Library + * + * Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef WSLAY_NET_H +#define WSLAY_NET_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#ifdef HAVE_ARPA_INET_H +# include +#endif /* HAVE_ARPA_INET_H */ +#ifdef HAVE_NETINET_IN_H +# include +#endif /* HAVE_NETINET_IN_H */ +/* For Mingw build */ +#ifdef HAVE_WINSOCK2_H +# include +#endif /* HAVE_WINSOCK2_H */ + +#ifdef WORDS_BIGENDIAN +# define ntoh64(x) (x) +# define hton64(x) (x) +#else /* !WORDS_BIGENDIAN */ +uint64_t wslay_byteswap64(uint64_t x); +# define ntoh64(x) wslay_byteswap64(x) +# define hton64(x) wslay_byteswap64(x) +#endif /* !WORDS_BIGENDIAN */ + +#endif /* WSLAY_NET_H */ diff --git a/src/spice2x/external/wslay/wslay_queue.c b/src/spice2x/external/wslay/wslay_queue.c new file mode 100644 index 0000000..ebde3ba --- /dev/null +++ b/src/spice2x/external/wslay/wslay_queue.c @@ -0,0 +1,77 @@ +/* + * Wslay - The WebSocket Library + * + * Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "wslay_queue.h" + +#include +#include + +#include "wslay_macro.h" + +void wslay_queue_init(struct wslay_queue *queue) { + queue->top = NULL; + queue->tail = &queue->top; +} + +void wslay_queue_deinit(struct wslay_queue *queue) { (void)queue; } + +void wslay_queue_push(struct wslay_queue *queue, + struct wslay_queue_entry *ent) { + ent->next = NULL; + *queue->tail = ent; + queue->tail = &ent->next; +} + +void wslay_queue_push_front(struct wslay_queue *queue, + struct wslay_queue_entry *ent) { + ent->next = queue->top; + queue->top = ent; + + if (ent->next == NULL) { + queue->tail = &ent->next; + } +} + +void wslay_queue_pop(struct wslay_queue *queue) { + assert(queue->top); + queue->top = queue->top->next; + if (queue->top == NULL) { + queue->tail = &queue->top; + } +} + +struct wslay_queue_entry *wslay_queue_top(struct wslay_queue *queue) { + assert(queue->top); + return queue->top; +} + +struct wslay_queue_entry *wslay_queue_tail(struct wslay_queue *queue) { + assert(queue->top); + return wslay_struct_of(queue->tail, struct wslay_queue_entry, next); +} + +int wslay_queue_empty(struct wslay_queue *queue) { + assert(queue->top || queue->tail == &queue->top); + return queue->top == NULL; +} diff --git a/src/spice2x/external/wslay/wslay_queue.h b/src/spice2x/external/wslay/wslay_queue.h new file mode 100644 index 0000000..fa16aea --- /dev/null +++ b/src/spice2x/external/wslay/wslay_queue.h @@ -0,0 +1,53 @@ +/* + * Wslay - The WebSocket Library + * + * Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef WSLAY_QUEUE_H +#define WSLAY_QUEUE_H + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include + +struct wslay_queue_entry { + struct wslay_queue_entry *next; +}; + +struct wslay_queue { + struct wslay_queue_entry *top; + struct wslay_queue_entry **tail; +}; + +void wslay_queue_init(struct wslay_queue *queue); +void wslay_queue_deinit(struct wslay_queue *queue); +void wslay_queue_push(struct wslay_queue *queue, struct wslay_queue_entry *ent); +void wslay_queue_push_front(struct wslay_queue *queue, + struct wslay_queue_entry *ent); +void wslay_queue_pop(struct wslay_queue *queue); +struct wslay_queue_entry *wslay_queue_top(struct wslay_queue *queue); +struct wslay_queue_entry *wslay_queue_tail(struct wslay_queue *queue); +int wslay_queue_empty(struct wslay_queue *queue); + +#endif /* WSLAY_QUEUE_H */ diff --git a/src/spice2x/games/drs/drs.cpp b/src/spice2x/games/drs/drs.cpp index 7d2bf6a..ceac441 100644 --- a/src/spice2x/games/drs/drs.cpp +++ b/src/spice2x/games/drs/drs.cpp @@ -9,6 +9,8 @@ #include "util/logging.h" #include "util/precise_timer.h" #include "util/memutils.h" +#include "io.h" +#include "motion_cam.h" #include "rgb_cam.h" #pragma pack(push) @@ -276,48 +278,50 @@ namespace games::drs { void DRSGame::attach() { Game::attach(); - // TouchSDK hooks - detour::iat("??0TouchSDK@@QEAA@XZ", - (void *) &TouchSDK_Constructor, avs::game::DLL_INSTANCE); - detour::iat("?SendData@TouchSDK@@QEAA_NU_DeviceInfo@@QEAEH1HH@Z", - (void *) &TouchSDK_SendData, avs::game::DLL_INSTANCE); - detour::iat("?SetSignalInit@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", - (void *) &TouchSDK_SetSignalInit, avs::game::DLL_INSTANCE); - detour::iat("??1TouchSDK@@QEAA@XZ", - (void *) &TouchSDK_Destructor, avs::game::DLL_INSTANCE); - detour::iat("?GetYLedTotal@TouchSDK@@QEAAHU_DeviceInfo@@H@Z", - (void *) &TouchSDK_GetYLedTotal, avs::game::DLL_INSTANCE); - detour::iat("?GetXLedTotal@TouchSDK@@QEAAHU_DeviceInfo@@H@Z", - (void *) &TouchSDK_GetXLedTotal, avs::game::DLL_INSTANCE); - detour::iat("?DisableTouch@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", - (void *) &TouchSDK_DisableTouch, avs::game::DLL_INSTANCE); - detour::iat("?DisableDrag@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", - (void *) &TouchSDK_DisableDrag, avs::game::DLL_INSTANCE); - detour::iat("?DisableWheel@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", - (void *) &TouchSDK_DisableWheel, avs::game::DLL_INSTANCE); - detour::iat("?DisableRightClick@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", - (void *) &TouchSDK_DisableRightClick, avs::game::DLL_INSTANCE); - detour::iat("?SetMultiTouchMode@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", - (void *) &TouchSDK_SetMultiTouchMode, avs::game::DLL_INSTANCE); - detour::iat("?EnableTouchWidthData@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", - (void *) &TouchSDK_EnableTouchWidthData, avs::game::DLL_INSTANCE); - detour::iat("?EnableRawData@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", - (void *) &TouchSDK_EnableRawData, avs::game::DLL_INSTANCE); - detour::iat("?SetAllEnable@TouchSDK@@QEAA_NU_DeviceInfo@@_NH@Z", - (void *) &TouchSDK_SetAllEnable, avs::game::DLL_INSTANCE); - detour::iat("?GetTouchDeviceCount@TouchSDK@@QEAAHXZ", - (void *) &TouchSDK_GetTouchDeviceCount, avs::game::DLL_INSTANCE); - detour::iat("?GetTouchSDKVersion@TouchSDK@@QEAAIXZ", - (void *) &TouchSDK_GetTouchSDKVersion, avs::game::DLL_INSTANCE); - detour::iat("?InitTouch@TouchSDK@@QEAAHPEAU_DeviceInfo@@HP6AXU2@PEBU_TouchPointData@@HHPEBX@ZP6AX1_N3@ZPEAX@Z", - (void *) &TouchSDK_InitTouch, avs::game::DLL_INSTANCE); - if (!DISABLE_TOUCH) { + // TouchSDK hooks + detour::iat("??0TouchSDK@@QEAA@XZ", + (void *) &TouchSDK_Constructor, avs::game::DLL_INSTANCE); + detour::iat("?SendData@TouchSDK@@QEAA_NU_DeviceInfo@@QEAEH1HH@Z", + (void *) &TouchSDK_SendData, avs::game::DLL_INSTANCE); + detour::iat("?SetSignalInit@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", + (void *) &TouchSDK_SetSignalInit, avs::game::DLL_INSTANCE); + detour::iat("??1TouchSDK@@QEAA@XZ", + (void *) &TouchSDK_Destructor, avs::game::DLL_INSTANCE); + detour::iat("?GetYLedTotal@TouchSDK@@QEAAHU_DeviceInfo@@H@Z", + (void *) &TouchSDK_GetYLedTotal, avs::game::DLL_INSTANCE); + detour::iat("?GetXLedTotal@TouchSDK@@QEAAHU_DeviceInfo@@H@Z", + (void *) &TouchSDK_GetXLedTotal, avs::game::DLL_INSTANCE); + detour::iat("?DisableTouch@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", + (void *) &TouchSDK_DisableTouch, avs::game::DLL_INSTANCE); + detour::iat("?DisableDrag@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", + (void *) &TouchSDK_DisableDrag, avs::game::DLL_INSTANCE); + detour::iat("?DisableWheel@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", + (void *) &TouchSDK_DisableWheel, avs::game::DLL_INSTANCE); + detour::iat("?DisableRightClick@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", + (void *) &TouchSDK_DisableRightClick, avs::game::DLL_INSTANCE); + detour::iat("?SetMultiTouchMode@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", + (void *) &TouchSDK_SetMultiTouchMode, avs::game::DLL_INSTANCE); + detour::iat("?EnableTouchWidthData@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", + (void *) &TouchSDK_EnableTouchWidthData, avs::game::DLL_INSTANCE); + detour::iat("?EnableRawData@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z", + (void *) &TouchSDK_EnableRawData, avs::game::DLL_INSTANCE); + detour::iat("?SetAllEnable@TouchSDK@@QEAA_NU_DeviceInfo@@_NH@Z", + (void *) &TouchSDK_SetAllEnable, avs::game::DLL_INSTANCE); + detour::iat("?GetTouchDeviceCount@TouchSDK@@QEAAHXZ", + (void *) &TouchSDK_GetTouchDeviceCount, avs::game::DLL_INSTANCE); + detour::iat("?GetTouchSDKVersion@TouchSDK@@QEAAIXZ", + (void *) &TouchSDK_GetTouchSDKVersion, avs::game::DLL_INSTANCE); + detour::iat("?InitTouch@TouchSDK@@QEAAHPEAU_DeviceInfo@@HP6AXU2@PEBU_TouchPointData@@HHPEBX@ZP6AX1_N3@ZPEAX@Z", + (void *) &TouchSDK_InitTouch, avs::game::DLL_INSTANCE); + start_touch(); } else { - log_info("drs", "no native input method detected"); + log_info("drs", "touch input for dance floor disabled"); } + init_down_motion_hook(); + if (RGB_CAMERA_HOOK) { init_rgb_camera_hook(); } diff --git a/src/spice2x/games/drs/io.cpp b/src/spice2x/games/drs/io.cpp index 4cf48ee..4979a61 100644 --- a/src/spice2x/games/drs/io.cpp +++ b/src/spice2x/games/drs/io.cpp @@ -20,7 +20,8 @@ std::vector