From e7b90271ca06e121b2136f0b159facc33bad2085 Mon Sep 17 00:00:00 2001 From: bicarus <202771338+bicarus-dev@users.noreply.github.com> Date: Sat, 22 Aug 2026 21:47:26 -0700 Subject: [PATCH] api: serve the websocket with wslay instead of headsocket (#887) `HeadSocket` library we are currently using is full of bugs and no longer maintained. Swap it out with `wslay`. --- src/spice2x/CMakeLists.txt | 13 +- src/spice2x/api/websocket.cpp | 731 ++++-- src/spice2x/external/headsocket.h | 2195 ----------------- src/spice2x/external/wslay/CMakeLists.txt | 33 + src/spice2x/external/wslay/LICENSE | 22 + src/spice2x/external/wslay/config.h | 9 + .../external/wslay/includes/wslay/wslay.h | 841 +++++++ .../external/wslay/includes/wslay/wslayver.h | 31 + src/spice2x/external/wslay/msvc_compat.h | 9 + src/spice2x/external/wslay/wslay_event.c | 1082 ++++++++ src/spice2x/external/wslay/wslay_event.h | 138 ++ src/spice2x/external/wslay/wslay_frame.c | 438 ++++ src/spice2x/external/wslay/wslay_frame.h | 77 + src/spice2x/external/wslay/wslay_macro.h | 39 + src/spice2x/external/wslay/wslay_net.c | 35 + src/spice2x/external/wslay/wslay_net.h | 54 + src/spice2x/external/wslay/wslay_queue.c | 77 + src/spice2x/external/wslay/wslay_queue.h | 53 + src/spice2x/licenses.txt | 46 + 19 files changed, 3538 insertions(+), 2385 deletions(-) delete mode 100644 src/spice2x/external/headsocket.h create mode 100644 src/spice2x/external/wslay/CMakeLists.txt create mode 100644 src/spice2x/external/wslay/LICENSE create mode 100644 src/spice2x/external/wslay/config.h create mode 100644 src/spice2x/external/wslay/includes/wslay/wslay.h create mode 100644 src/spice2x/external/wslay/includes/wslay/wslayver.h create mode 100644 src/spice2x/external/wslay/msvc_compat.h create mode 100644 src/spice2x/external/wslay/wslay_event.c create mode 100644 src/spice2x/external/wslay/wslay_event.h create mode 100644 src/spice2x/external/wslay/wslay_frame.c create mode 100644 src/spice2x/external/wslay/wslay_frame.h create mode 100644 src/spice2x/external/wslay/wslay_macro.h create mode 100644 src/spice2x/external/wslay/wslay_net.c create mode 100644 src/spice2x/external/wslay/wslay_net.h create mode 100644 src/spice2x/external/wslay/wslay_queue.c create mode 100644 src/spice2x/external/wslay/wslay_queue.h diff --git a/src/spice2x/CMakeLists.txt b/src/spice2x/CMakeLists.txt index 973b867..0faaa2b 100644 --- a/src/spice2x/CMakeLists.txt +++ b/src/spice2x/CMakeLists.txt @@ -254,6 +254,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 @@ -797,7 +798,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) @@ -837,7 +838,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) @@ -855,7 +856,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") @@ -878,7 +879,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) @@ -900,7 +901,7 @@ 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") @@ -918,7 +919,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/api/websocket.cpp b/src/spice2x/api/websocket.cpp index be7c67c..f530c12 100644 --- a/src/spice2x/api/websocket.cpp +++ b/src/spice2x/api/websocket.cpp @@ -1,216 +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; - - // headsocket doesn't expose the peer address on its own client API, but the - // sockaddr_in captured at accept time is sitting right there in the impl - std::string remote_address() const { - char address_data[INET_ADDRSTRLEN] {}; - inet_ntop(AF_INET, &this->_p->conn.impl()->from.sin_addr, - address_data, INET_ADDRSTRLEN); - return std::string(address_data); - } - - 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 - const auto address = this->remote_address(); - log_info("api::websocket", "client connected: {}", address); - overlay::notifications::add( - overlay::notifications::Severity::Success, - fmt::format("API websocket client connected ({})", address)); - } - - void WebSocketClient::on_disconnect() { - - // log disconnection - const auto address = this->remote_address(); - log_info("api::websocket", "client disconnected: {}", address); - overlay::notifications::add( - overlay::notifications::Severity::Info, - fmt::format("API websocket client disconnected ({})", address)); - - // 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 fdad8c5..0000000 --- a/src/spice2x/external/headsocket.h +++ /dev/null @@ -1,2195 +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; - // addrlen must be a valid in/out pointer or the OS leaves conn_impl.from untouched, - // so the peer address silently reads back as 0.0.0.0 - int from_len = sizeof(conn_impl.from); - conn_impl.socket = ::accept(_p->serverSocket, reinterpret_cast(&conn_impl.from), &from_len); - 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/licenses.txt b/src/spice2x/licenses.txt index e552c1f..9021636 100644 --- a/src/spice2x/licenses.txt +++ b/src/spice2x/licenses.txt @@ -1402,6 +1402,52 @@ 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. +Wslay (MIT) +------------------------------------------- +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. + +Wslay UTF-8 decoder (MIT) +------------------------------------------- +Copyright (c) 2008-2010 Bjoern Hoehrmann +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. + Contributions ------------------------------------------- cardio - Felix - MIT License