#ifdef WITH_NETWORKING #include "websocket.h" #include "socket_optimization.h" #include #include #include // SSE2 #include #include #include #include #include #ifdef _WIN32 #define NOMINMAX #include #include #include #pragma comment(lib, "ws2_32.lib") #else #define SOCKET int #define INVALID_SOCKET -1 #define SOCKET_ERROR -1 #define closesocket close #endif #include #include #include #include #include "ring_buffer.h" #ifdef WITH_SSL #ifdef _WIN32 #define SECURITY_WIN32 #include #include #include #include #pragma comment(lib, "crypt32.lib") #pragma comment(lib, "secur32.lib") #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif #endif using namespace v8; namespace WebSocketWrapper { // global state static std::unordered_map> active_websockets; static std::atomic next_websocket_id{1}; static std::atomic winsock_initialized{false}; void initialize() { if (!winsock_initialized.exchange(true)) { WSADATA wsaData; int result = WSAStartup(MAKEWORD(2, 2), &wsaData); if (result != 0) { kinc_log(KINC_LOG_LEVEL_ERROR, "WSAStartup failed: %d", result); } #ifdef DEBUG_NETWORK else { kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket client support initialized successfully"); } #endif } } void cleanup() { active_websockets.clear(); if (winsock_initialized.exchange(false)) { WSACleanup(); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket cleanup complete"); #endif } } void processEvents() { try { for (auto& [id, client] : active_websockets) { if (client) { client->processEvents(); } } } catch (...) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: CRASH in global processEvents"); #endif } } int createWebSocketConnection(v8::Isolate* isolate, const std::string& url) { int id = next_websocket_id++; auto client = std::make_unique(isolate, getGlobalContext(), url); active_websockets[id] = std::move(client); return id; } class SimpleWebSocketClient { private: SOCKET socket_; std::thread* thread_; std::atomic connected_{false}; std::atomic should_stop_{false}; std::string host_; int port_; std::string path_; public: SimpleWebSocketClient(const std::string& url) : socket_(INVALID_SOCKET), thread_(nullptr) { parseUrl(url); } ~SimpleWebSocketClient() { disconnect(); } bool connect() { socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (socket_ == INVALID_SOCKET) { return false; } SocketOptimization::optimizeWebSocket(static_cast(socket_)); sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_port = htons(port_); inet_pton(AF_INET, host_.c_str(), &addr.sin_addr); if (::connect(socket_, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { closesocket(socket_); socket_ = INVALID_SOCKET; return false; } std::string handshake = "GET " + path_ + " HTTP/1.1\r\n" "Host: " + host_ + ":" + std::to_string(port_) + "\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n\r\n"; if (send(socket_, handshake.c_str(), static_cast(handshake.length()), 0) == SOCKET_ERROR) { closesocket(socket_); socket_ = INVALID_SOCKET; return false; } char buffer[SocketOptimization::SMALL_BUFFER_SIZE]; int bytes = recv(socket_, buffer, sizeof(buffer) - 1, 0); if (bytes > 0) { buffer[bytes] = '\0'; if (strstr(buffer, "101 Switching Protocols")) { connected_ = true; return true; } } closesocket(socket_); socket_ = INVALID_SOCKET; return false; } void disconnect() { should_stop_ = true; if (socket_ != INVALID_SOCKET) { closesocket(socket_); socket_ = INVALID_SOCKET; } if (thread_ && thread_->joinable()) { thread_->join(); delete thread_; thread_ = nullptr; } connected_ = false; } bool isConnected() const { return connected_; } void sendText(const std::string& data) { if (!connected_ || socket_ == INVALID_SOCKET) return; // text frame format FIN + text frame std::vector frame; frame.push_back(0x81); if (data.length() < 126) { frame.push_back(0x80 | static_cast(data.length())); // MASK + length } else { // TODO: support longer messages return; } // adds simplified masking key uint8_t mask[4] = {0x12, 0x34, 0x56, 0x78}; frame.insert(frame.end(), mask, mask + 4); // adds the masked payload for (size_t i = 0; i < data.length(); i++) { frame.push_back(data[i] ^ mask[i % 4]); } send(socket_, (char*)frame.data(), static_cast(frame.size()), 0); } private: void parseUrl(const std::string& url) { std::regex url_regex(R"(^wss?://([^:/]+)(?::(\d+))?(/.*)?$)"); std::smatch matches; if (std::regex_match(url, matches, url_regex)) { host_ = matches[1].str(); port_ = matches[2].matched ? std::stoi(matches[2].str()) : 80; path_ = matches[3].matched ? matches[3].str() : "/"; } else { host_ = "localhost"; port_ = 80; path_ = "/"; } } }; WebSocketClient::WebSocketClient(Isolate* isolate, Global* global_context, const std::string& url) : isolate_(isolate), global_context_(global_context), url_(url), ready_state_(CONNECTING), is_ssl_(false), buffered_amount_(0), ws_(nullptr), ssl_ws_(nullptr) #ifdef WITH_SSL #ifdef _WIN32 , ssl_cred_handle_(nullptr), ssl_context_handle_(nullptr), ssl_context_initialized_(false), ssl_socket_(-1) #else , ssl_ctx_(nullptr), ssl_(nullptr), ssl_initialized_(false) #endif #endif { #ifdef WITH_SSL #ifdef _WIN32 memset(&ssl_cred_handle_, 0, sizeof(ssl_cred_handle_)); memset(&ssl_context_handle_, 0, sizeof(ssl_context_handle_)); #endif #endif #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "Creating WebSocket connection to: %s", url.c_str()); #endif std::string host, path; int port; parseUrl(url, host, port, path); is_ssl_ = (url.find("wss://") == 0); ready_state_ = CONNECTING; // start connection using thread pool GlobalThreadPool::getInstance().enqueue([this, host, port, path]() { try { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket connecting to %s:%d%s (SSL: %s)", host.c_str(), port, path.c_str(), is_ssl_ ? "yes" : "no"); #endif if (connectToServer(host, port, path)) { ready_state_ = OPEN; handleOpen(); messageLoop(); } else { ready_state_ = CLOSED; handleError("Connection failed"); } } catch (...) { ready_state_ = CLOSED; } }); } WebSocketClient::~WebSocketClient() { if (ready_state_ == OPEN) { close(); } #ifdef WITH_SSL cleanupWSL(); #endif } bool WebSocketClient::parseUrl(const std::string& url, std::string& host, int& port, std::string& path) { std::regex url_regex(R"(^(wss?)://([^:/]+)(?::(\d+))?(/.*)?$)"); std::smatch matches; if (!std::regex_match(url, matches, url_regex)) { return false; } std::string protocol = matches[1].str(); host = matches[2].str(); if (matches[3].matched) { port = std::stoi(matches[3].str()); } else { port = (protocol == "wss") ? 443 : 80; } path = matches[4].matched ? matches[4].str() : "/"; is_ssl_ = (protocol == "wss"); return true; } void WebSocketClient::send(const std::string& data) { if (ready_state_ != OPEN) { handleError("WebSocket is not open"); return; } if (ws_ == nullptr) { handleError("WebSocket: No socket available for sending"); return; } try { buffered_amount_ += static_cast(data.length()); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket sending: %s", data.c_str()); #endif // create and send WebSocket frame std::vector frame = createWebSocketFrame(data); int sock = static_cast(reinterpret_cast(ws_)); int sendResult; if (is_ssl_ && ssl_context_initialized_) { sendResult = webSocketSSLSend(frame.data(), static_cast(frame.size())); } else { sendResult = ::send(sock, reinterpret_cast(frame.data()), static_cast(frame.size()), 0); } if (sendResult <= 0) { buffered_amount_ = 0; handleError("WebSocket: Failed to send message"); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to send %d bytes to server", (int)frame.size()); #endif return; } #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Successfully sent %d bytes to server", sendResult); #endif buffered_amount_ = 0; } catch (const std::exception& e) { buffered_amount_ = 0; handleError(std::string("Send error: ") + e.what()); } } void WebSocketClient::sendBinary(const std::string& data) { if (ready_state_ != OPEN) { handleError("WebSocket is not open"); return; } if (ws_ == nullptr) { handleError("WebSocket: No socket available for sending"); return; } try { buffered_amount_ += static_cast(data.length()); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket sending binary: %d bytes", (int)data.length()); #endif // binary frame with opcode 0x02 std::vector frame = createWebSocketBinaryFrame(data); int sock = static_cast(reinterpret_cast(ws_)); int sendResult; if (is_ssl_ && ssl_context_initialized_) { sendResult = webSocketSSLSend(frame.data(), static_cast(frame.size())); } else { sendResult = ::send(sock, reinterpret_cast(frame.data()), static_cast(frame.size()), 0); } if (sendResult <= 0) { buffered_amount_ = 0; handleError("WebSocket: Failed to send binary message"); return; } #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Successfully sent %d binary bytes to server", sendResult); #endif buffered_amount_ = 0; } catch (const std::exception& e) { buffered_amount_ = 0; handleError(std::string("Send binary error: ") + e.what()); } } void WebSocketClient::close(int code, const std::string& reason) { if (ready_state_ == CLOSING || ready_state_ == CLOSED) { return; } ready_state_ = CLOSING; ready_state_ = CLOSED; handleClose(code, reason); } void WebSocketClient::setOnOpen(Local callback) { on_open_.Reset(isolate_, callback); } void WebSocketClient::setOnMessage(Local callback) { on_message_.Reset(isolate_, callback); } void WebSocketClient::setOnError(Local callback) { on_error_.Reset(isolate_, callback); } void WebSocketClient::setOnClose(Local callback) { on_close_.Reset(isolate_, callback); } // event handlers called from worker thread that queue events safely void WebSocketClient::handleOpen() { event_queue_.push(WebSocketEvent(EVENT_OPEN)); } void WebSocketClient::handleMessage(const std::string& message) { // push directly to lock free queue event_queue_.push(WebSocketEvent(EVENT_MESSAGE, message)); } void WebSocketClient::handleError(const std::string& error) { try { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket error: %s", error.c_str()); #endif event_queue_.push(WebSocketEvent(EVENT_ERROR, error)); } catch (...) { // silent crash } } void WebSocketClient::handleClose(int code, const std::string& reason) { ready_state_ = CLOSED; event_queue_.push(WebSocketEvent(EVENT_CLOSE, code, reason)); } // process events on main thread with lock free queue and batching void WebSocketClient::processEvents() { try { // collect for batched processing and pre allocate std::vector pending_events; pending_events.reserve(64); WebSocketEvent event; while (event_queue_.try_pop(event)) { pending_events.push_back(std::move(event)); } if (pending_events.empty()) return; // message events for batch processing std::vector message_batch; message_batch.reserve(pending_events.size()); // process non message events and collect messages for (auto& evt : pending_events) { switch (evt.type) { case EVENT_OPEN: processOpenEvent(); break; case EVENT_MESSAGE: message_batch.push_back(std::move(evt.data)); break; case EVENT_ERROR: processErrorEvent(evt.data); break; case EVENT_CLOSE: processCloseEvent(evt.code, evt.reason); break; } } // batch process all messages if (!message_batch.empty()) { processMessageBatch(message_batch); } } catch (...) { kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: CRASH in processEvents"); } } void WebSocketClient::processMessageBatch(const std::vector& messages) { if (on_message_.IsEmpty() || messages.empty()) return; Locker locker{isolate_}; Isolate::Scope isolate_scope(isolate_); HandleScope handle_scope(isolate_); Local context = Local::New(isolate_, *global_context_); Context::Scope context_scope(context); Local func = on_message_.Get(isolate_); Local dataKey = String::NewFromUtf8(isolate_, "data").ToLocalChecked(); for (const auto& message : messages) { Local event = Object::New(isolate_); Local arrayBuffer = ArrayBuffer::New(isolate_, message.length()); std::shared_ptr backing = arrayBuffer->GetBackingStore(); memcpy(backing->Data(), message.c_str(), message.length()); event->Set(context, dataKey, arrayBuffer); Local argv[1] = { event }; TryCatch try_catch(isolate_); func->Call(context, context->Global(), 1, argv); } } // called on main thread void WebSocketClient::processOpenEvent() { if (!on_open_.IsEmpty()) { Local argv[1] = {}; callCallback(on_open_, 0, argv); } } void WebSocketClient::processMessageEvent(const std::string& message) { if (!on_message_.IsEmpty()) { Locker locker{isolate_}; Isolate::Scope isolate_scope(isolate_); HandleScope handle_scope(isolate_); Local context = Local::New(isolate_, *global_context_); Context::Scope context_scope(context); Local event = Object::New(isolate_); Local arrayBuffer = ArrayBuffer::New(isolate_, message.length()); std::shared_ptr backing = arrayBuffer->GetBackingStore(); memcpy(backing->Data(), message.c_str(), message.length()); event->Set(context, String::NewFromUtf8(isolate_, "data").ToLocalChecked(), arrayBuffer); Local argv[1] = { event }; callCallback(on_message_, 1, argv); } } void WebSocketClient::processErrorEvent(const std::string& error) { kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: processErrorEvent START"); try { if (!on_error_.IsEmpty()) { kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: processErrorEvent getting locker"); Locker locker{isolate_}; kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: processErrorEvent setting up V8"); Isolate::Scope isolate_scope(isolate_); HandleScope handle_scope(isolate_); Local context = Local::New(isolate_, *global_context_); Context::Scope context_scope(context); kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: processErrorEvent creating event object"); Local event = Object::New(isolate_); event->Set(context, String::NewFromUtf8(isolate_, "message").ToLocalChecked(), String::NewFromUtf8(isolate_, error.c_str()).ToLocalChecked()); kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: processErrorEvent calling callback"); Local argv[1] = { event }; callCallback(on_error_, 1, argv); kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: processErrorEvent DONE"); } } catch (...) { kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: CRASH in processErrorEvent"); } } void WebSocketClient::processCloseEvent(int code, const std::string& reason) { if (!on_close_.IsEmpty()) { Locker locker{isolate_}; Isolate::Scope isolate_scope(isolate_); HandleScope handle_scope(isolate_); Local context = Local::New(isolate_, *global_context_); Context::Scope context_scope(context); Local event = Object::New(isolate_); event->Set(context, String::NewFromUtf8(isolate_, "code").ToLocalChecked(), Number::New(isolate_, code)); event->Set(context, String::NewFromUtf8(isolate_, "reason").ToLocalChecked(), String::NewFromUtf8(isolate_, reason.c_str()).ToLocalChecked()); Local argv[1] = { event }; callCallback(on_close_, 1, argv); } } void WebSocketClient::callCallback(Global& callback, int argc, Local argv[]) { if (callback.IsEmpty()) return; Locker locker{isolate_}; Isolate::Scope isolate_scope(isolate_); HandleScope handle_scope(isolate_); Local context = Local::New(isolate_, *global_context_); Context::Scope context_scope(context); TryCatch try_catch(isolate_); Local func = callback.Get(isolate_); Local result; if (func->Call(context, context->Global(), argc, argv).ToLocal(&result)) { // Success if (try_catch.HasCaught()) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket callback error"); #endif } } } int WebSocketClient::webSocketSSLReceive(char* buffer, int bufferSize) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: webSocketSSLReceive called with bufferSize=%d", bufferSize); #endif if (!ssl_context_initialized_) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: SSL not initialized for receive"); #endif return -1; } #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL context initialized, starting receive process"); #endif // first try to decrypt any existing buffered data if (!ssl_buffer_.empty()) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Attempting to decrypt existing buffer data (%d bytes)", (int)ssl_buffer_.size()); #endif SecBuffer buffers[4]; SecBufferDesc message; // set up decrypt buffers with existing data buffers[0].pvBuffer = ssl_buffer_.data(); buffers[0].cbBuffer = static_cast(ssl_buffer_.size()); buffers[0].BufferType = SECBUFFER_DATA; buffers[1].BufferType = SECBUFFER_EMPTY; buffers[2].BufferType = SECBUFFER_EMPTY; buffers[3].BufferType = SECBUFFER_EMPTY; message.ulVersion = SECBUFFER_VERSION; message.cBuffers = 4; message.pBuffers = buffers; SECURITY_STATUS status = DecryptMessage(reinterpret_cast(ssl_context_handle_), &message, 0, nullptr); if (status == SEC_E_OK) { // find decrypted data buffer for (int i = 0; i < 4; i++) { if (buffers[i].BufferType == SECBUFFER_DATA && buffers[i].cbBuffer > 0) { int decrypted_size = (std::min)(static_cast(buffers[i].cbBuffer), bufferSize); memcpy(buffer, buffers[i].pvBuffer, decrypted_size); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Successfully decrypted %d bytes from existing buffer", decrypted_size); #endif // remove processed data from buffer and keep extra data if (buffers[3].BufferType == SECBUFFER_EXTRA && buffers[3].cbBuffer > 0) { memmove(ssl_buffer_.data(), (char*)buffers[3].pvBuffer, buffers[3].cbBuffer); ssl_buffer_.resize(buffers[3].cbBuffer); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Kept %d bytes of extra data in buffer", buffers[3].cbBuffer); #endif } else { ssl_buffer_.clear(); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Cleared SSL buffer after successful decryption"); #endif } return decrypted_size; } } } else if (status != SEC_E_INCOMPLETE_MESSAGE) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: SSL decrypt failed with status 0x%x, clearing buffer", status); #endif ssl_buffer_.clear(); return -1; } #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Incomplete SSL message, need more data (current buffer: %d bytes)", (int)ssl_buffer_.size()); #endif } #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Reading more SSL data from socket %d with proper polling", ssl_socket_); #endif fd_set readfds; struct timeval timeout; FD_ZERO(&readfds); FD_SET(ssl_socket_, &readfds); timeout.tv_sec = 5; timeout.tv_usec = 0; #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Waiting for WebSocket handshake response on socket %d with 5s timeout", ssl_socket_); #endif int select_result = select(0, &readfds, nullptr, nullptr, &timeout); if (select_result == SOCKET_ERROR) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: select() failed: %d", WSAGetLastError()); #endif return -1; } else if (select_result == 0) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Timeout waiting for WebSocket handshake response (5s) - server may not support WebSocket protocol"); kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: This could indicate: 1) Server doesn't support WebSocket, 2) Network issue, 3) Server overloaded"); #endif return -1; } else if (!FD_ISSET(ssl_socket_, &readfds)) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Socket not ready for SSL reading after select"); #endif return -1; } #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL data available, reading from socket %d", ssl_socket_); #endif char temp_buffer[SocketOptimization::SMALL_BUFFER_SIZE]; int raw_bytes = recv(ssl_socket_, temp_buffer, sizeof(temp_buffer), 0); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: recv returned %d bytes of raw SSL data", raw_bytes); #endif if (raw_bytes <= 0) { if (raw_bytes == 0) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: SSL connection closed by server during handshake"); #endif } else { int recv_error = WSAGetLastError(); if (recv_error == WSAEWOULDBLOCK) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: No SSL data available right now (WSAEWOULDBLOCK) - normal for non-blocking sockets"); #endif return 0; } else { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: recv failed with SSL error %d", recv_error); #endif } } return -1; } #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Successfully received %d bytes of raw SSL data, adding to buffer (current size: %d)", raw_bytes, (int)ssl_buffer_.size()); #endif // add new data to SSL buffer size_t old_size = ssl_buffer_.size(); ssl_buffer_.insert(ssl_buffer_.end(), temp_buffer, temp_buffer + raw_bytes); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL buffer updated: %d -> %d bytes", (int)old_size, (int)ssl_buffer_.size()); #endif // try to decrypt the accumulated data and set up decrypt buffers with all accumulated data SecBuffer buffers[4]; SecBufferDesc message; buffers[0].pvBuffer = ssl_buffer_.data(); buffers[0].cbBuffer = static_cast(ssl_buffer_.size()); buffers[0].BufferType = SECBUFFER_DATA; buffers[1].BufferType = SECBUFFER_EMPTY; buffers[2].BufferType = SECBUFFER_EMPTY; buffers[3].BufferType = SECBUFFER_EMPTY; message.ulVersion = SECBUFFER_VERSION; message.cBuffers = 4; message.pBuffers = buffers; #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Attempting SSL decryption of %d bytes", (int)ssl_buffer_.size()); #endif SECURITY_STATUS status = DecryptMessage(reinterpret_cast(ssl_context_handle_), &message, 0, nullptr); if (status == SEC_E_OK) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL decryption successful"); #endif for (int i = 0; i < 4; i++) { if (buffers[i].BufferType == SECBUFFER_DATA && buffers[i].cbBuffer > 0) { int decrypted_size = (std::min)(static_cast(buffers[i].cbBuffer), bufferSize); memcpy(buffer, buffers[i].pvBuffer, decrypted_size); if (decrypted_size < bufferSize) { buffer[decrypted_size] = '\0'; } #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Successfully decrypted %d bytes of SSL handshake response", decrypted_size); #endif #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Handshake response preview: %.200s...", buffer); #endif if (buffers[3].BufferType == SECBUFFER_EXTRA && buffers[3].cbBuffer > 0) { memmove(ssl_buffer_.data(), (char*)buffers[3].pvBuffer, buffers[3].cbBuffer); ssl_buffer_.resize(buffers[3].cbBuffer); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Kept %d bytes of extra SSL data", buffers[3].cbBuffer); #endif } else { ssl_buffer_.clear(); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Cleared SSL buffer after successful handshake decryption"); #endif } return decrypted_size; } } #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL decrypt succeeded but no handshake data found"); #endif return 0; } else if (status == SEC_E_INCOMPLETE_MESSAGE) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Incomplete SSL message, need more handshake data (buffer size: %d)", (int)ssl_buffer_.size()); #endif return 0; // need more data so we keep buffer intact and try again } else { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: SSL decrypt failed with handshake status 0x%x, clearing buffer", status); #endif ssl_buffer_.clear(); return -1; } } bool WebSocketClient::connectToServer(const std::string& host, int port, const std::string& path) { try { int sock = static_cast(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); if (sock == -1) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to create socket"); #endif return false; } sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_port = htons(port); if (inet_pton(AF_INET, host.c_str(), &addr.sin_addr) != 1) { // try DNS resolution struct addrinfo hints = {}, *result; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; if (getaddrinfo(host.c_str(), nullptr, &hints, &result) != 0) { #ifdef _WIN32 closesocket(sock); #else close(sock); #endif #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to resolve host %s", host.c_str()); #endif return false; } addr.sin_addr = ((sockaddr_in*)result->ai_addr)->sin_addr; freeaddrinfo(result); } if (connect(sock, (sockaddr*)&addr, sizeof(addr)) == -1) { #ifdef _WIN32 closesocket(sock); #else close(sock); #endif #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to connect to %s:%d", host.c_str(), port); #endif return false; } #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: TCP connected to %s:%d", host.c_str(), port); #endif ws_ = reinterpret_cast(static_cast(sock)); if (is_ssl_ && port == 443) { if (!initializeWebSocketSSL(sock, host)) { #ifdef _WIN32 closesocket(sock); #else close(sock); #endif ws_ = nullptr; return false; } #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL/TLS handshake completed"); #endif } if (!performWebSocketHandshake(sock, host, port, path)) { if (is_ssl_) cleanupWebSocketSSL(); #ifdef _WIN32 closesocket(sock); #else close(sock); #endif ws_ = nullptr; return false; } #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Handshake completed successfully"); #endif ready_state_ = OPEN; fireOpenEvent(); return true; } catch (const std::exception& e) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Connection error: %s", e.what()); #endif return false; } } // RFC 6455 handshake implementation bool WebSocketClient::performWebSocketHandshake(int sock, const std::string& host, int port, const std::string& path) { try { std::string webSocketKey = generateWebSocketKey(); std::string hostHeader = host; // dont include port for standard ports (80 for HTTP, 443 for HTTPS) if (!((is_ssl_ && port == 443) || (!is_ssl_ && port == 80))) { hostHeader += ":" + std::to_string(port); } std::string handshakeRequest = "GET " + path + " HTTP/1.1\r\n" "Host: " + hostHeader + "\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Key: " + webSocketKey + "\r\n" "Sec-WebSocket-Version: 13\r\n" "Origin: https://" + host + "\r\n" "User-Agent: RunT/1.0\r\n" "\r\n"; #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Sending handshake request"); #endif #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Handshake request content:\n%s", handshakeRequest.c_str()); #endif int sendResult; if (is_ssl_ && ssl_context_initialized_) { sendResult = webSocketSSLSend(handshakeRequest.c_str(), static_cast(handshakeRequest.length())); } else { sendResult = ::send(sock, handshakeRequest.c_str(), static_cast(handshakeRequest.length()), 0); } if (sendResult <= 0) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to send handshake request"); #endif return false; } char buffer[SocketOptimization::SMALL_BUFFER_SIZE]; int bytesReceived; if (is_ssl_ && ssl_context_initialized_) { bytesReceived = webSocketSSLReceive(buffer, sizeof(buffer) - 1); } else { bytesReceived = recv(sock, buffer, sizeof(buffer) - 1, 0); } if (bytesReceived <= 0) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to receive handshake response"); #endif return false; } buffer[bytesReceived] = '\0'; std::string response(buffer); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Received handshake response"); #endif if (response.find("HTTP/1.1 101") != 0) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Invalid handshake response status"); #endif return false; } // Sec-WebSocket-Accept case insensitive per RFC 7230 std::string expectedAccept = base64Encode(sha1Hash(webSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")); // convert response to lowercase for case insensitive header search std::string responseLower = response; std::transform(responseLower.begin(), responseLower.end(), responseLower.begin(), ::tolower); size_t acceptPos = responseLower.find("sec-websocket-accept: "); if (acceptPos == std::string::npos) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Missing Sec-WebSocket-Accept header"); #endif return false; } acceptPos += 22; // is the length of "sec-websocket-accept: " size_t acceptEnd = response.find("\r\n", acceptPos); std::string actualAccept = response.substr(acceptPos, acceptEnd - acceptPos); if (actualAccept != expectedAccept) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Invalid Sec-WebSocket-Accept value"); #endif return false; } return true; } catch (const std::exception& e) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Handshake error: %s", e.what()); #endif return false; } } // use event queue for thread safety void WebSocketClient::fireOpenEvent() { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Connection opened"); #endif event_queue_.push(WebSocketEvent(EVENT_OPEN)); } void WebSocketClient::fireMessageEvent(const std::string& message, bool binary) { #ifdef DEBUG_NETWORK // TODO: only log first 100 chars to avoid stack overflow in kinc_log for large messages if (message.length() > 100) { kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Message received (%zu bytes): %.100s...", message.length(), message.c_str()); } else { kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Message received: %s", message.c_str()); } #endif event_queue_.push(WebSocketEvent(EVENT_MESSAGE, message)); } void WebSocketClient::fireErrorEvent(const std::string& error) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Error: %s", error.c_str()); #endif event_queue_.push(WebSocketEvent(EVENT_ERROR, error)); } void WebSocketClient::fireCloseEvent(int code, const std::string& reason) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Connection closed: %d - %s", code, reason.c_str()); #endif ready_state_ = CLOSED; event_queue_.push(WebSocketEvent(EVENT_CLOSE, code, reason)); } // complete frame helper for ring buffer messageLoop void WebSocketClient::processFrame(uint8_t opcode, bool fin, const std::vector& payload) { switch (opcode) { case 0x0: // continuation frame case 0x1: // text frame case 0x2: // binary frame if (fin) { std::string message(payload.begin(), payload.end()); // force binary because we are fancy fireMessageEvent(message, true); } else { partial_frame_buffer_ = payload; expecting_continuation_ = true; continuation_opcode_ = opcode; } break; case 0x8: // close frame { uint16_t close_code = 1000; std::string close_reason; if (payload.size() >= 2) { close_code = (payload[0] << 8) | payload[1]; if (payload.size() > 2) { close_reason = std::string(payload.begin() + 2, payload.end()); } } fireCloseEvent(close_code, close_reason); } break; case 0x9: // ping frame { std::vector pongFrame = createPongFrame(payload); int sock = static_cast(reinterpret_cast(ws_)); if (is_ssl_ && ssl_context_initialized_) { webSocketSSLSend(pongFrame.data(), static_cast(pongFrame.size())); } else { ::send(sock, reinterpret_cast(pongFrame.data()), static_cast(pongFrame.size()), 0); } } break; case 0xA: // pong frame break; default: kinc_log(KINC_LOG_LEVEL_WARNING, "WebSocket: Unknown opcode 0x%X", opcode); break; } } // following uWebSockets void WebSocketClient::messageLoop() { if (ws_ == nullptr) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: No socket available for message loop"); #endif return; } int sock = static_cast(reinterpret_cast(ws_)); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Starting message loop"); #endif // 512KB heap allocated shared receive const size_t LIBUS_RECV_BUFFER_LENGTH = 524288; RingBuffer ringBuf(LIBUS_RECV_BUFFER_LENGTH); // fragmentBuffer for accumulating message fragments grows dynamically on heap when message spans multiple recv() calls std::string fragmentBuffer; // state machine bool wantsHead = true; // true = expecting frame header, false = continuation uint64_t remainingBytes = 0; // bytes remaining for current message payload uint8_t currentOpcode = 0; // opcode of current message being assembled bool currentFin = false; // FIN flag of current frame while (ready_state_ == OPEN) { size_t contiguousSpace; uint8_t* writePtr = ringBuf.getWritePtr(&contiguousSpace); if (contiguousSpace == 0) { if (!wantsHead && ringBuf.size() > 0) { size_t available = ringBuf.size(); size_t toRead = (available < remainingBytes) ? available : (size_t)remainingBytes; std::vector chunk(toRead); ringBuf.read(chunk.data(), toRead); fragmentBuffer.append(reinterpret_cast(chunk.data()), toRead); remainingBytes -= toRead; if (remainingBytes == 0 && currentFin) { std::vector payload(fragmentBuffer.begin(), fragmentBuffer.end()); processFrame(currentOpcode, currentFin, payload); fragmentBuffer.clear(); wantsHead = true; } continue; } #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_WARNING, "WebSocket: Buffer full (%zu bytes)", LIBUS_RECV_BUFFER_LENGTH); #endif break; } int bytesReceived; if (is_ssl_ && ssl_context_initialized_) { bytesReceived = webSocketSSLReceive(reinterpret_cast(writePtr), static_cast(contiguousSpace)); } else { bytesReceived = recv(sock, reinterpret_cast(writePtr), static_cast(contiguousSpace), 0); } if (bytesReceived <= 0) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Connection closed by server"); #endif break; } ringBuf.commitWrite(bytesReceived); if (!wantsHead) { size_t available = ringBuf.size(); size_t toRead = (available < remainingBytes) ? available : (size_t)remainingBytes; std::vector chunk(toRead); ringBuf.read(chunk.data(), toRead); fragmentBuffer.append(reinterpret_cast(chunk.data()), toRead); remainingBytes -= toRead; if (remainingBytes == 0 && currentFin) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Large message complete (%zu bytes)", fragmentBuffer.size()); std::vector payload(fragmentBuffer.begin(), fragmentBuffer.end()); #endif processFrame(currentOpcode, currentFin, payload); fragmentBuffer.clear(); wantsHead = true; } continue; } while (ringBuf.size() >= 2) { WsFrameInfo frameInfo = {}; // Zero-initialize for safe partial-parse checking WsFrameResult result = parseWsFrameHeader(ringBuf, frameInfo); if (result == WsFrameResult::WS_INCOMPLETE) { // can mean many things, we follow uWebsockets if (frameInfo.header_size > 0 && ringBuf.size() >= frameInfo.header_size && frameInfo.payload_length > LIBUS_RECV_BUFFER_LENGTH / 2) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Large payload (%llu bytes), using fragmentBuffer", (unsigned long long)frameInfo.payload_length); #endif fragmentBuffer.reserve((size_t)frameInfo.payload_length); currentOpcode = frameInfo.opcode; currentFin = frameInfo.fin; remainingBytes = frameInfo.payload_length; wantsHead = false; ringBuf.consume(frameInfo.header_size); break; } break; } if (result == WsFrameResult::WS_ERROR) { // so we resync and skip to next valid frame header ringBuf.consume(1); continue; } if (!frameInfo.fin || fragmentBuffer.length() > 0) { // first fragment reserves, subsequent appends if (fragmentBuffer.length() == 0) { fragmentBuffer.reserve((size_t)frameInfo.payload_length); currentOpcode = frameInfo.opcode ? frameInfo.opcode : currentOpcode; } std::vector payload((size_t)frameInfo.payload_length); extractWsPayload(ringBuf, frameInfo, payload.data()); fragmentBuffer.append(reinterpret_cast(payload.data()), payload.size()); if (frameInfo.fin) { std::vector fullPayload(fragmentBuffer.begin(), fragmentBuffer.end()); processFrame(currentOpcode, true, fullPayload); fragmentBuffer.clear(); } } else { std::vector payload((size_t)frameInfo.payload_length); extractWsPayload(ringBuf, frameInfo, payload.data()); processFrame(frameInfo.opcode, frameInfo.fin, payload); } } } #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Message loop completed"); #endif } // frame parsing (RFC 6455) bool WebSocketClient::parseWebSocketFrame(const std::vector& buffer, size_t& offset) { size_t original_offset = offset; if (offset + 2 > buffer.size()) { return false; } uint8_t byte1 = buffer[offset++]; uint8_t byte2 = buffer[offset++]; bool fin = (byte1 & 0x80) != 0; uint8_t rsv = (byte1 >> 4) & 0x07; // RSV1, RSV2, RSV3 uint8_t opcode = byte1 & 0x0F; bool masked = (byte2 & 0x80) != 0; uint64_t payload_length = byte2 & 0x7F; // RFC 6455: RSV bits must be 0 unless an extension is negotiated if (rsv != 0) { #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_WARNING, "WebSocket: Non-zero RSV bits (%d) - possible frame corruption", rsv); #endif offset = original_offset + 1; return true; } if (payload_length == 126) { if (offset + 2 > buffer.size()) { offset = original_offset; return false; } payload_length = (buffer[offset] << 8) | buffer[offset + 1]; offset += 2; } else if (payload_length == 127) { if (offset + 8 > buffer.size()) { offset = original_offset; return false; } payload_length = 0; for (int i = 0; i < 8; i++) { payload_length = (payload_length << 8) | buffer[offset + i]; } offset += 8; } // TODO: confirm we shouldnt send masked frames uint32_t mask_key = 0; if (masked) { if (offset + 4 > buffer.size()) { offset = original_offset; return false; } mask_key = (buffer[offset] << 24) | (buffer[offset + 1] << 16) | (buffer[offset + 2] << 8) | buffer[offset + 3]; offset += 4; } if (offset + payload_length > buffer.size()) { offset = original_offset; return false; } std::vector payload(buffer.begin() + offset, buffer.begin() + offset + payload_length); offset += payload_length; if (masked) { maskData(payload, mask_key); } switch (opcode) { case 0x0: // continuation frame case 0x1: // text frame case 0x2: // binary frame if (fin) { std::string message(payload.begin(), payload.end()); fireMessageEvent(message, true); // force binary mode } else { partial_frame_buffer_ = payload; expecting_continuation_ = true; continuation_opcode_ = opcode; } break; case 0x8: { uint16_t close_code = 1000; std::string close_reason; if (payload.size() >= 2) { close_code = (payload[0] << 8) | payload[1]; if (payload.size() > 2) { close_reason = std::string(payload.begin() + 2, payload.end()); } } fireCloseEvent(close_code, close_reason); return false; } break; case 0x9: { std::vector pongFrame = createPongFrame(payload); int sock = static_cast(reinterpret_cast(ws_)); int sendResult; if (is_ssl_ && ssl_context_initialized_) { sendResult = webSocketSSLSend(pongFrame.data(), static_cast(pongFrame.size())); } else { sendResult = ::send(sock, reinterpret_cast(pongFrame.data()), static_cast(pongFrame.size()), 0); } #ifdef DEBUG_NETWORK if (sendResult > 0) { kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Sent pong response"); } #endif } break; case 0xA: #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Received pong frame"); #endif break; case 0x3: case 0x4: case 0x5: case 0x6: case 0x7: // non-control frames case 0xB: case 0xC: case 0xD: case 0xE: case 0xF: // control frames // RFC 6455: reserved opcodes we skip the frame but continue processing #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_WARNING, "WebSocket: Received reserved opcode %d, skipping frame", opcode); #endif break; default: #ifdef DEBUG_NETWORK // invalid opcode indicates frame misalignment so we try to recover kinc_log(KINC_LOG_LEVEL_WARNING, "WebSocket: Invalid opcode %d - possible frame misalignment", opcode); // we dont return false we allow message loop to continue with fresh data #endif break; } return true; } // RFC 6455 frame creation std::vector WebSocketClient::createWebSocketFrame(const std::string& message, uint8_t opcode) { std::vector frame; // first byte: FIN (1) + RSV (000) + Opcode (4 bits) frame.push_back(0x80 | opcode); uint64_t payload_length = message.length(); uint32_t mask_key = rand() | (rand() << 16); if (payload_length < 126) { frame.push_back(0x80 | static_cast(payload_length)); // MASK (1) + length } else if (payload_length < 65536) { frame.push_back(0x80 | 126); // MASK (1) + 126 frame.push_back(static_cast(payload_length >> 8)); frame.push_back(static_cast(payload_length & 0xFF)); } else { frame.push_back(0x80 | 127); // MASK (1) + 127 for (int i = 7; i >= 0; i--) { frame.push_back(static_cast((payload_length >> (i * 8)) & 0xFF)); } } frame.push_back(static_cast(mask_key >> 24)); frame.push_back(static_cast(mask_key >> 16)); frame.push_back(static_cast(mask_key >> 8)); frame.push_back(static_cast(mask_key)); std::vector payload(message.begin(), message.end()); maskData(payload, mask_key); frame.insert(frame.end(), payload.begin(), payload.end()); return frame; } std::vector WebSocketClient::createWebSocketBinaryFrame(const std::string& data) { return createWebSocketFrame(data, 0x02); } std::vector WebSocketClient::createCloseFrame(uint16_t code, const std::string& reason) { std::vector payload; payload.push_back(static_cast(code >> 8)); payload.push_back(static_cast(code & 0xFF)); payload.insert(payload.end(), reason.begin(), reason.end()); return createWebSocketFrame(std::string(payload.begin(), payload.end()), 0x8); } std::vector WebSocketClient::createPongFrame(const std::vector& payload) { std::string payloadStr(payload.begin(), payload.end()); return createWebSocketFrame(payloadStr, 0xA); } std::string WebSocketClient::generateWebSocketKey() { std::vector key(16); for (int i = 0; i < 16; i++) { key[i] = rand() & 0xFF; } return base64Encode(key); } std::string WebSocketClient::base64Encode(const std::vector& data) { static const char* chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; std::string result; int pad = data.size() % 3; for (size_t i = 0; i < data.size(); i += 3) { uint32_t tmp = (data[i] << 16); if (i + 1 < data.size()) tmp |= (data[i + 1] << 8); if (i + 2 < data.size()) tmp |= data[i + 2]; result += chars[(tmp >> 18) & 0x3F]; result += chars[(tmp >> 12) & 0x3F]; result += (i + 1 < data.size()) ? chars[(tmp >> 6) & 0x3F] : '='; result += (i + 2 < data.size()) ? chars[tmp & 0x3F] : '='; } return result; } // SHA1 hash using Windows CryptoAPI std::vector WebSocketClient::sha1Hash(const std::string& data) { #ifdef _WIN32 HCRYPTPROV hProv = 0; HCRYPTHASH hHash = 0; std::vector result(20, 0); DWORD hashLen = 20; try { if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: CryptAcquireContext failed"); return result; } if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) { kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: CryptCreateHash failed"); CryptReleaseContext(hProv, 0); return result; } if (!CryptHashData(hHash, reinterpret_cast(data.c_str()), static_cast(data.length()), 0)) { kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: CryptHashData failed"); CryptDestroyHash(hHash); CryptReleaseContext(hProv, 0); return result; } if (!CryptGetHashParam(hHash, HP_HASHVAL, result.data(), &hashLen, 0)) { kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: CryptGetHashParam failed"); CryptDestroyHash(hHash); CryptReleaseContext(hProv, 0); return result; } CryptDestroyHash(hHash); CryptReleaseContext(hProv, 0); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SHA1 hash computed successfully"); #endif return result; } catch (const std::exception& e) { kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: SHA1 hash error: %s", e.what()); if (hHash) CryptDestroyHash(hHash); if (hProv) CryptReleaseContext(hProv, 0); return result; } #else // TODO: SHA1 kinc_log(KINC_LOG_LEVEL_WARNING, "WebSocket: SHA1 not implemented for this platform"); return std::vector(20, 0); #endif } // masking with SIMD optimization void WebSocketClient::maskData(std::vector& data, uint32_t maskKey) { uint8_t mask[4] = { static_cast(maskKey >> 24), static_cast(maskKey >> 16), static_cast(maskKey >> 8), static_cast(maskKey) }; size_t len = data.size(); uint8_t* ptr = data.data(); #ifdef _WIN32 if (len >= 16) { // byte rotation: mask[0,1,2,3,0,1,2,3,...] __m128i mask_vec = _mm_set_epi8( mask[3], mask[2], mask[1], mask[0], // bytes 12-15 mask[3], mask[2], mask[1], mask[0], // bytes 8-11 mask[3], mask[2], mask[1], mask[0], // bytes 4-7 mask[3], mask[2], mask[1], mask[0] // bytes 0-3 ); size_t simd_len = len & ~15; for (size_t i = 0; i < simd_len; i += 16) { __m128i data_vec = _mm_loadu_si128((__m128i*)(ptr + i)); __m128i result = _mm_xor_si128(data_vec, mask_vec); _mm_storeu_si128((__m128i*)(ptr + i), result); } for (size_t i = simd_len; i < len; i++) { ptr[i] ^= mask[i % 4]; } } else #endif { // fallback to scalar implementation for (size_t i = 0; i < len; i++) { ptr[i] ^= mask[i % 4]; } } } bool WebSocketClient::initializeWebSocketSSL(int sock, const std::string& host) { #ifdef WITH_SSL #ifdef _WIN32 if (ssl_context_initialized_) { return true; } ssl_socket_ = sock; if (!initWSL()) { kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to initialize SSL context"); return false; } if (!performWSLHandshake(sock, host)) { kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: SSL handshake failed"); cleanupWSL(); return false; } ssl_context_initialized_ = true; #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL/TLS initialized successfully"); #endif return true; #else if (ssl_initialized_) { return true; } // TODO: OpenSSL kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: SSL not supported on this platform"); return false; #endif #else kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: SSL not compiled in"); return false; #endif } void WebSocketClient::cleanupWebSocketSSL() { #ifdef WITH_SSL #ifdef _WIN32 if (ssl_context_initialized_) { cleanupWSL(); ssl_context_initialized_ = false; ssl_socket_ = -1; kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL/TLS cleaned up"); } #else if (ssl_initialized_) { // TODO: OpenSSL cleanup ssl_initialized_ = false; kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL/TLS cleaned up"); } #endif #endif } int WebSocketClient::webSocketSSLSend(const void* data, int len) { #ifdef WITH_SSL #ifdef _WIN32 if (!ssl_context_initialized_) { return ::send(ssl_socket_, reinterpret_cast(data), len, 0); } return webSocketSSLSend(reinterpret_cast(data), len); #else return ::send(ssl_socket_, reinterpret_cast(data), len, 0); #endif #else return ::send(ssl_socket_, reinterpret_cast(data), len, 0); #endif } #ifdef WITH_SSL bool WebSocketClient::initWSL() { #ifdef _WIN32 if (ssl_context_initialized_) { return true; } ssl_cred_handle_ = new CredHandle(); memset(ssl_cred_handle_, 0, sizeof(CredHandle)); SCHANNEL_CRED cred = {0}; cred.dwVersion = SCHANNEL_CRED_VERSION; cred.grbitEnabledProtocols = SP_PROT_TLS1_2 | SP_PROT_TLS1_1; cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION | SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT; cred.dwMinimumCipherStrength = 0; cred.dwMaximumCipherStrength = 0; SECURITY_STATUS status = AcquireCredentialsHandleA( NULL, const_cast(UNISP_NAME_A), SECPKG_CRED_OUTBOUND, NULL, &cred, NULL, NULL, reinterpret_cast(ssl_cred_handle_), NULL); if (status != SEC_E_OK) { const char* cred_error_desc = "Unknown credential error"; switch (status) { case SEC_E_SECPKG_NOT_FOUND: cred_error_desc = "SEC_E_SECPKG_NOT_FOUND - Security package not found"; break; case SEC_E_NOT_OWNER: cred_error_desc = "SEC_E_NOT_OWNER - Not owner"; break; case SEC_E_CANNOT_INSTALL: cred_error_desc = "SEC_E_CANNOT_INSTALL - Cannot install"; break; case SEC_E_INVALID_TOKEN: cred_error_desc = "SEC_E_INVALID_TOKEN - Invalid token"; break; case SEC_E_LOGON_DENIED: cred_error_desc = "SEC_E_LOGON_DENIED - Logon denied"; break; } kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to acquire WebSocket SSL credentials: 0x%x (%s)", status, cred_error_desc); if (ssl_cred_handle_) { delete reinterpret_cast(ssl_cred_handle_); ssl_cred_handle_ = nullptr; } return false; } kinc_log(KINC_LOG_LEVEL_INFO, "Successfully acquired WebSocket SSL credentials"); ssl_context_initialized_ = true; return true; #else if (ssl_initialized_) { return true; } SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); ssl_ctx_ = SSL_CTX_new(TLS_client_method()); if (!ssl_ctx_) { kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to create WebSocket SSL context"); return false; } SSL_CTX_set_options(ssl_ctx_, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); SSL_CTX_set_verify(ssl_ctx_, SSL_VERIFY_PEER, NULL); SSL_CTX_set_default_verify_paths(ssl_ctx_); ssl_initialized_ = true; return true; #endif } void WebSocketClient::cleanupWSL() { #ifdef _WIN32 if (ssl_context_initialized_) { if (ssl_context_handle_) { DeleteSecurityContext(reinterpret_cast(ssl_context_handle_)); delete reinterpret_cast(ssl_context_handle_); ssl_context_handle_ = nullptr; } if (ssl_cred_handle_) { FreeCredentialsHandle(reinterpret_cast(ssl_cred_handle_)); delete reinterpret_cast(ssl_cred_handle_); ssl_cred_handle_ = nullptr; } ssl_context_initialized_ = false; } ssl_buffer_.clear(); ssl_socket_ = -1; #else if (ssl_) { SSL_free(ssl_); ssl_ = nullptr; } if (ssl_ctx_) { SSL_CTX_free(ssl_ctx_); ssl_ctx_ = nullptr; } ssl_initialized_ = false; #endif } bool WebSocketClient::performWSLHandshake(int socket, const std::string& host) { if (!initWSL()) { return false; } ssl_socket_ = socket; #ifdef _WIN32 kinc_log(KINC_LOG_LEVEL_INFO, "Performing SChannel WebSocket SSL handshake..."); SecBufferDesc outbuffer_desc, inbuffer_desc; SecBuffer outbuffers[1], inbuffers[2]; DWORD context_attributes; TimeStamp expiry; outbuffers[0].pvBuffer = nullptr; outbuffers[0].BufferType = SECBUFFER_TOKEN; outbuffers[0].cbBuffer = 0; outbuffer_desc.cBuffers = 1; outbuffer_desc.pBuffers = outbuffers; outbuffer_desc.ulVersion = SECBUFFER_VERSION; inbuffers[0].pvBuffer = nullptr; inbuffers[0].BufferType = SECBUFFER_TOKEN; inbuffers[0].cbBuffer = 0; inbuffers[1].pvBuffer = nullptr; inbuffers[1].BufferType = SECBUFFER_EMPTY; inbuffers[1].cbBuffer = 0; inbuffer_desc.cBuffers = 2; inbuffer_desc.pBuffers = inbuffers; inbuffer_desc.ulVersion = SECBUFFER_VERSION; SECURITY_STATUS status = SEC_I_CONTINUE_NEEDED; bool first_call = true; std::vector handshake_buffer; while (status == SEC_I_CONTINUE_NEEDED || status == SEC_E_INCOMPLETE_MESSAGE) { if (first_call) { // the initial call / first call should use NULL for phContext and allocate CtxtHandle structure for the output ssl_context_handle_ = new CtxtHandle(); memset(ssl_context_handle_, 0, sizeof(CtxtHandle)); kinc_log(KINC_LOG_LEVEL_INFO, "Calling InitializeSecurityContextA for WebSocket first handshake..."); kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket buffer before call: cbBuffer=%d, pvBuffer=%p", outbuffers[0].cbBuffer, outbuffers[0].pvBuffer); status = InitializeSecurityContextA( reinterpret_cast(ssl_cred_handle_), nullptr, const_cast(host.c_str()), ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_REQ_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM, 0, SECURITY_NATIVE_DREP, nullptr, 0, reinterpret_cast(ssl_context_handle_), &outbuffer_desc, &context_attributes, &expiry); kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket InitializeSecurityContextA result: status=0x%x", status); kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket buffer after call: cbBuffer=%d, pvBuffer=%p", outbuffers[0].cbBuffer, outbuffers[0].pvBuffer); if (status != SEC_I_CONTINUE_NEEDED && status != SEC_E_OK) { const char* init_error_desc = "Unknown initialization error"; switch (status) { case SEC_E_INVALID_HANDLE: init_error_desc = "SEC_E_INVALID_HANDLE - Invalid handle"; break; case SEC_E_INVALID_TOKEN: init_error_desc = "SEC_E_INVALID_TOKEN - Invalid token"; break; case SEC_E_LOGON_DENIED: init_error_desc = "SEC_E_LOGON_DENIED - Logon denied"; break; case SEC_E_TARGET_UNKNOWN: init_error_desc = "SEC_E_TARGET_UNKNOWN - Target unknown"; break; case SEC_E_INTERNAL_ERROR: init_error_desc = "SEC_E_INTERNAL_ERROR - Internal error"; break; case SEC_E_SECPKG_NOT_FOUND: init_error_desc = "SEC_E_SECPKG_NOT_FOUND - Security package not found"; break; } kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket InitializeSecurityContextA failed: 0x%x (%s)", status, init_error_desc); if (ssl_context_handle_) { delete reinterpret_cast(ssl_context_handle_); ssl_context_handle_ = nullptr; } return false; } first_call = false; } else { outbuffers[0].pvBuffer = nullptr; outbuffers[0].BufferType = SECBUFFER_TOKEN; outbuffers[0].cbBuffer = 0; if (!handshake_buffer.empty()) { inbuffers[0].pvBuffer = handshake_buffer.data(); inbuffers[0].cbBuffer = static_cast(handshake_buffer.size()); inbuffers[0].BufferType = SECBUFFER_TOKEN; inbuffers[1].pvBuffer = nullptr; inbuffers[1].BufferType = SECBUFFER_EMPTY; inbuffers[1].cbBuffer = 0; } kinc_log(KINC_LOG_LEVEL_INFO, "Calling InitializeSecurityContextA for WebSocket continuation handshake..."); kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket input buffer: cbBuffer=%d, pvBuffer=%p", inbuffers[0].cbBuffer, inbuffers[0].pvBuffer); kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket context handle: %p", ssl_context_handle_); status = InitializeSecurityContextA( reinterpret_cast(ssl_cred_handle_), reinterpret_cast(ssl_context_handle_), const_cast(host.c_str()), ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_REQ_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM, 0, SECURITY_NATIVE_DREP, &inbuffer_desc, 0, reinterpret_cast(ssl_context_handle_), &outbuffer_desc, &context_attributes, &expiry); kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket continuation InitializeSecurityContextA result: status=0x%x", status); kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket output buffer after continuation: cbBuffer=%d, pvBuffer=%p", outbuffers[0].cbBuffer, outbuffers[0].pvBuffer); // handle for some data consumed, check for extra data in the buffer and move to front if (status == SEC_E_OK || status == SEC_I_CONTINUE_NEEDED) { // if (inbuffers[1].BufferType == SECBUFFER_EXTRA && inbuffers[1].cbBuffer > 0) { size_t extra_size = inbuffers[1].cbBuffer; size_t consumed = handshake_buffer.size() - extra_size; std::memmove(handshake_buffer.data(), handshake_buffer.data() + consumed, extra_size); handshake_buffer.resize(extra_size); } else { handshake_buffer.clear(); } } } if (status == SEC_I_CONTINUE_NEEDED && outbuffers[0].cbBuffer > 0 && outbuffers[0].pvBuffer != nullptr) { // validate buffer before sending if (outbuffers[0].cbBuffer < 0 || outbuffers[0].cbBuffer > 65536) { kinc_log(KINC_LOG_LEVEL_ERROR, "Invalid WebSocket SSL handshake buffer size: %d bytes (corrupted data)", outbuffers[0].cbBuffer); if (outbuffers[0].pvBuffer) FreeContextBuffer(outbuffers[0].pvBuffer); return false; } kinc_log(KINC_LOG_LEVEL_INFO, "Sending WebSocket SSL handshake data: %d bytes", outbuffers[0].cbBuffer); int sent = ::send(socket, static_cast(outbuffers[0].pvBuffer), outbuffers[0].cbBuffer, 0); if (sent <= 0) { int error = WSAGetLastError(); const char* error_desc = "Unknown socket error"; switch (error) { case WSAENOTCONN: error_desc = "WSAENOTCONN - Socket is not connected"; break; case WSAECONNRESET: error_desc = "WSAECONNRESET - Connection reset by peer"; break; case WSAEWOULDBLOCK: error_desc = "WSAEWOULDBLOCK - Resource temporarily unavailable"; break; case WSAENETDOWN: error_desc = "WSAENETDOWN - Network is down"; break; case WSAEFAULT: error_desc = "WSAEFAULT - Bad address"; break; case WSAEINVAL: error_desc = "WSAEINVAL - Invalid argument"; break; case WSAENOTSOCK: error_desc = "WSAENOTSOCK - Socket operation on non-socket"; break; } kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to send WebSocket SSL handshake data: sent=%d, error=%d (%s)", sent, error, error_desc); if (outbuffers[0].pvBuffer) FreeContextBuffer(outbuffers[0].pvBuffer); return false; } kinc_log(KINC_LOG_LEVEL_INFO, "Successfully sent WebSocket SSL handshake data: %d bytes", sent); FreeContextBuffer(outbuffers[0].pvBuffer); outbuffers[0].pvBuffer = nullptr; outbuffers[0].cbBuffer = 0; } if (status == SEC_I_CONTINUE_NEEDED || status == SEC_E_INCOMPLETE_MESSAGE) { char recv_buffer[SocketOptimization::SMALL_BUFFER_SIZE]; kinc_log(KINC_LOG_LEVEL_INFO, "Waiting to receive WebSocket SSL handshake response..."); int received = ::recv(socket, recv_buffer, sizeof(recv_buffer), 0); if (received <= 0) { int error = WSAGetLastError(); const char* error_desc = "Unknown socket error"; switch (error) { case WSAENOTCONN: error_desc = "WSAENOTCONN - Socket is not connected"; break; case WSAECONNRESET: error_desc = "WSAECONNRESET - Connection reset by peer"; break; case WSAEWOULDBLOCK: error_desc = "WSAEWOULDBLOCK - Resource temporarily unavailable"; break; case WSAENETDOWN: error_desc = "WSAENETDOWN - Network is down"; break; case WSAEFAULT: error_desc = "WSAEFAULT - Bad address"; break; case WSAEINVAL: error_desc = "WSAEINVAL - Invalid argument"; break; case WSAENOTSOCK: error_desc = "WSAENOTSOCK - Socket operation on non-socket"; break; case WSAECONNABORTED: error_desc = "WSAECONNABORTED - Connection aborted"; break; } kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to receive WebSocket SSL handshake response: received=%d, error=%d (%s)", received, error, error_desc); return false; } kinc_log(KINC_LOG_LEVEL_INFO, "Received WebSocket SSL handshake response: %d bytes", received); handshake_buffer.insert(handshake_buffer.end(), recv_buffer, recv_buffer + received); } } if (status == SEC_E_OK) { ssl_context_initialized_ = true; kinc_log(KINC_LOG_LEVEL_INFO, "SChannel WebSocket SSL handshake completed successfully"); return true; } else { const char* error_desc = "Unknown error"; switch (status) { case SEC_E_LOGON_DENIED: error_desc = "SEC_E_LOGON_DENIED - The logon attempt failed"; break; case SEC_E_INVALID_TOKEN: error_desc = "SEC_E_INVALID_TOKEN - Invalid security token"; break; case SEC_E_INVALID_HANDLE: error_desc = "SEC_E_INVALID_HANDLE - Invalid handle"; break; case SEC_E_INTERNAL_ERROR: error_desc = "SEC_E_INTERNAL_ERROR - Internal error"; break; case SEC_E_NO_CREDENTIALS: error_desc = "SEC_E_NO_CREDENTIALS - No credentials available"; break; case SEC_E_WRONG_PRINCIPAL: error_desc = "SEC_E_WRONG_PRINCIPAL - Wrong principal"; break; case SEC_E_CERT_EXPIRED: error_desc = "SEC_E_CERT_EXPIRED - Certificate expired"; break; case SEC_E_CERT_UNKNOWN: error_desc = "SEC_E_CERT_UNKNOWN - Certificate unknown"; break; case SEC_E_UNTRUSTED_ROOT: error_desc = "SEC_E_UNTRUSTED_ROOT - Untrusted root certificate"; break; } kinc_log(KINC_LOG_LEVEL_ERROR, "SChannel WebSocket SSL handshake failed with status: 0x%x (%s)", status, error_desc); return false; } #else ssl_ = SSL_new(ssl_ctx_); if (!ssl_) { kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to create WebSocket SSL structure"); return false; } if (SSL_set_fd(ssl_, socket) != 1) { kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to set WebSocket SSL socket"); SSL_free(ssl_); ssl_ = nullptr; return false; } SSL_set_tlsext_host_name(ssl_, host.c_str()); int result = SSL_connect(ssl_); if (result != 1) { int error = SSL_get_error(ssl_, result); kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket SSL handshake failed: %d", error); SSL_free(ssl_); ssl_ = nullptr; return false; } ssl_initialized_ = true; kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket OpenSSL handshake completed successfully"); return true; #endif } int WebSocketClient::wslRead(char* buffer, int length) { #ifdef _WIN32 if (!ssl_context_initialized_) { return -1; } while (true) { if (!ssl_buffer_.empty()) { SecBufferDesc message_desc; SecBuffer message_buffers[4]; message_desc.ulVersion = SECBUFFER_VERSION; message_desc.cBuffers = 4; message_desc.pBuffers = message_buffers; message_buffers[0].BufferType = SECBUFFER_DATA; message_buffers[0].pvBuffer = ssl_buffer_.data(); message_buffers[0].cbBuffer = static_cast(ssl_buffer_.size()); message_buffers[1].BufferType = SECBUFFER_EMPTY; message_buffers[1].pvBuffer = nullptr; message_buffers[1].cbBuffer = 0; message_buffers[2].BufferType = SECBUFFER_EMPTY; message_buffers[2].pvBuffer = nullptr; message_buffers[2].cbBuffer = 0; message_buffers[3].BufferType = SECBUFFER_EMPTY; message_buffers[3].pvBuffer = nullptr; message_buffers[3].cbBuffer = 0; SECURITY_STATUS status = DecryptMessage( reinterpret_cast(ssl_context_handle_), &message_desc, 0, nullptr); if (status == SEC_E_OK) { SecBuffer* data_buffer = nullptr; SecBuffer* extra_buffer = nullptr; for (int i = 0; i < 4; i++) { if (message_buffers[i].BufferType == SECBUFFER_DATA) { data_buffer = &message_buffers[i]; } else if (message_buffers[i].BufferType == SECBUFFER_EXTRA) { extra_buffer = &message_buffers[i]; } } if (data_buffer && data_buffer->cbBuffer > 0) { int bytes_to_copy = (std::min)(length, static_cast(data_buffer->cbBuffer)); memcpy(buffer, data_buffer->pvBuffer, bytes_to_copy); if (extra_buffer && extra_buffer->cbBuffer > 0) { std::memmove(ssl_buffer_.data(), extra_buffer->pvBuffer, extra_buffer->cbBuffer); ssl_buffer_.resize(extra_buffer->cbBuffer); } else { ssl_buffer_.clear(); } return bytes_to_copy; } ssl_buffer_.clear(); } else if (status == SEC_E_INCOMPLETE_MESSAGE) { // Need more encrypted data, read from socket kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL incomplete message detected (status=0x%x, SEC_E_INCOMPLETE_MESSAGE=0x%x), reading more data (buffer size: %zu)", status, SEC_E_INCOMPLETE_MESSAGE, ssl_buffer_.size()); char recv_buffer[SocketOptimization::SMALL_BUFFER_SIZE]; int received = ::recv(ssl_socket_, recv_buffer, sizeof(recv_buffer), 0); if (received <= 0) { if (received == 0) { kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL connection closed by server during read"); } else { kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to receive encrypted data: %d (WSAError: %d)", received, WSAGetLastError()); } return -1; } size_t old_size = ssl_buffer_.size(); ssl_buffer_.insert(ssl_buffer_.end(), recv_buffer, recv_buffer + received); kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Added %d bytes to SSL buffer (total: %zu -> %zu)", received, old_size, ssl_buffer_.size()); continue; } else if (status == 0x90317) { // incomplete message error kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Detected incomplete message error (0x90317), buffer size: %zu", ssl_buffer_.size()); // log the actual buffer content as hex to see what we received if (ssl_buffer_.size() > 0) { std::string hex_preview; for (size_t i = 0; i < (std::min)(static_cast(31), ssl_buffer_.size()); i++) { char hex_byte[4]; sprintf(hex_byte, "%02X ", (unsigned char)ssl_buffer_[i]); hex_preview += hex_byte; } kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL buffer hex (first %d bytes): %s", static_cast((std::min)(static_cast(31), ssl_buffer_.size())), hex_preview.c_str()); // check for ssl alert record type 0x15 if (ssl_buffer_.size() >= 5 && ssl_buffer_[0] == 0x15) { kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Detected SSL Alert record (0x15) - server rejected handshake"); if (ssl_buffer_.size() >= 7) { uint8_t alert_level = ssl_buffer_[5]; uint8_t alert_desc = ssl_buffer_[6]; kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL Alert - Level: %d, Description: %d", alert_level, alert_desc); } } else if (ssl_buffer_.size() >= 1) { kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL record type: 0x%02X", (unsigned char)ssl_buffer_[0]); } } // try a smaller read first to see if server has more data char recv_buffer[SocketOptimization::SMALL_BUFFER_SIZE / 8]; int received = ::recv(ssl_socket_, recv_buffer, sizeof(recv_buffer), 0); if (received <= 0) { if (received == 0) { kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL connection closed by server - may be complete response"); kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Attempting to process existing buffer as complete response"); return -1; } else { kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to receive encrypted data: %d (WSAError: %d)", received, WSAGetLastError()); } return -1; } size_t old_size = ssl_buffer_.size(); ssl_buffer_.insert(ssl_buffer_.end(), recv_buffer, recv_buffer + received); kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Added %d bytes to SSL buffer (total: %zu -> %zu)", received, old_size, ssl_buffer_.size()); continue; } else { const char* status_desc = "Unknown SSL error"; switch (status) { case SEC_E_INVALID_TOKEN: status_desc = "SEC_E_INVALID_TOKEN - Invalid token"; break; case SEC_E_INVALID_HANDLE: status_desc = "SEC_E_INVALID_HANDLE - Invalid handle"; break; case SEC_E_MESSAGE_ALTERED: status_desc = "SEC_E_MESSAGE_ALTERED - Message altered"; break; case SEC_E_OUT_OF_SEQUENCE: status_desc = "SEC_E_OUT_OF_SEQUENCE - Out of sequence"; break; case SEC_E_NO_AUTHENTICATING_AUTHORITY: status_desc = "SEC_E_NO_AUTHENTICATING_AUTHORITY - No auth authority"; break; case 0x90317: status_desc = "0x90317 - Incomplete message (should be handled above)"; break; } kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: SChannel decrypt failed: 0x%x (%s) - buffer size: %zu, SEC_E_INCOMPLETE_MESSAGE=0x%x", status, status_desc, ssl_buffer_.size(), SEC_E_INCOMPLETE_MESSAGE); return -1; } } char recv_buffer[SocketOptimization::SMALL_BUFFER_SIZE]; int received = ::recv(ssl_socket_, recv_buffer, sizeof(recv_buffer), 0); if (received <= 0) { if (received == 0) { return 0; } kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to receive encrypted data for WebSocket: %d", received); return -1; } ssl_buffer_.insert(ssl_buffer_.end(), recv_buffer, recv_buffer + received); // retry decryption } #else if (!ssl_) { return -1; } int bytes_read = SSL_read(ssl_, buffer, length); if (bytes_read <= 0) { int error = SSL_get_error(ssl_, bytes_read); if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) { return 0; } return -1; } return bytes_read; #endif } int WebSocketClient::webSocketSSLSend(const char* buffer, int length) { #ifdef WITH_SSL #ifdef _WIN32 kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: webSocketSSLSend(const char*) called, length=%d, ssl_initialized=%d", length, ssl_context_initialized_ ? 1 : 0); if (!ssl_context_initialized_) { kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL not initialized, using plain send"); return ::send(ssl_socket_, buffer, length, 0); } SecBufferDesc message_desc; SecBuffer message_buffers[4]; SecPkgContext_StreamSizes stream_sizes; SECURITY_STATUS status = QueryContextAttributesA( static_cast(ssl_context_handle_), SECPKG_ATTR_STREAM_SIZES, &stream_sizes); if (status != SEC_E_OK) { kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to get WebSocket stream sizes: 0x%x", status); return -1; } int total_size = stream_sizes.cbHeader + length + stream_sizes.cbTrailer; char* encrypt_buffer = new char[total_size]; message_desc.ulVersion = SECBUFFER_VERSION; message_desc.cBuffers = 4; message_desc.pBuffers = message_buffers; message_buffers[0].BufferType = SECBUFFER_STREAM_HEADER; message_buffers[0].pvBuffer = encrypt_buffer; message_buffers[0].cbBuffer = stream_sizes.cbHeader; message_buffers[1].BufferType = SECBUFFER_DATA; message_buffers[1].pvBuffer = encrypt_buffer + stream_sizes.cbHeader; message_buffers[1].cbBuffer = length; memcpy(message_buffers[1].pvBuffer, buffer, length); message_buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; message_buffers[2].pvBuffer = encrypt_buffer + stream_sizes.cbHeader + length; message_buffers[2].cbBuffer = stream_sizes.cbTrailer; message_buffers[3].BufferType = SECBUFFER_EMPTY; message_buffers[3].pvBuffer = nullptr; message_buffers[3].cbBuffer = 0; status = EncryptMessage( static_cast(ssl_context_handle_), 0, &message_desc, 0); if (status != SEC_E_OK) { kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket SChannel encrypt failed: 0x%x", status); delete[] encrypt_buffer; return -1; } // calculate encrypted size and send to socket int encrypted_size = message_buffers[0].cbBuffer + message_buffers[1].cbBuffer + message_buffers[2].cbBuffer; int bytes_sent = ::send(ssl_socket_, encrypt_buffer, encrypted_size, 0); #ifdef DEBUG_NETWORK kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: SSL send - encrypted %d bytes, sent %d bytes", encrypted_size, bytes_sent); #endif delete[] encrypt_buffer; if (bytes_sent <= 0) { kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: SSL send failed: %d", WSAGetLastError()); return -1; } return length; #else if (!ssl_) { return -1; } int bytes_written = SSL_write(ssl_, buffer, length); if (bytes_written <= 0) { int error = SSL_get_error(ssl_, bytes_written); if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) { return 0; } return -1; } return bytes_written; #endif #else return ::send(ssl_socket_, buffer, length, 0); #endif } std::string WebSocketClient::base64Encode(const std::string& data) { const char* chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; std::string result; int i = 0; unsigned char char_array_3[3]; unsigned char char_array_4[4]; const char* bytes_to_encode = data.c_str(); int in_len = static_cast(data.length()); while (in_len--) { char_array_3[i++] = *(bytes_to_encode++); if (i == 3) { char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; for (i = 0; i < 4; i++) { result += chars[char_array_4[i]]; } i = 0; } } if (i) { for (int j = i; j < 3; j++) { char_array_3[j] = '\0'; } char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; for (int j = 0; j < i + 1; j++) { result += chars[char_array_4[j]]; } while (i++ < 3) { result += '='; } } return result; } #endif } void runt_websocket_create(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); if (args.Length() < 1 || !args[0]->IsString()) { isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "URL required").ToLocalChecked())); return; } String::Utf8Value url(isolate, args[0]); kinc_log(KINC_LOG_LEVEL_INFO, "[WebSocket Client] Creating connection to: %s", *url); try { int id = WebSocketWrapper::next_websocket_id++; auto client = std::make_unique(isolate, WebSocketWrapper::getGlobalContext(), *url); WebSocketWrapper::active_websockets[id] = std::move(client); kinc_log(KINC_LOG_LEVEL_INFO, "[WebSocket Client] Connection created successfully with ID: %d", id); args.GetReturnValue().Set(Number::New(isolate, id)); } catch (const std::exception& e) { kinc_log(KINC_LOG_LEVEL_ERROR, "[WebSocket Client] Failed to create connection: %s", e.what()); args.GetReturnValue().Set(Number::New(isolate, -1)); } catch (...) { kinc_log(KINC_LOG_LEVEL_ERROR, "[WebSocket Client] Unknown error creating connection"); args.GetReturnValue().Set(Number::New(isolate, -1)); } } void runt_websocket_send(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsNumber()) { isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "ID and data required").ToLocalChecked())); return; } int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); auto it = WebSocketWrapper::active_websockets.find(id); if (it == WebSocketWrapper::active_websockets.end()) { return; } if (args[1]->IsArrayBuffer()) { Local buffer = args[1].As(); void* data = buffer->GetBackingStore()->Data(); size_t length = buffer->ByteLength(); std::string binaryData(static_cast(data), length); it->second->sendBinary(binaryData); } else if (args[1]->IsArrayBufferView()) { Local view = args[1].As(); Local buffer = view->Buffer(); void* data = static_cast(buffer->GetBackingStore()->Data()) + view->ByteOffset(); size_t length = view->ByteLength(); std::string binaryData(static_cast(data), length); it->second->sendBinary(binaryData); } else if (args[1]->IsString()) { String::Utf8Value data(isolate, args[1]); it->second->send(*data); } else { isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Data must be String or ArrayBuffer").ToLocalChecked())); } } void runt_websocket_send_binary(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsNumber()) { isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "ID and binary data required").ToLocalChecked())); return; } int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); auto it = WebSocketWrapper::active_websockets.find(id); if (it == WebSocketWrapper::active_websockets.end()) { return; } if (args[1]->IsArrayBuffer()) { Local buffer = args[1].As(); void* data = buffer->GetBackingStore()->Data(); size_t length = buffer->ByteLength(); std::string binaryData(static_cast(data), length); it->second->sendBinary(binaryData); } else if (args[1]->IsArrayBufferView()) { Local view = args[1].As(); Local buffer = view->Buffer(); void* data = static_cast(buffer->GetBackingStore()->Data()) + view->ByteOffset(); size_t length = view->ByteLength(); std::string binaryData(static_cast(data), length); it->second->sendBinary(binaryData); } else { isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Binary data must be ArrayBuffer or ArrayBufferView").ToLocalChecked())); } } void runt_websocket_close(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); if (args.Length() < 1 || !args[0]->IsNumber()) { isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "ID required").ToLocalChecked())); return; } int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); int code = 1000; std::string reason = ""; if (args.Length() > 1 && args[1]->IsNumber()) { code = args[1]->Int32Value(isolate->GetCurrentContext()).FromJust(); } if (args.Length() > 2 && args[2]->IsString()) { String::Utf8Value reasonStr(isolate, args[2]); reason = *reasonStr; } auto it = WebSocketWrapper::active_websockets.find(id); if (it != WebSocketWrapper::active_websockets.end()) { it->second->close(code, reason); // erase here causes iterator invalidation when called from callback // closed websockets will be cleaned up naturally // WebSocketWrapper::active_websockets.erase(it); } } void runt_websocket_get_ready_state(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); if (args.Length() < 1 || !args[0]->IsNumber()) { isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "ID required").ToLocalChecked())); return; } int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); auto it = WebSocketWrapper::active_websockets.find(id); if (it != WebSocketWrapper::active_websockets.end()) { args.GetReturnValue().Set(Number::New(isolate, it->second->getReadyState())); } else { args.GetReturnValue().Set(Number::New(isolate, WebSocketWrapper::CLOSED)); } } void runt_websocket_get_url(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); if (args.Length() < 1 || !args[0]->IsNumber()) { isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "ID required").ToLocalChecked())); return; } int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); auto it = WebSocketWrapper::active_websockets.find(id); if (it != WebSocketWrapper::active_websockets.end()) { args.GetReturnValue().Set(String::NewFromUtf8(isolate, it->second->getUrl().c_str()).ToLocalChecked()); } else { args.GetReturnValue().Set(String::NewFromUtf8(isolate, "").ToLocalChecked()); } } void runt_websocket_get_buffered_amount(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); if (args.Length() < 1 || !args[0]->IsNumber()) { isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "ID required").ToLocalChecked())); return; } int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); auto it = WebSocketWrapper::active_websockets.find(id); if (it != WebSocketWrapper::active_websockets.end()) { args.GetReturnValue().Set(Number::New(isolate, it->second->getBufferedAmount())); } else { args.GetReturnValue().Set(Number::New(isolate, 0)); } } void runt_websocket_set_onopen(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) { isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "ID and callback required").ToLocalChecked())); return; } int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); Local callback = Local::Cast(args[1]); auto it = WebSocketWrapper::active_websockets.find(id); if (it != WebSocketWrapper::active_websockets.end()) { it->second->setOnOpen(callback); } } void runt_websocket_set_onmessage(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) { isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "ID and callback required").ToLocalChecked())); return; } int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); Local callback = Local::Cast(args[1]); auto it = WebSocketWrapper::active_websockets.find(id); if (it != WebSocketWrapper::active_websockets.end()) { it->second->setOnMessage(callback); } } void runt_websocket_set_onerror(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) { isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "ID and callback required").ToLocalChecked())); return; } int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); Local callback = Local::Cast(args[1]); auto it = WebSocketWrapper::active_websockets.find(id); if (it != WebSocketWrapper::active_websockets.end()) { it->second->setOnError(callback); } } void runt_websocket_set_onclose(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) { isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "ID and callback required").ToLocalChecked())); return; } int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); Local callback = Local::Cast(args[1]); auto it = WebSocketWrapper::active_websockets.find(id); if (it != WebSocketWrapper::active_websockets.end()) { it->second->setOnClose(callback); } } // Native WebSocket class implementation for V8 static void WebSocketConstructor(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); Local context = isolate->GetCurrentContext(); if (!args.IsConstructCall()) { isolate->ThrowException(Exception::TypeError( String::NewFromUtf8(isolate, "WebSocket constructor requires 'new'").ToLocalChecked())); return; } if (args.Length() < 1 || !args[0]->IsString()) { isolate->ThrowException(Exception::TypeError( String::NewFromUtf8(isolate, "WebSocket constructor requires a URL").ToLocalChecked())); return; } String::Utf8Value url(isolate, args[0]); int wsId = WebSocketWrapper::createWebSocketConnection(isolate, *url); Local instance = args.This(); instance->Set(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked(), Integer::New(isolate, wsId)); instance->Set(context, String::NewFromUtf8(isolate, "url").ToLocalChecked(), args[0]); instance->Set(context, String::NewFromUtf8(isolate, "readyState").ToLocalChecked(), Integer::New(isolate, 0)); instance->Set(context, String::NewFromUtf8(isolate, "protocol").ToLocalChecked(), String::NewFromUtf8(isolate, "").ToLocalChecked()); instance->Set(context, String::NewFromUtf8(isolate, "extensions").ToLocalChecked(), String::NewFromUtf8(isolate, "").ToLocalChecked()); instance->Set(context, String::NewFromUtf8(isolate, "binaryType").ToLocalChecked(), String::NewFromUtf8(isolate, "blob").ToLocalChecked()); instance->Set(context, String::NewFromUtf8(isolate, "bufferedAmount").ToLocalChecked(), Integer::New(isolate, 0)); instance->Set(context, String::NewFromUtf8(isolate, "onopen").ToLocalChecked(), Null(isolate)); instance->Set(context, String::NewFromUtf8(isolate, "onmessage").ToLocalChecked(), Null(isolate)); instance->Set(context, String::NewFromUtf8(isolate, "onerror").ToLocalChecked(), Null(isolate)); instance->Set(context, String::NewFromUtf8(isolate, "onclose").ToLocalChecked(), Null(isolate)); args.GetReturnValue().Set(instance); } static void WebSocketSend(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); Local context = isolate->GetCurrentContext(); Local self = args.Holder(); Local readyStateVal = self->Get(context, String::NewFromUtf8(isolate, "readyState").ToLocalChecked()).ToLocalChecked(); int readyState = readyStateVal->Int32Value(context).FromJust(); if (readyState == 0) { isolate->ThrowException(Exception::Error( String::NewFromUtf8(isolate, "WebSocket is not open: readyState 0 (CONNECTING)").ToLocalChecked())); return; } if (readyState != 1) { isolate->ThrowException(Exception::Error( String::NewFromUtf8(isolate, "WebSocket is not open").ToLocalChecked())); return; } Local idVal = self->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked(); int wsId = idVal->Int32Value(context).FromJust(); auto it = WebSocketWrapper::active_websockets.find(wsId); if (it == WebSocketWrapper::active_websockets.end()) { isolate->ThrowException(Exception::Error( String::NewFromUtf8(isolate, "WebSocket connection not found").ToLocalChecked())); return; } if (args.Length() < 1) return; if (args[0]->IsArrayBuffer()) { Local ab = args[0].As(); std::string data(static_cast(ab->GetBackingStore()->Data()), ab->ByteLength()); it->second->sendBinary(data); } else if (args[0]->IsArrayBufferView()) { Local view = args[0].As(); Local ab = view->Buffer(); size_t offset = view->ByteOffset(); size_t length = view->ByteLength(); std::string data(static_cast(ab->GetBackingStore()->Data()) + offset, length); it->second->sendBinary(data); } else { String::Utf8Value str(isolate, args[0]); it->second->send(*str); } } static void WebSocketClose(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); Local context = isolate->GetCurrentContext(); Local self = args.Holder(); Local readyStateVal = self->Get(context, String::NewFromUtf8(isolate, "readyState").ToLocalChecked()).ToLocalChecked(); int readyState = readyStateVal->Int32Value(context).FromJust(); if (readyState == 2 || readyState == 3) return; self->Set(context, String::NewFromUtf8(isolate, "readyState").ToLocalChecked(), Integer::New(isolate, 2)); Local idVal = self->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked(); int wsId = idVal->Int32Value(context).FromJust(); int code = 1000; std::string reason = ""; if (args.Length() >= 1 && args[0]->IsNumber()) { code = args[0]->Int32Value(context).FromJust(); } if (args.Length() >= 2 && args[1]->IsString()) { String::Utf8Value r(isolate, args[1]); reason = *r; } auto it = WebSocketWrapper::active_websockets.find(wsId); if (it != WebSocketWrapper::active_websockets.end()) { it->second->close(code, reason); } } void createWebSocketClass(Isolate* isolate, Local& global) { Local wsTpl = FunctionTemplate::New(isolate, WebSocketConstructor); wsTpl->SetClassName(String::NewFromUtf8(isolate, "WebSocket").ToLocalChecked()); wsTpl->InstanceTemplate()->SetInternalFieldCount(1); Local proto = wsTpl->PrototypeTemplate(); proto->Set(isolate, "send", FunctionTemplate::New(isolate, WebSocketSend)); proto->Set(isolate, "close", FunctionTemplate::New(isolate, WebSocketClose)); Local wsFunc = wsTpl->GetFunction(isolate->GetCurrentContext()).ToLocalChecked(); wsFunc->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "CONNECTING").ToLocalChecked(), Integer::New(isolate, 0)); wsFunc->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "OPEN").ToLocalChecked(), Integer::New(isolate, 1)); wsFunc->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "CLOSING").ToLocalChecked(), Integer::New(isolate, 2)); wsFunc->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "CLOSED").ToLocalChecked(), Integer::New(isolate, 3)); global->Set(isolate, "WebSocket", wsTpl); } static void EventConstructor(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); Local context = isolate->GetCurrentContext(); Local instance = args.This(); Local type = args.Length() > 0 && args[0]->IsString() ? args[0].As() : String::NewFromUtf8(isolate, "").ToLocalChecked(); instance->Set(context, String::NewFromUtf8(isolate, "type").ToLocalChecked(), type); instance->Set(context, String::NewFromUtf8(isolate, "target").ToLocalChecked(), Null(isolate)); instance->Set(context, String::NewFromUtf8(isolate, "currentTarget").ToLocalChecked(), Null(isolate)); instance->Set(context, String::NewFromUtf8(isolate, "timeStamp").ToLocalChecked(), Number::New(isolate, static_cast(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()))); args.GetReturnValue().Set(instance); } static void MessageEventConstructor(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); Local context = isolate->GetCurrentContext(); Local instance = args.This(); Local type = args.Length() > 0 && args[0]->IsString() ? args[0].As() : String::NewFromUtf8(isolate, "message").ToLocalChecked(); instance->Set(context, String::NewFromUtf8(isolate, "type").ToLocalChecked(), type); instance->Set(context, String::NewFromUtf8(isolate, "target").ToLocalChecked(), Null(isolate)); instance->Set(context, String::NewFromUtf8(isolate, "currentTarget").ToLocalChecked(), Null(isolate)); instance->Set(context, String::NewFromUtf8(isolate, "timeStamp").ToLocalChecked(), Number::New(isolate, static_cast(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()))); if (args.Length() > 1 && args[1]->IsObject()) { Local init = args[1].As(); Local data = init->Get(context, String::NewFromUtf8(isolate, "data").ToLocalChecked()).ToLocalChecked(); instance->Set(context, String::NewFromUtf8(isolate, "data").ToLocalChecked(), data); Local origin = init->Get(context, String::NewFromUtf8(isolate, "origin").ToLocalChecked()).ToLocalChecked(); instance->Set(context, String::NewFromUtf8(isolate, "origin").ToLocalChecked(), origin->IsUndefined() ? String::NewFromUtf8(isolate, "").ToLocalChecked().As() : origin); } else { instance->Set(context, String::NewFromUtf8(isolate, "data").ToLocalChecked(), Null(isolate)); instance->Set(context, String::NewFromUtf8(isolate, "origin").ToLocalChecked(), String::NewFromUtf8(isolate, "").ToLocalChecked()); } args.GetReturnValue().Set(instance); } static void CloseEventConstructor(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); Local context = isolate->GetCurrentContext(); Local instance = args.This(); Local type = args.Length() > 0 && args[0]->IsString() ? args[0].As() : String::NewFromUtf8(isolate, "close").ToLocalChecked(); instance->Set(context, String::NewFromUtf8(isolate, "type").ToLocalChecked(), type); instance->Set(context, String::NewFromUtf8(isolate, "target").ToLocalChecked(), Null(isolate)); instance->Set(context, String::NewFromUtf8(isolate, "currentTarget").ToLocalChecked(), Null(isolate)); instance->Set(context, String::NewFromUtf8(isolate, "timeStamp").ToLocalChecked(), Number::New(isolate, static_cast(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()))); int code = 1000; std::string reason = ""; bool wasClean = false; if (args.Length() > 1 && args[1]->IsObject()) { Local init = args[1].As(); Local codeVal = init->Get(context, String::NewFromUtf8(isolate, "code").ToLocalChecked()).ToLocalChecked(); if (codeVal->IsNumber()) code = codeVal->Int32Value(context).FromJust(); Local reasonVal = init->Get(context, String::NewFromUtf8(isolate, "reason").ToLocalChecked()).ToLocalChecked(); if (reasonVal->IsString()) { String::Utf8Value r(isolate, reasonVal); reason = *r; } Local cleanVal = init->Get(context, String::NewFromUtf8(isolate, "wasClean").ToLocalChecked()).ToLocalChecked(); if (cleanVal->IsBoolean()) wasClean = cleanVal->BooleanValue(isolate); } instance->Set(context, String::NewFromUtf8(isolate, "code").ToLocalChecked(), Integer::New(isolate, code)); instance->Set(context, String::NewFromUtf8(isolate, "reason").ToLocalChecked(), String::NewFromUtf8(isolate, reason.c_str()).ToLocalChecked()); instance->Set(context, String::NewFromUtf8(isolate, "wasClean").ToLocalChecked(), Boolean::New(isolate, wasClean)); args.GetReturnValue().Set(instance); } void createWebSocketEventClasses(Isolate* isolate, Local& global) { Local eventTpl = FunctionTemplate::New(isolate, EventConstructor); eventTpl->SetClassName(String::NewFromUtf8(isolate, "Event").ToLocalChecked()); global->Set(isolate, "Event", eventTpl); Local msgEventTpl = FunctionTemplate::New(isolate, MessageEventConstructor); msgEventTpl->SetClassName(String::NewFromUtf8(isolate, "MessageEvent").ToLocalChecked()); global->Set(isolate, "MessageEvent", msgEventTpl); Local closeEventTpl = FunctionTemplate::New(isolate, CloseEventConstructor); closeEventTpl->SetClassName(String::NewFromUtf8(isolate, "CloseEvent").ToLocalChecked()); global->Set(isolate, "CloseEvent", closeEventTpl); } #endif