├── LICENSE ├── Makefile ├── README.md ├── binding.gyp ├── dist ├── eiows.d.ts ├── eiows.js └── wrapper.mjs ├── nodejs ├── LICENSE └── src │ ├── addon.cpp │ └── addon.h ├── package.json └── uWebSockets ├── LICENSE └── src ├── Extensions.cpp ├── Extensions.h ├── Group.cpp ├── Group.h ├── Hub.cpp ├── Hub.h ├── Libuv.h ├── Networking.cpp ├── Networking.h ├── Node.cpp ├── Node.h ├── Socket.cpp ├── Socket.h ├── WebSocket.cpp ├── WebSocket.h └── WebSocketProtocol.h /LICENSE: -------------------------------------------------------------------------------- 1 | This software is based on uWebSockets which has ZLIB license 2 | make sure to check uWebsockets/src/LICENSE. 3 | 4 | For the code provided by mmdevries: 5 | 6 | MIT License 7 | 8 | Copyright (c) 2019 mmdevries 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NODE := $(shell node --version) 2 | NODE_VERSION := $(shell node --version | sed 's/^v//') 3 | USE_NCRYPTO := $(shell if [ $$(printf '%s\n' $(NODE_VERSION) 22.14.0 | sort -V | head -n1) = "22.14.0" ]; then echo 1; else echo 0; fi) 4 | 5 | default: 6 | git clone -c advice.detachedHead=false --depth 1 https://github.com/nodejs/node -b $(NODE) nodejs/src/node 7 | mv nodejs/src/node/src/crypto/crypto_tls.h nodejs/src/node/src/crypto/crypto_tls.h.bak 8 | @if [ "$(USE_NCRYPTO)" -eq "1" ]; then \ 9 | awk '1;/is_awaiting_new_session/{print "const ncrypto::SSLPointer *getSSL() const { return &ssl_; }"}' nodejs/src/node/src/crypto/crypto_tls.h.bak > nodejs/src/node/src/crypto/crypto_tls.h; \ 10 | else \ 11 | awk '1;/is_awaiting_new_session/{print " const SSLPointer *getSSL() const { return &ssl_; }"}' nodejs/src/node/src/crypto/crypto_tls.h.bak > nodejs/src/node/src/crypto/crypto_tls.h; \ 12 | fi 13 | 14 | .PHONY: clean 15 | clean: 16 | rm -rf nodejs/src/node 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | eiows is a replacement module for ws which allows, but doesn't guarantee, significant performance and memory-usage improvements. This module is specifically only compatible with Node.js. 2 | This package is mainly meant for projects which depend on the performance of the “original uws package”. This package requires engine.io(3.4.2 or higher) and it should work on Node 16, 18, 20, 22, 24. Git should be installed on the system to build and compile the module. 3 | This module only runs on Linux/FreeBSD/MacOS. 4 | 5 | Installation: 6 | 7 | npm install eiows 8 | 9 | or 10 | 11 | yarn add eiows 12 | 13 | 14 | Examples: 15 | 16 | // ESM 17 | import * as http from 'http'; 18 | import { Server } from "socket.io"; 19 | import eiows from 'eiows'; 20 | 21 | let server = http.createServer(); 22 | 23 | let io = new Server(server, { 24 | wsEngine: eiows.Server, 25 | perMessageDeflate: { 26 | threshold: 32768 27 | } 28 | }); 29 | 30 | io.on("connection", () => { 31 | console.log('Yes, you did it!'); 32 | }); 33 | server.listen(8080); 34 | 35 | // CJS 36 | var http = require('http'); 37 | var server = http.createServer(); 38 | 39 | var io = require("socket.io")(server, { 40 | wsEngine: require("eiows").Server, 41 | perMessageDeflate: { 42 | threshold: 32768 43 | } 44 | }); 45 | 46 | io.on("connection", function(socket) { 47 | console.log('Yes, you did it!'); 48 | }); 49 | server.listen(8080); 50 | 51 | Have fun! 52 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "eiows", 5 | 'variables': { 6 | 'node_version': '= \"20\"'", { 53 | 'include_dirs': [ 54 | 'nodejs/src/node/deps/ncrypto' 55 | ], 56 | 'conditions': [ 57 | ['OS!="mac"', { 58 | 'cflags_cc': ['-std=c++20'], 59 | }], 60 | ['OS=="mac"', { 61 | 'xcode_settings': { 62 | 'MACOSX_DEPLOYMENT_TARGET': '10.15', 63 | 'CLANG_CXX_LANGUAGE_STANDARD': 'c++20' 64 | } 65 | }] 66 | ] 67 | }] 68 | ] 69 | }, 70 | { 71 | 'target_name': 'action_after_build', 72 | 'type': 'none', 73 | 'dependencies': ['eiows'], 74 | 'actions': [ 75 | { 76 | 'action_name': 'move_lib', 77 | 'inputs': [ 78 | '<@(PRODUCT_DIR)/eiows.node' 79 | ], 80 | 'outputs': [ 81 | 'eiows' 82 | ], 83 | 'action': ['cp', '<@(PRODUCT_DIR)/eiows.node', 'dist/eiows_ { 40 | try { 41 | return require(`./eiows_${process.version}`); 42 | } catch (e) { 43 | throw new Error(e.toString() + '\n\nCompilation of µWebSockets has failed.' + 44 | 'Please install a supported C++17 compiler, git and/or update node and reinstall the module \'eiows\'.'); 45 | } 46 | })(); 47 | 48 | native.setNoop(noop); 49 | 50 | class WebSocket { 51 | constructor(external) { 52 | this.external = external; 53 | this.internalOnMessage = noop; 54 | this.internalOnClose = noop; 55 | } 56 | 57 | on(eventName, f) { 58 | if (eventName === 'message') { 59 | if (this.internalOnMessage !== noop) { 60 | throw Error(EE_ERROR); 61 | } 62 | this.internalOnMessage = f; 63 | } 64 | return this; 65 | } 66 | 67 | once(eventName, f) { 68 | if (eventName === 'close') { 69 | if (this.internalOnClose !== noop) { 70 | throw Error(EE_ERROR); 71 | } 72 | this.internalOnClose = (code, message) => { 73 | this.internalOnClose = noop; 74 | f(code, message); 75 | }; 76 | } 77 | return this; 78 | } 79 | 80 | get _socket() { 81 | const address = this.external ? native.getAddress(this.external) : new Array(3); 82 | return { 83 | remotePort: address[0], 84 | remoteAddress: address[1], 85 | remoteFamily: address[2] 86 | }; 87 | } 88 | 89 | removeListener() { 90 | return this; 91 | } 92 | 93 | send(message, options, cb) { // options will be ignored 94 | if (this.external) { 95 | const binary = (typeof message !== 'string'); 96 | var compress = false; 97 | if (eiows.compressBo) { 98 | var byteLength; 99 | if (!binary) { 100 | byteLength = Buffer.byteLength(message); 101 | } else { 102 | byteLength = toBuffer(message).length; 103 | } 104 | if (byteLength >= eiows.compressThreshold) { 105 | compress = true; 106 | } 107 | } 108 | if (typeof options === 'function') { 109 | cb = options; 110 | } 111 | native.server.send(this.external, message, binary ? eiows.OPCODE_BINARY : eiows.OPCODE_TEXT, cb ? (() => { 112 | process.nextTick(cb); 113 | }) : undefined, compress); 114 | } else if (cb) { 115 | cb(new Error('not opened')); 116 | } 117 | } 118 | 119 | close(code, data) { 120 | if (this.external) { 121 | native.server.close(this.external, code, data); 122 | this.external = null; 123 | } 124 | } 125 | } 126 | 127 | class Server { 128 | constructor(options) { 129 | if (!options) { 130 | throw new TypeError('missing options'); 131 | } 132 | 133 | var nativeOptions = 0; 134 | if (options.perMessageDeflate !== undefined && options.perMessageDeflate !== false) { 135 | nativeOptions |= eiows.PERMESSAGE_DEFLATE; 136 | eiows.compressBo = true; 137 | if (!isNaN(options.perMessageDeflate.threshold) && options.perMessageDeflate.threshold >= 0) { 138 | eiows.compressThreshold = options.perMessageDeflate.threshold; 139 | } 140 | if (options.perMessageDeflate.serverNoContextTakeover === false) { 141 | nativeOptions |= eiows.SLIDING_DEFLATE_WINDOW; 142 | } 143 | } 144 | 145 | this.serverGroup = native.server.group.create(nativeOptions, options.maxPayload === undefined ? DEFAULT_PAYLOAD_LIMIT : options.maxPayload); 146 | 147 | this._upgradeCallback = noop; 148 | this._noDelay = options.noDelay === undefined ? true : options.noDelay; 149 | 150 | native.server.group.onDisconnection(this.serverGroup, (external, code, message, webSocket) => { 151 | webSocket.external = null; 152 | process.nextTick(() => { 153 | webSocket.internalOnClose(code, message); 154 | }); 155 | native.clearUserData(external); 156 | }); 157 | 158 | native.server.group.onMessage(this.serverGroup, (message, webSocket) => { 159 | webSocket.internalOnMessage(message, typeof message === 'object'); 160 | }); 161 | 162 | native.server.group.onConnection(this.serverGroup, (external) => { 163 | const webSocket = new WebSocket(external); 164 | native.setUserData(external, webSocket); 165 | this._upgradeCallback(webSocket); 166 | }); 167 | } 168 | 169 | handleUpgrade(request, socket, upgradeHead, callback) { 170 | const secKey = request.headers['sec-websocket-key']; 171 | const socketHandle = socket.ssl ? socket._parent._handle : socket._handle; 172 | if (socketHandle && secKey && secKey.length === 24) { 173 | const sslState = socket.ssl ? native.getSSLContext(socket.ssl) : null; 174 | socket.setNoDelay(this._noDelay); 175 | const ticket = native.transfer(socketHandle.fd === -1 ? socketHandle : socketHandle.fd, sslState); 176 | socket.on('close', () => { 177 | if (this.serverGroup) { 178 | this._upgradeCallback = callback; 179 | native.upgrade(this.serverGroup, ticket, secKey, request.headers['sec-websocket-extensions'], request.headers['sec-websocket-protocol']); 180 | } 181 | }); 182 | setImmediate(() => { 183 | socket.destroy(); 184 | }); 185 | } else { 186 | return abortConnection(socket, 400, 'Bad Request'); 187 | } 188 | } 189 | 190 | close() { 191 | if (this.serverGroup) { 192 | native.server.group.close(this.serverGroup); 193 | this.serverGroup = null; 194 | } 195 | } 196 | } 197 | 198 | eiows.Server = Server; 199 | eiows.native = native; 200 | 201 | module.exports = eiows; 202 | -------------------------------------------------------------------------------- /dist/wrapper.mjs: -------------------------------------------------------------------------------- 1 | import eiows from './eiows.js'; 2 | export default eiows; 3 | -------------------------------------------------------------------------------- /nodejs/LICENSE: -------------------------------------------------------------------------------- 1 | mmdevries notice: 2 | 3 | This software is modified fork of uWebSockets the original software 4 | is available in uWebSockets(https://github.com/uNetworking/uWebSockets/tree/v0.14) repository. 5 | 6 | 7 | Copyright (c) 2016 Alex Hultman and contributors 8 | 9 | This software is provided 'as-is', without any express or implied 10 | warranty. In no event will the authors be held liable for any damages 11 | arising from the use of this software. 12 | 13 | Permission is granted to anyone to use this software for any purpose, 14 | including commercial applications, and to alter it and redistribute it 15 | freely, subject to the following restrictions: 16 | 17 | 1. The origin of this software must not be misrepresented; you must not 18 | claim that you wrote the original software. If you use this software 19 | in a product, an acknowledgement in the product documentation would be 20 | appreciated but is not required. 21 | 2. Altered source versions must be plainly marked as such, and must not be 22 | misrepresented as being the original software. 23 | 3. This notice may not be removed or altered from any source distribution. 24 | -------------------------------------------------------------------------------- /nodejs/src/addon.cpp: -------------------------------------------------------------------------------- 1 | #include "../../uWebSockets/src/Hub.h" 2 | #include "addon.h" 3 | 4 | void Main(Local exports) 5 | { 6 | Isolate *isolate = exports->GetIsolate(); 7 | exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "server", NewStringType::kNormal).ToLocalChecked(), Namespace(isolate).object); 8 | NODE_SET_METHOD(exports, "getSSLContext", getSSLContext); 9 | NODE_SET_METHOD(exports, "setUserData", setUserData); 10 | NODE_SET_METHOD(exports, "clearUserData", clearUserData); 11 | NODE_SET_METHOD(exports, "getAddress", getAddress); 12 | NODE_SET_METHOD(exports, "transfer", transfer); 13 | NODE_SET_METHOD(exports, "upgrade", upgrade); 14 | NODE_SET_METHOD(exports, "setNoop", setNoop); 15 | registerCheck(isolate); 16 | } 17 | 18 | NODE_MODULE(eiows, Main) 19 | -------------------------------------------------------------------------------- /nodejs/src/addon.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define NODE_WANT_INTERNALS 1 9 | #include "node/src/crypto/crypto_tls.h" 10 | 11 | using BaseObject = node::BaseObject; 12 | using TLSWrap = node::crypto::TLSWrap; 13 | 14 | class TLSWrapSSLGetter : public TLSWrap { 15 | public: 16 | void setSSL(const v8::FunctionCallbackInfo &info){ 17 | v8::Isolate* isolate = info.GetIsolate(); 18 | if (!getSSL()){ 19 | info.GetReturnValue().Set(v8::Null(isolate)); 20 | return; 21 | } 22 | SSL* ptr = getSSL()->get(); 23 | info.GetReturnValue().Set(v8::External::New(isolate, ptr)); 24 | } 25 | }; 26 | #undef NODE_WANT_INTERNALS 27 | 28 | using namespace std; 29 | using namespace v8; 30 | 31 | eioWS::Hub hub(0); 32 | uv_check_t check; 33 | Persistent noop; 34 | 35 | void registerCheck(Isolate *isolate) { 36 | uv_check_init((uv_loop_t *)hub.getLoop(), &check); 37 | check.data = isolate; 38 | uv_check_start(&check, [](uv_check_t *check) { 39 | Isolate *isolate = (Isolate *)check->data; 40 | HandleScope hs(isolate); 41 | Local::New(isolate, noop)->Call(isolate->GetCurrentContext(), Null(isolate), 0, nullptr); 42 | }); 43 | uv_unref((uv_handle_t *)&check); 44 | } 45 | 46 | class NativeString { 47 | char *data; 48 | size_t length; 49 | char utf8ValueMemory[sizeof(String::Utf8Value)]; 50 | String::Utf8Value *utf8Value = nullptr; 51 | 52 | public: 53 | NativeString(Isolate *isolate, const Local &value) { 54 | if (value->IsUndefined()) { 55 | data = nullptr; 56 | length = 0; 57 | } else if (value->IsString()) { 58 | utf8Value = new (utf8ValueMemory) String::Utf8Value(isolate, value); 59 | data = (**utf8Value); 60 | length = utf8Value->length(); 61 | } else if (node::Buffer::HasInstance(value)) { 62 | data = node::Buffer::Data(value); 63 | length = node::Buffer::Length(value); 64 | } else if (value->IsTypedArray()) { 65 | Local arrayBufferView = Local::Cast(value); 66 | auto contents = arrayBufferView->Buffer()->GetBackingStore(); 67 | length = arrayBufferView->ByteLength(); 68 | data = (char *) contents->Data() + arrayBufferView->ByteOffset(); 69 | } else if (value->IsArrayBuffer()) { 70 | Local arrayBuffer = Local::Cast(value); 71 | auto contents = arrayBuffer->GetBackingStore(); 72 | length = contents->ByteLength(); 73 | data = (char *) contents->Data(); 74 | } else if (value->IsSharedArrayBuffer()) { 75 | Local arrayBuffer = Local::Cast(value); 76 | auto contents = arrayBuffer->GetBackingStore(); 77 | length = contents->ByteLength(); 78 | data = (char *) contents->Data(); 79 | } else { 80 | static char empty[] = ""; 81 | data = empty; 82 | length = 0; 83 | } 84 | } 85 | 86 | char *getData() { return data; } 87 | 88 | size_t getLength() const { return length; } 89 | 90 | ~NativeString() { 91 | if (utf8Value) { 92 | utf8Value->~Utf8Value(); 93 | } 94 | } 95 | }; 96 | 97 | struct GroupData { 98 | Persistent connectionHandler, messageHandler, disconnectionHandler; 99 | int size = 0; 100 | }; 101 | 102 | void createGroup(const FunctionCallbackInfo &args) { 103 | eioWS::Group *group = hub.createGroup(args[0].As()->Value(), args[1].As()->Value()); 104 | group->setUserData(new GroupData); 105 | args.GetReturnValue().Set(External::New(args.GetIsolate(), group)); 106 | } 107 | 108 | inline Local wrapSocket(eioWS::WebSocket *webSocket, Isolate *isolate) { 109 | return External::New(isolate, webSocket); 110 | } 111 | 112 | inline eioWS::WebSocket *unwrapSocket(Local external) { 113 | return (eioWS::WebSocket *)external->Value(); 114 | } 115 | 116 | inline Local wrapMessage(const char *message, size_t length, eioWS::OpCode opCode, Isolate *isolate) { 117 | if (opCode == eioWS::OpCode::BINARY) { 118 | return node::Buffer::Copy(isolate, (char *)message, length).ToLocalChecked(); 119 | } else { 120 | return String::NewFromUtf8(isolate, message, NewStringType::kNormal, length).ToLocalChecked(); 121 | } 122 | } 123 | 124 | inline Local getDataV8(eioWS::WebSocket *webSocket, Isolate *isolate) { 125 | return webSocket->getUserData() ? Local::New(isolate, *(Persistent *)webSocket->getUserData()) : Local::Cast(Undefined(isolate)); 126 | } 127 | 128 | void getUserData(const FunctionCallbackInfo &args) { 129 | args.GetReturnValue().Set(getDataV8(unwrapSocket(args[0].As()), args.GetIsolate())); 130 | } 131 | 132 | void clearUserData(const FunctionCallbackInfo &args) { 133 | eioWS::WebSocket *webSocket = unwrapSocket(args[0].As()); 134 | ((Persistent *)webSocket->getUserData())->Reset(); 135 | delete (Persistent *)webSocket->getUserData(); 136 | } 137 | 138 | void setUserData(const FunctionCallbackInfo &args) { 139 | eioWS::WebSocket *webSocket = unwrapSocket(args[0].As()); 140 | if (webSocket->getUserData()) { 141 | ((Persistent *)webSocket->getUserData())->Reset(args.GetIsolate(), args[1]); 142 | } else { 143 | webSocket->setUserData(new Persistent(args.GetIsolate(), args[1])); 144 | } 145 | } 146 | 147 | void getAddress(const FunctionCallbackInfo &args) { 148 | typename eioWS::WebSocket::Address address = unwrapSocket(args[0].As())->getAddress(); 149 | Isolate *isolate = args.GetIsolate(); 150 | Local array = Array::New(isolate, 3); 151 | array->Set(isolate->GetCurrentContext(), 0, Integer::New(isolate, address.port)); 152 | array->Set(isolate->GetCurrentContext(), 1, String::NewFromUtf8(isolate, address.address, NewStringType::kNormal).ToLocalChecked()); 153 | array->Set(isolate->GetCurrentContext(), 2, String::NewFromUtf8(isolate, address.family, NewStringType::kNormal).ToLocalChecked()); 154 | args.GetReturnValue().Set(array); 155 | } 156 | 157 | uv_handle_t *getTcpHandle(void *handleWrap) { 158 | volatile char *memory = (volatile char *)handleWrap; 159 | for (volatile uv_handle_t *tcpHandle = (volatile uv_handle_t *)memory; 160 | tcpHandle->type != UV_TCP || tcpHandle->data != handleWrap || 161 | tcpHandle->loop != uv_default_loop(); 162 | tcpHandle = (volatile uv_handle_t *)memory) { 163 | memory++; 164 | } 165 | return (uv_handle_t *)memory; 166 | } 167 | 168 | struct SendCallbackData { 169 | Persistent jsCallback; 170 | Isolate *isolate; 171 | }; 172 | 173 | void sendCallback(eioWS::WebSocket *webSocket, void *data, bool cancelled, void *reserved) { 174 | SendCallbackData *sc = static_cast(data); 175 | if (!cancelled) { 176 | HandleScope hs(sc->isolate); 177 | Local::New(sc->isolate, sc->jsCallback)->Call(sc->isolate->GetCurrentContext(), Null(sc->isolate), 0, nullptr); 178 | } 179 | sc->jsCallback.Reset(); 180 | delete sc; 181 | } 182 | 183 | void send(const FunctionCallbackInfo &args) { 184 | eioWS::OpCode opCode = (eioWS::OpCode)args[2].As()->Value(); 185 | NativeString nativeString(args.GetIsolate(), args[1]); 186 | 187 | SendCallbackData *sc = nullptr; 188 | void (*callback)(eioWS::WebSocket *, void *, bool, void *) = nullptr; 189 | 190 | if (args[3]->IsFunction()) { 191 | callback = sendCallback; 192 | sc = new SendCallbackData; 193 | sc->jsCallback.Reset(args.GetIsolate(), Local::Cast(args[3])); 194 | sc->isolate = args.GetIsolate(); 195 | } 196 | 197 | bool compress = args[4].As()->Value(); 198 | unwrapSocket(args[0].As())->send(nativeString.getData(), nativeString.getLength(), opCode, callback, sc, compress); 199 | } 200 | 201 | struct Ticket { 202 | uv_os_sock_t fd; 203 | SSL *ssl; 204 | }; 205 | 206 | void upgrade(const FunctionCallbackInfo &args) { 207 | eioWS::Group *serverGroup = (eioWS::Group *)args[0].As()->Value(); 208 | Ticket *ticket = static_cast(args[1].As()->Value()); 209 | Isolate *isolate = args.GetIsolate(); 210 | NativeString secKey(isolate, args[2]); 211 | NativeString extensions(isolate, args[3]); 212 | NativeString subprotocol(isolate, args[4]); 213 | 214 | // todo: move this check into core! 215 | if (ticket->fd != INVALID_SOCKET) { 216 | hub.upgrade(ticket->fd, secKey.getData(), ticket->ssl, extensions.getData(), extensions.getLength(), subprotocol.getData(), subprotocol.getLength(), serverGroup); 217 | } else { 218 | if (ticket->ssl) { 219 | SSL_free(ticket->ssl); 220 | } 221 | } 222 | delete ticket; 223 | } 224 | 225 | void transfer(const FunctionCallbackInfo &args) { 226 | // (_handle.fd OR _handle), SSL 227 | uv_handle_t *handle = nullptr; 228 | Ticket *ticket = new Ticket; 229 | if (args[0]->IsObject()) { 230 | Local context = args.GetIsolate()->GetCurrentContext(); 231 | uv_fileno((handle = getTcpHandle( args[0]->ToObject(context).ToLocalChecked()->GetAlignedPointerFromInternalField(0))), (uv_os_fd_t *)&ticket->fd); 232 | } else { 233 | ticket->fd = args[0].As()->Value(); 234 | } 235 | 236 | ticket->fd = dup(ticket->fd); 237 | ticket->ssl = nullptr; 238 | if (args[1]->IsExternal()) { 239 | ticket->ssl = (SSL *)args[1].As()->Value(); 240 | SSL_up_ref(ticket->ssl); 241 | } 242 | 243 | // uv_close calls shutdown if not set on Windows 244 | if (handle) { 245 | // UV_HANDLE_SHARED_TCP_SOCKET 246 | handle->flags |= 0x40000000; 247 | } 248 | 249 | args.GetReturnValue().Set(External::New(args.GetIsolate(), ticket)); 250 | } 251 | 252 | void onConnection(const FunctionCallbackInfo &args) { 253 | eioWS::Group *group = (eioWS::Group *)args[0].As()->Value(); 254 | GroupData *groupData = static_cast(group->getUserData()); 255 | 256 | Isolate *isolate = args.GetIsolate(); 257 | Persistent *connectionCallback = &groupData->connectionHandler; 258 | connectionCallback->Reset(isolate, Local::Cast(args[1])); 259 | group->onConnection([isolate, connectionCallback, groupData](eioWS::WebSocket *webSocket) { 260 | groupData->size++; 261 | HandleScope hs(isolate); 262 | Local argv[] = {wrapSocket(webSocket, isolate)}; 263 | Local::New(isolate, *connectionCallback)->Call(isolate->GetCurrentContext(), Null(isolate), 1, argv); 264 | }); 265 | } 266 | 267 | void onMessage(const FunctionCallbackInfo &args) { 268 | eioWS::Group *group = (eioWS::Group *)args[0].As()->Value(); 269 | GroupData *groupData = static_cast(group->getUserData()); 270 | 271 | Isolate *isolate = args.GetIsolate(); 272 | Persistent *messageCallback = &groupData->messageHandler; 273 | 274 | messageCallback->Reset(isolate, Local::Cast(args[1])); 275 | group->onMessage([isolate, messageCallback, group](eioWS::WebSocket *webSocket, const char *message, size_t length, eioWS::OpCode opCode) { 276 | if(length != 1 || message[0] != 65) { 277 | HandleScope hs(isolate); 278 | Local argv[] = {wrapMessage(message, length, opCode, isolate), 279 | getDataV8(webSocket, isolate)}; 280 | Local::New(isolate, *messageCallback)->Call(isolate->GetCurrentContext(), Null(isolate), 2, argv); 281 | } 282 | }); 283 | } 284 | 285 | void onDisconnection(const FunctionCallbackInfo &args) { 286 | eioWS::Group *group = (eioWS::Group *)args[0].As()->Value(); 287 | GroupData *groupData = static_cast(group->getUserData()); 288 | 289 | Isolate *isolate = args.GetIsolate(); 290 | Persistent *disconnectionCallback = &groupData->disconnectionHandler; 291 | disconnectionCallback->Reset(isolate, Local::Cast(args[1])); 292 | 293 | group->onDisconnection([isolate, disconnectionCallback, groupData]( eioWS::WebSocket *webSocket, int code, char *message, size_t length) { 294 | groupData->size--; 295 | HandleScope hs(isolate); 296 | Local argv[] = { 297 | wrapSocket(webSocket, isolate), Integer::New(isolate, code), 298 | wrapMessage(message, length, eioWS::OpCode::CLOSE, isolate), 299 | getDataV8(webSocket, isolate)}; 300 | Local::New(isolate, *disconnectionCallback)->Call(isolate->GetCurrentContext(), Null(isolate), 4, argv); 301 | }); 302 | } 303 | 304 | void closeSocket(const FunctionCallbackInfo &args) { 305 | NativeString nativeString(args.GetIsolate(), args[2]); 306 | unwrapSocket(args[0].As())->close(args[1].As()->Value(), nativeString.getData(), nativeString.getLength()); 307 | } 308 | 309 | void closeGroup(const FunctionCallbackInfo &args) { 310 | NativeString nativeString(args.GetIsolate(), args[2]); 311 | eioWS::Group *group = (eioWS::Group *)args[0].As()->Value(); 312 | group->close(args[1].As()->Value(), nativeString.getData(), nativeString.getLength()); 313 | } 314 | 315 | void getSSLContext(const FunctionCallbackInfo &args) { 316 | Isolate* isolate = args.GetIsolate(); 317 | if(args.Length() < 1 || !args[0]->IsObject()){ 318 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Error: One object expected", NewStringType::kNormal).ToLocalChecked())); 319 | return; 320 | } 321 | Local context = isolate->GetCurrentContext(); 322 | Local obj = args[0]->ToObject(context).ToLocalChecked(); 323 | TLSWrapSSLGetter* tw; 324 | ASSIGN_OR_RETURN_UNWRAP(&tw, obj); 325 | tw->setSSL(args); 326 | } 327 | 328 | void setNoop(const FunctionCallbackInfo &args) { 329 | noop.Reset(args.GetIsolate(), Local::Cast(args[0])); 330 | } 331 | 332 | struct Namespace { 333 | Local object; 334 | Namespace(Isolate *isolate) { 335 | object = Object::New(isolate); 336 | NODE_SET_METHOD(object, "send", send); 337 | NODE_SET_METHOD(object, "close", closeSocket); 338 | 339 | Local group = Object::New(isolate); 340 | NODE_SET_METHOD(group, "onConnection", onConnection); 341 | NODE_SET_METHOD(group, "onMessage", onMessage); 342 | NODE_SET_METHOD(group, "onDisconnection", onDisconnection); 343 | 344 | NODE_SET_METHOD(group, "create", createGroup); 345 | NODE_SET_METHOD(group, "close", closeGroup); 346 | 347 | object->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "group", NewStringType::kNormal).ToLocalChecked(), group); 348 | } 349 | }; 350 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eiows", 3 | "version": "8.2.0", 4 | "description": "custom fork of uWebSockets 0.14", 5 | "license": "MIT", 6 | "main": "./dist/eiows.js", 7 | "types": "./dist/eiows.d.ts", 8 | "os": ["!win32", "linux", "freebsd", "darwin"], 9 | "exports": { 10 | ".": { 11 | "import": "./dist/wrapper.mjs", 12 | "require": "./dist/eiows.js", 13 | "types": "./dist/eiows.d.ts" 14 | }, 15 | "./package.json": "./package.json" 16 | }, 17 | "scripts": { 18 | "preinstall": "make", 19 | "install": "node-gyp rebuild > build_log.txt 2>&1 || exit 0", 20 | "postinstall": "make clean" 21 | }, 22 | "engines": { 23 | "node": ">=16 <25.0" 24 | }, 25 | "keywords": [ 26 | "websocket", 27 | "engine.io", 28 | "socket.io", 29 | "eiows", 30 | "uws" 31 | ], 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/mmdevries/eiows.git" 35 | }, 36 | "bugs": { 37 | "url": "https://github.com/mmdevries/eiows/issues" 38 | }, 39 | "homepage": "https://github.com/mmdevries/eiows", 40 | "gypfile": true 41 | } 42 | -------------------------------------------------------------------------------- /uWebSockets/LICENSE: -------------------------------------------------------------------------------- 1 | mmdevries notice: 2 | 3 | This software is modified fork of uWebSockets the original software 4 | is available in uWebSockets(https://github.com/uNetworking/uWebSockets/tree/v0.14) repository. 5 | 6 | 7 | Original License: 8 | 9 | Copyright (c) 2016 Alex Hultman and contributors 10 | 11 | This software is provided 'as-is', without any express or implied 12 | warranty. In no event will the authors be held liable for any damages 13 | arising from the use of this software. 14 | 15 | Permission is granted to anyone to use this software for any purpose, 16 | including commercial applications, and to alter it and redistribute it 17 | freely, subject to the following restrictions: 18 | 19 | 1. The origin of this software must not be misrepresented; you must not 20 | claim that you wrote the original software. If you use this software 21 | in a product, an acknowledgement in the product documentation would be 22 | appreciated but is not required. 23 | 2. Altered source versions must be plainly marked as such, and must not be 24 | misrepresented as being the original software. 25 | 3. This notice may not be removed or altered from any source distribution. 26 | -------------------------------------------------------------------------------- /uWebSockets/src/Extensions.cpp: -------------------------------------------------------------------------------- 1 | #include "Extensions.h" 2 | 3 | namespace eioWS { 4 | 5 | enum ExtensionTokens { 6 | TOK_PERMESSAGE_DEFLATE = 1838, 7 | TOK_SERVER_NO_CONTEXT_TAKEOVER = 2807, 8 | TOK_CLIENT_NO_CONTEXT_TAKEOVER = 2783, 9 | TOK_SERVER_MAX_WINDOW_BITS = 2372, 10 | TOK_CLIENT_MAX_WINDOW_BITS = 2348 11 | }; 12 | 13 | class ExtensionsParser { 14 | private: 15 | int *lastInteger = nullptr; 16 | 17 | public: 18 | bool perMessageDeflate = false; 19 | bool serverNoContextTakeover = false; 20 | bool clientNoContextTakeover = false; 21 | int serverMaxWindowBits = 0; 22 | int clientMaxWindowBits = 0; 23 | 24 | static int getToken(const char *&in, const char *stop); 25 | ExtensionsParser(const char *data, size_t length); 26 | }; 27 | 28 | int ExtensionsParser::getToken(const char *&in, const char *stop) { 29 | while (!isalnum(*in) && in != stop) { 30 | in++; 31 | } 32 | 33 | int hashedToken = 0; 34 | while (isalnum(*in) || *in == '-' || *in == '_') { 35 | if (isdigit(*in)) { 36 | hashedToken = hashedToken * 10 - (*in - '0'); 37 | } else { 38 | hashedToken += *in; 39 | } 40 | in++; 41 | } 42 | return hashedToken; 43 | } 44 | 45 | ExtensionsParser::ExtensionsParser(const char *data, size_t length) { 46 | const char *stop = data + length; 47 | int token = 1; 48 | for (; token && token != TOK_PERMESSAGE_DEFLATE; token = getToken(data, stop)); 49 | 50 | perMessageDeflate = (token == TOK_PERMESSAGE_DEFLATE); 51 | while ((token = getToken(data, stop))) { 52 | switch (token) { 53 | case TOK_PERMESSAGE_DEFLATE: 54 | return; 55 | case TOK_SERVER_NO_CONTEXT_TAKEOVER: 56 | serverNoContextTakeover = true; 57 | break; 58 | case TOK_CLIENT_NO_CONTEXT_TAKEOVER: 59 | clientNoContextTakeover = true; 60 | break; 61 | case TOK_SERVER_MAX_WINDOW_BITS: 62 | serverMaxWindowBits = 1; 63 | lastInteger = &serverMaxWindowBits; 64 | break; 65 | case TOK_CLIENT_MAX_WINDOW_BITS: 66 | clientMaxWindowBits = 1; 67 | lastInteger = &clientMaxWindowBits; 68 | break; 69 | default: 70 | if (token < 0 && lastInteger) { 71 | *lastInteger = -token; 72 | } 73 | break; 74 | } 75 | } 76 | } 77 | 78 | ExtensionsNegotiator::ExtensionsNegotiator(int wantedOptions) { 79 | options = wantedOptions; 80 | } 81 | 82 | std::string ExtensionsNegotiator::generateOffer() const { 83 | std::string extensionsOffer; 84 | if (options & Options::PERMESSAGE_DEFLATE) { 85 | extensionsOffer += "permessage-deflate"; 86 | 87 | if (options & Options::CLIENT_NO_CONTEXT_TAKEOVER) { 88 | extensionsOffer += "; client_no_context_takeover"; 89 | } 90 | // we do not support accepting this yet 91 | // todo: if we agree on this, do not allocate a compressor 92 | // per socket! 93 | 94 | // It is RECOMMENDED that a server supports the 95 | // "server_no_context_takeover" extension parameter in an extension 96 | // negotiation offer. 97 | if (options & Options::SERVER_NO_CONTEXT_TAKEOVER) { 98 | //extensionsOffer += "; server_no_context_takeover"; 99 | } 100 | } 101 | return extensionsOffer; 102 | } 103 | 104 | void ExtensionsNegotiator::readOffer(std::string offer) { 105 | ExtensionsParser extensionsParser(offer.data(), offer.length()); 106 | if ((options & PERMESSAGE_DEFLATE) && extensionsParser.perMessageDeflate) { 107 | if (extensionsParser.clientNoContextTakeover || (options & CLIENT_NO_CONTEXT_TAKEOVER)) { 108 | options |= CLIENT_NO_CONTEXT_TAKEOVER; 109 | } 110 | if (extensionsParser.serverNoContextTakeover) { 111 | options |= SERVER_NO_CONTEXT_TAKEOVER; 112 | } else { 113 | options &= ~SERVER_NO_CONTEXT_TAKEOVER; 114 | } 115 | } else { 116 | options &= ~PERMESSAGE_DEFLATE; 117 | } 118 | } 119 | 120 | int ExtensionsNegotiator::getNegotiatedOptions() const { 121 | return options; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /uWebSockets/src/Extensions.h: -------------------------------------------------------------------------------- 1 | #ifndef EXTENSIONS_UWS_H 2 | #define EXTENSIONS_UWS_H 3 | 4 | #include 5 | 6 | namespace eioWS { 7 | enum Options : unsigned int { 8 | NO_OPTIONS = 0, 9 | PERMESSAGE_DEFLATE = 1, 10 | SERVER_NO_CONTEXT_TAKEOVER = 2, // remove this 11 | CLIENT_NO_CONTEXT_TAKEOVER = 4, // remove this 12 | NO_DELAY = 8, 13 | SLIDING_DEFLATE_WINDOW = 16 14 | }; 15 | 16 | class ExtensionsNegotiator { 17 | protected: 18 | int options; 19 | public: 20 | ExtensionsNegotiator(int wantedOptions); 21 | std::string generateOffer() const; 22 | void readOffer(std::string offer); 23 | int getNegotiatedOptions() const; 24 | }; 25 | } 26 | 27 | #endif // EXTENSIONS_UWS_H 28 | -------------------------------------------------------------------------------- /uWebSockets/src/Group.cpp: -------------------------------------------------------------------------------- 1 | #include "Group.h" 2 | 3 | namespace eioWS { 4 | void Group::setUserData(void *user) { 5 | this->userData = user; 6 | } 7 | 8 | void *Group::getUserData() { 9 | return userData; 10 | } 11 | 12 | void Group::addWebSocket(WebSocket *webSocket) { 13 | if (webSocketHead) { 14 | webSocketHead->prev = webSocket; 15 | webSocket->next = webSocketHead; 16 | } else { 17 | webSocket->next = nullptr; 18 | } 19 | webSocketHead = webSocket; 20 | webSocket->prev = nullptr; 21 | } 22 | 23 | void Group::removeWebSocket(WebSocket *webSocket) { 24 | if (iterators.size()) { 25 | iterators.top() = webSocket->next; 26 | } 27 | if (webSocket->prev == webSocket->next) { 28 | webSocketHead = nullptr; 29 | } else { 30 | if (webSocket->prev) { 31 | (static_cast(webSocket->prev))->next = webSocket->next; 32 | } else { 33 | webSocketHead = static_cast(webSocket->next); 34 | } 35 | if (webSocket->next) { 36 | (static_cast(webSocket->next))->prev = webSocket->prev; 37 | } 38 | } 39 | } 40 | 41 | Group::Group(int extensionOptions, unsigned int maxPayload, Hub *hub, uS::NodeData *nodeData) : 42 | uS::NodeData(*nodeData), 43 | maxPayload(maxPayload), 44 | hub(hub), 45 | extensionOptions(extensionOptions) { 46 | this->extensionOptions |= CLIENT_NO_CONTEXT_TAKEOVER | SERVER_NO_CONTEXT_TAKEOVER; 47 | } 48 | 49 | void Group::onConnection(const std::function &handler) { 50 | connectionHandler = handler; 51 | } 52 | 53 | void Group::onMessage(const std::function &handler) { 54 | messageHandler = handler; 55 | } 56 | 57 | void Group::onDisconnection(const std::function &handler) { 58 | disconnectionHandler = handler; 59 | } 60 | 61 | void Group::close(int code, char *message, size_t length) { 62 | forEach([code, message, length](eioWS::WebSocket *ws) { 63 | ws->close(code, message, length); 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /uWebSockets/src/Group.h: -------------------------------------------------------------------------------- 1 | #ifndef GROUP_UWS_H 2 | #define GROUP_UWS_H 3 | 4 | #include "WebSocket.h" 5 | #include "Extensions.h" 6 | #include 7 | #include 8 | 9 | namespace eioWS { 10 | struct Hub; 11 | 12 | struct Group : protected uS::NodeData { 13 | protected: 14 | friend struct Hub; 15 | friend struct WebSocket; 16 | 17 | std::function connectionHandler = [](WebSocket *) {}; 18 | std::function messageHandler = [](WebSocket *, char *, size_t, OpCode) {}; 19 | std::function disconnectionHandler = [](WebSocket *, int, char *, size_t) {}; 20 | 21 | unsigned int maxPayload; 22 | Hub *hub; 23 | int extensionOptions; 24 | std::stack iterators; 25 | 26 | // todo: cannot be named user, collides with parent! 27 | void *userData = nullptr; 28 | 29 | WebSocket *webSocketHead = nullptr; 30 | 31 | void addWebSocket(WebSocket *webSocket); 32 | void removeWebSocket(WebSocket *webSocket); 33 | 34 | Group(int extensionOptions, unsigned int maxPayload, Hub *hub, uS::NodeData *nodeData); 35 | 36 | public: 37 | void onConnection(const std::function &handler); 38 | void onMessage(const std::function &handler); 39 | void onDisconnection(const std::function &handler); 40 | 41 | void setUserData(void *user); 42 | void *getUserData(); 43 | 44 | void close(int code = 1000, char *message = nullptr, size_t length = 0); 45 | 46 | template 47 | void forEach(const F &cb) { 48 | uS::Poll *iterator = webSocketHead; 49 | iterators.push(iterator); 50 | while (iterator) { 51 | uS::Poll *lastIterator = iterator; 52 | cb(static_cast(iterator)); 53 | iterator = iterators.top(); 54 | if (lastIterator == iterator) { 55 | iterator = ((uS::Socket *) iterator)->next; 56 | iterators.top() = iterator; 57 | } 58 | } 59 | iterators.pop(); 60 | } 61 | 62 | static Group *from(uS::Socket *s) { 63 | return static_cast(s->getNodeData()); 64 | } 65 | }; 66 | } 67 | 68 | #endif // GROUP_UWS_H 69 | -------------------------------------------------------------------------------- /uWebSockets/src/Hub.cpp: -------------------------------------------------------------------------------- 1 | #include "Hub.h" 2 | #include 3 | #include 4 | 5 | namespace eioWS { 6 | z_stream *Hub::allocateDefaultCompressor(z_stream *zStream) { 7 | deflateInit2(zStream, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); 8 | return zStream; 9 | } 10 | 11 | char *Hub::deflate(char *data, size_t &length, z_stream *slidingDeflateWindow) { 12 | dynamicZlibBuffer.clear(); 13 | 14 | z_stream *compressor = slidingDeflateWindow ? slidingDeflateWindow : &deflationStream; 15 | 16 | compressor->next_in = reinterpret_cast(data); 17 | compressor->avail_in = (unsigned int) length; 18 | 19 | // note: zlib requires more than 6 bytes with Z_SYNC_FLUSH 20 | const int DEFLATE_OUTPUT_CHUNK = LARGE_BUFFER_SIZE; 21 | 22 | int err; 23 | do { 24 | compressor->next_out = reinterpret_cast(zlibBuffer); 25 | compressor->avail_out = DEFLATE_OUTPUT_CHUNK; 26 | 27 | err = ::deflate(compressor, Z_SYNC_FLUSH); 28 | if (Z_OK == err && compressor->avail_out == 0) { 29 | dynamicZlibBuffer.append(zlibBuffer, DEFLATE_OUTPUT_CHUNK - compressor->avail_out); 30 | continue; 31 | } else { 32 | break; 33 | } 34 | } while (true); 35 | 36 | // note: should not change avail_out 37 | if (!slidingDeflateWindow) { 38 | deflateReset(compressor); 39 | } 40 | 41 | if (dynamicZlibBuffer.length()) { 42 | dynamicZlibBuffer.append(zlibBuffer, DEFLATE_OUTPUT_CHUNK - compressor->avail_out); 43 | 44 | length = dynamicZlibBuffer.length() - 4; 45 | return reinterpret_cast(dynamicZlibBuffer.data()); 46 | } 47 | 48 | length = DEFLATE_OUTPUT_CHUNK - compressor->avail_out - 4; 49 | return zlibBuffer; 50 | } 51 | 52 | // todo: let's go through this code once more some time! 53 | char *Hub::inflate(char *data, size_t &length, size_t maxPayload) { 54 | dynamicZlibBuffer.clear(); 55 | 56 | inflationStream.next_in = reinterpret_cast(data); 57 | inflationStream.avail_in = (unsigned int) length; 58 | 59 | int err; 60 | do { 61 | inflationStream.next_out = reinterpret_cast(zlibBuffer); 62 | inflationStream.avail_out = LARGE_BUFFER_SIZE; 63 | err = ::inflate(&inflationStream, Z_FINISH); 64 | if (!inflationStream.avail_in) { 65 | break; 66 | } 67 | 68 | dynamicZlibBuffer.append(zlibBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out); 69 | } while (err == Z_BUF_ERROR && dynamicZlibBuffer.length() <= maxPayload); 70 | 71 | inflateReset(&inflationStream); 72 | 73 | if ((err != Z_BUF_ERROR && err != Z_OK) || dynamicZlibBuffer.length() > maxPayload) { 74 | length = 0; 75 | return nullptr; 76 | } 77 | 78 | if (dynamicZlibBuffer.length()) { 79 | dynamicZlibBuffer.append(zlibBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out); 80 | 81 | length = dynamicZlibBuffer.length(); 82 | return reinterpret_cast(dynamicZlibBuffer.data()); 83 | } 84 | 85 | length = LARGE_BUFFER_SIZE - inflationStream.avail_out; 86 | return zlibBuffer; 87 | } 88 | 89 | void Hub::upgrade(uv_os_sock_t fd, const char *secKey, SSL *ssl, const char *extensions, size_t extensionsLength, const char *subprotocol, size_t subprotocolLength, Group *serverGroup) { 90 | if (!serverGroup) { 91 | serverGroup = &getDefaultGroup(); 92 | } 93 | 94 | uS::Socket s((uS::NodeData *) serverGroup, this->getLoop(), fd, ssl); 95 | s.setNoDelay(true); 96 | 97 | bool perMessageDeflate = false; 98 | ExtensionsNegotiator extensionsNegotiator(serverGroup->extensionOptions); 99 | extensionsNegotiator.readOffer(std::string(extensions, extensionsLength)); 100 | std::string extensionsResponse = extensionsNegotiator.generateOffer(); 101 | if (extensionsNegotiator.getNegotiatedOptions() & PERMESSAGE_DEFLATE) { 102 | perMessageDeflate = true; 103 | } 104 | 105 | WebSocket *webSocket = new WebSocket(serverGroup->maxPayload, perMessageDeflate, &s); 106 | webSocket->upgrade(secKey, extensionsResponse, subprotocol, subprotocolLength); 107 | 108 | webSocket->setState(); 109 | webSocket->change(webSocket, webSocket->setPoll(UV_READABLE)); 110 | serverGroup->addWebSocket(webSocket); 111 | serverGroup->connectionHandler(webSocket); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /uWebSockets/src/Hub.h: -------------------------------------------------------------------------------- 1 | #ifndef HUB_UWS_H 2 | #define HUB_UWS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "Group.h" 9 | #include "Node.h" 10 | 11 | namespace eioWS { 12 | struct Hub : protected uS::Node, public Group { 13 | protected: 14 | struct ConnectionData { 15 | std::string path; 16 | void *user; 17 | }; 18 | 19 | static z_stream *allocateDefaultCompressor(z_stream *zStream); 20 | 21 | z_stream inflationStream = {}, deflationStream = {}; 22 | char *deflate(char *data, size_t &length, z_stream *slidingDeflateWindow); 23 | char *inflate(char *data, size_t &length, size_t maxPayload); 24 | char *zlibBuffer; 25 | std::string dynamicZlibBuffer; 26 | static const int LARGE_BUFFER_SIZE = 300 * 1024; 27 | 28 | public: 29 | Group *createGroup(int extensionOptions = 0, unsigned int maxPayload = 16777216) { 30 | return new Group(extensionOptions, maxPayload, this, nodeData); 31 | } 32 | 33 | Group &getDefaultGroup() { 34 | return static_cast(*this); 35 | } 36 | 37 | void upgrade(uv_os_sock_t fd, const char *secKey, SSL *ssl, const char *extensions, size_t extensionsLength, const char *subprotocol, size_t subprotocolLength, Group *serverGroup = nullptr); 38 | 39 | Hub(int extensionOptions = 0, unsigned int maxPayload = 16777216) : 40 | uS::Node(LARGE_BUFFER_SIZE, WebSocketProtocol::CONSUME_PRE_PADDING, WebSocketProtocol::CONSUME_POST_PADDING), 41 | Group(extensionOptions, maxPayload, this, nodeData) { 42 | inflateInit2(&inflationStream, -15); 43 | zlibBuffer = new char[LARGE_BUFFER_SIZE]; 44 | allocateDefaultCompressor(&deflationStream); 45 | } 46 | 47 | ~Hub() { 48 | inflateEnd(&inflationStream); 49 | deflateEnd(&deflationStream); 50 | delete [] zlibBuffer; 51 | } 52 | 53 | using uS::Node::getLoop; 54 | using Group::onConnection; 55 | using Group::onMessage; 56 | using Group::onDisconnection; 57 | 58 | friend struct WebSocket; 59 | }; 60 | } 61 | 62 | #endif // HUB_UWS_H 63 | -------------------------------------------------------------------------------- /uWebSockets/src/Libuv.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBUV_H 2 | #define LIBUV_H 3 | 4 | #include 5 | static_assert (UV_VERSION_MINOR >= 3, "µWebSockets requires libuv >=1.3.0"); 6 | 7 | namespace uS { 8 | struct Loop : uv_loop_t { 9 | static Loop *createLoop() { 10 | return static_cast(uv_default_loop()); 11 | } 12 | }; 13 | 14 | struct Async { 15 | uv_async_t uv_async; 16 | 17 | Async(Loop *loop) { 18 | uv_async.loop = loop; 19 | } 20 | 21 | void start(void (*cb)(Async *)) { 22 | uv_async_init(uv_async.loop, &uv_async, (uv_async_cb) cb); 23 | } 24 | 25 | void send() { 26 | uv_async_send(&uv_async); 27 | } 28 | 29 | void close() { 30 | uv_close(reinterpret_cast(&uv_async), [](uv_handle_t *a) { 31 | delete reinterpret_cast(a); 32 | }); 33 | } 34 | 35 | void setData(void *data) { 36 | uv_async.data = data; 37 | } 38 | 39 | void *getData() { 40 | return uv_async.data; 41 | } 42 | }; 43 | 44 | struct Timer { 45 | uv_timer_t uv_timer; 46 | 47 | Timer(Loop *loop) { 48 | uv_timer_init(loop, &uv_timer); 49 | } 50 | 51 | void start(void (*cb)(Timer *), int first, int repeat) { 52 | uv_timer_start(&uv_timer, (uv_timer_cb) cb, first, repeat); 53 | } 54 | 55 | void setData(void *data) { 56 | uv_timer.data = data; 57 | } 58 | 59 | void *getData() { 60 | return uv_timer.data; 61 | } 62 | 63 | void stop() { 64 | uv_timer_stop(&uv_timer); 65 | } 66 | 67 | void close() { 68 | uv_close(reinterpret_cast(&uv_timer), [](uv_handle_t *t) { 69 | delete reinterpret_cast(t); 70 | }); 71 | } 72 | private: 73 | ~Timer() {} 74 | }; 75 | 76 | struct Poll { 77 | uv_poll_t *uv_poll; 78 | void (*cb)(Poll *p, int status, int events); 79 | 80 | Poll(Loop *loop, uv_os_sock_t fd) { 81 | uv_poll = new uv_poll_t; 82 | uv_poll_init_socket(loop, uv_poll, fd); 83 | cb = nullptr; 84 | } 85 | 86 | Poll(Poll &&other) { 87 | uv_poll = other.uv_poll; 88 | cb = other.cb; 89 | other.uv_poll = nullptr; 90 | } 91 | 92 | Poll(const Poll &other) = delete; 93 | 94 | ~Poll() { 95 | delete uv_poll; 96 | } 97 | 98 | bool isClosed() { 99 | return uv_is_closing(reinterpret_cast(uv_poll)); 100 | } 101 | 102 | uv_os_sock_t getFd() const { 103 | return uv_poll->io_watcher.fd; 104 | } 105 | 106 | void setCb(void (*cb)(Poll *p, int status, int events)) { 107 | this->cb = cb; 108 | } 109 | 110 | void (*getCb())(Poll *, int, int) { 111 | return cb; 112 | } 113 | 114 | void start(Poll *self, int events) { 115 | uv_poll->data = self; 116 | uv_poll_start(uv_poll, events, [](uv_poll_t *p, int status, int events) { 117 | Poll *self = static_cast(p->data); 118 | self->cb(self, status, events); 119 | }); 120 | } 121 | 122 | void change(Poll *self, int events) { 123 | start(self, events); 124 | } 125 | 126 | void stop() { 127 | uv_poll_stop(uv_poll); 128 | } 129 | 130 | void close(void (*cb)(Poll *)) { 131 | this->cb = (void(*)(Poll *, int, int)) cb; 132 | uv_close(reinterpret_cast(uv_poll), [](uv_handle_t *p) { 133 | Poll *poll = static_cast(p->data); 134 | void (*cb)(Poll *) = (void(*)(Poll *)) poll->cb; 135 | cb(poll); 136 | }); 137 | } 138 | }; 139 | } 140 | 141 | #endif // LIBUV_H 142 | -------------------------------------------------------------------------------- /uWebSockets/src/Networking.cpp: -------------------------------------------------------------------------------- 1 | #include "Networking.h" 2 | 3 | namespace uS { 4 | struct Init { 5 | Init() {signal(SIGPIPE, SIG_IGN);} 6 | } init; 7 | } 8 | -------------------------------------------------------------------------------- /uWebSockets/src/Networking.h: -------------------------------------------------------------------------------- 1 | // the purpose of this header should be to provide SSL and networking wrapped in a common interface 2 | // it should allow cross-platform networking and SSL and also easy usage of mTCP and similar tech 3 | #ifndef NETWORKING_UWS_H 4 | #define NETWORKING_UWS_H 5 | 6 | #define SOCKET_ERROR -1 7 | #define INVALID_SOCKET -1 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "Libuv.h" 14 | 15 | namespace uS { 16 | // todo: mark sockets nonblocking in these functions 17 | // todo: probably merge this Context with the TLS::Context for same interface for SSL and non-SSL! 18 | struct Context { 19 | Context() {} 20 | ~Context() {} 21 | 22 | static void closeSocket(uv_os_sock_t fd) { 23 | close(fd); 24 | } 25 | 26 | static bool wouldBlock() { 27 | return errno == EWOULDBLOCK;// || errno == EAGAIN; 28 | } 29 | }; 30 | 31 | struct Socket; 32 | 33 | // NodeData is like a Context, maybe merge them? 34 | struct NodeData { 35 | char *recvBufferMemoryBlock; 36 | char *recvBuffer; 37 | int recvLength; 38 | Loop *loop; 39 | uS::Context *netContext; 40 | void *user = nullptr; 41 | static const int preAllocMaxSize = 8192; 42 | char **preAlloc; 43 | 44 | Async *async = nullptr; 45 | pthread_t tid; 46 | 47 | std::recursive_mutex *asyncMutex; 48 | std::vector transferQueue; 49 | std::vector changePollQueue; 50 | static void asyncCallback(Async *async); 51 | 52 | static int getMemoryBlockIndex(size_t length) { 53 | return static_cast((length >> 4) + static_cast(length & 15)); 54 | } 55 | 56 | char *getSmallMemoryBlock(int index) { 57 | if (preAlloc[index]) { 58 | char *memory = preAlloc[index]; 59 | preAlloc[index] = nullptr; 60 | return memory; 61 | } else { 62 | return new char[index << 4]; 63 | } 64 | } 65 | 66 | void freeSmallMemoryBlock(char *memory, int index) { 67 | if (!preAlloc[index]) { 68 | preAlloc[index] = memory; 69 | } else { 70 | delete [] memory; 71 | } 72 | } 73 | 74 | public: 75 | void clearPendingPollChanges(Poll *p) { 76 | asyncMutex->lock(); 77 | changePollQueue.erase( 78 | std::remove(changePollQueue.begin(), changePollQueue.end(), p), 79 | changePollQueue.end() 80 | ); 81 | asyncMutex->unlock(); 82 | } 83 | }; 84 | } 85 | 86 | #endif // NETWORKING_UWS_H 87 | -------------------------------------------------------------------------------- /uWebSockets/src/Node.cpp: -------------------------------------------------------------------------------- 1 | #include "Node.h" 2 | 3 | namespace uS { 4 | Node::Node(int recvLength, int prePadding, int postPadding) { 5 | nodeData = new NodeData; 6 | nodeData->recvBufferMemoryBlock = new char[recvLength]; 7 | nodeData->recvBuffer = nodeData->recvBufferMemoryBlock + prePadding; 8 | nodeData->recvLength = recvLength - prePadding - postPadding; 9 | 10 | nodeData->tid = pthread_self(); 11 | loop = Loop::createLoop(); 12 | 13 | // each node has a context 14 | nodeData->netContext = new Context(); 15 | nodeData->loop = loop; 16 | 17 | nodeData->asyncMutex = &asyncMutex; 18 | 19 | int indices = NodeData::getMemoryBlockIndex(NodeData::preAllocMaxSize) + 1; 20 | nodeData->preAlloc = new char*[indices]; 21 | for (int i = 0; i < indices; i++) { 22 | nodeData->preAlloc[i] = nullptr; 23 | } 24 | } 25 | 26 | Node::~Node() { 27 | delete [] nodeData->recvBufferMemoryBlock; 28 | 29 | int indices = NodeData::getMemoryBlockIndex(NodeData::preAllocMaxSize) + 1; 30 | for (int i = 0; i < indices; i++) { 31 | if (nodeData->preAlloc[i]) { 32 | delete [] nodeData->preAlloc[i]; 33 | } 34 | } 35 | delete [] nodeData->preAlloc; 36 | delete nodeData->netContext; 37 | delete nodeData; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /uWebSockets/src/Node.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_UWS_H 2 | #define NODE_UWS_H 3 | 4 | #include "Networking.h" 5 | #include 6 | #include 7 | 8 | namespace uS { 9 | class Node { 10 | protected: 11 | Loop *loop; 12 | NodeData *nodeData; 13 | std::recursive_mutex asyncMutex; 14 | 15 | public: 16 | Node(int recvLength = 1024, int prePadding = 0, int postPadding = 0); 17 | ~Node(); 18 | 19 | Loop *getLoop() { 20 | return loop; 21 | } 22 | }; 23 | } 24 | 25 | #endif // NODE_UWS_H 26 | -------------------------------------------------------------------------------- /uWebSockets/src/Socket.cpp: -------------------------------------------------------------------------------- 1 | #include "Socket.h" 2 | 3 | namespace uS { 4 | Socket::Address Socket::getAddress() const { 5 | uv_os_sock_t fd = getFd(); 6 | 7 | sockaddr_storage addr; 8 | socklen_t addrLength = sizeof(addr); 9 | if (getpeername(fd, reinterpret_cast(&addr), &addrLength) == -1) { 10 | return {0, "", ""}; 11 | } 12 | 13 | static __thread char buf[INET6_ADDRSTRLEN]; 14 | 15 | if (addr.ss_family == AF_INET) { 16 | sockaddr_in *ipv4 = reinterpret_cast(&addr); 17 | inet_ntop(AF_INET, &ipv4->sin_addr, buf, sizeof(buf)); 18 | return {ntohs(ipv4->sin_port), buf, "IPv4"}; 19 | } else { 20 | sockaddr_in6 *ipv6 = reinterpret_cast(&addr); 21 | inet_ntop(AF_INET6, &ipv6->sin6_addr, buf, sizeof(buf)); 22 | return {ntohs(ipv6->sin6_port), buf, "IPv6"}; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /uWebSockets/src/Socket.h: -------------------------------------------------------------------------------- 1 | #ifndef SOCKET_UWS_H 2 | #define SOCKET_UWS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "Networking.h" 8 | 9 | namespace uS { 10 | // perfectly 64 bytes (4 + 60) 11 | struct Socket : Poll { 12 | protected: 13 | struct { 14 | int poll : 4; 15 | int shuttingDown : 4; 16 | } state = {0, false}; 17 | 18 | SSL *ssl; 19 | void *user = nullptr; 20 | NodeData *nodeData; 21 | const int HEADER_LENGTH = 10; 22 | 23 | struct Queue { 24 | struct Message { 25 | const char *data; 26 | size_t length; 27 | Message *nextMessage = nullptr; 28 | void (*callback)(void *socket, void *data, bool cancelled, void *reserved) = nullptr; 29 | void *callbackData = nullptr, *reserved = nullptr; 30 | }; 31 | 32 | Message *head = nullptr, *tail = nullptr; 33 | void pop() 34 | { 35 | Message *nextMessage; 36 | if ((nextMessage = head->nextMessage)) { 37 | delete [] reinterpret_cast(head); 38 | head = nextMessage; 39 | } else { 40 | delete [] reinterpret_cast(head); 41 | head = tail = nullptr; 42 | } 43 | } 44 | 45 | bool empty() const {return head == nullptr;} 46 | Message *front() {return head;} 47 | 48 | void push(Message *message) 49 | { 50 | message->nextMessage = nullptr; 51 | if (tail) { 52 | tail->nextMessage = message; 53 | tail = message; 54 | } else { 55 | head = message; 56 | tail = message; 57 | } 58 | } 59 | } messageQueue; 60 | 61 | int getPoll() { 62 | return state.poll; 63 | } 64 | 65 | int setPoll(int poll) { 66 | state.poll = poll; 67 | return poll; 68 | } 69 | 70 | void setShuttingDown(bool shuttingDown) { 71 | state.shuttingDown = shuttingDown; 72 | } 73 | 74 | void changePoll(Socket *socket) { 75 | if (socket->nodeData->tid != pthread_self()) { 76 | socket->nodeData->asyncMutex->lock(); 77 | socket->nodeData->changePollQueue.push_back(socket); 78 | socket->nodeData->asyncMutex->unlock(); 79 | socket->nodeData->async->send(); 80 | } else { 81 | change(socket, socket->getPoll()); 82 | } 83 | } 84 | 85 | // clears user data! 86 | template 87 | void startTimeout(int timeoutMs = 15000) { 88 | Timer *timer = new Timer(nodeData->loop); 89 | timer->setData(this); 90 | timer->start([](Timer *timer) { 91 | Socket *s = static_cast(timer->getData()); 92 | s->cancelTimeout(); 93 | onTimeout(s); 94 | }, timeoutMs, 0); 95 | 96 | user = timer; 97 | } 98 | 99 | void cancelTimeout() { 100 | Timer *timer = static_cast(getUserData()); 101 | if (timer) { 102 | timer->stop(); 103 | timer->close(); 104 | user = nullptr; 105 | } 106 | } 107 | 108 | template 109 | static void sslIoHandler(Poll *p, int status, int events) { 110 | Socket *socket = static_cast(p); 111 | 112 | if (status < 0) { 113 | STATE::onEnd(static_cast(p)); 114 | return; 115 | } 116 | 117 | if (!socket->messageQueue.empty() && ((events & UV_WRITABLE) || SSL_want(socket->ssl) == SSL_READING)) { 118 | while (true) { 119 | Queue::Message *messagePtr = socket->messageQueue.front(); 120 | ssize_t sent = SSL_write(socket->ssl, messagePtr->data, static_cast(messagePtr->length)); 121 | if (sent == (ssize_t) messagePtr->length) { 122 | if (messagePtr->callback) { 123 | messagePtr->callback(p, messagePtr->callbackData, false, messagePtr->reserved); 124 | } 125 | socket->messageQueue.pop(); 126 | if (socket->messageQueue.empty()) { 127 | if ((socket->state.poll & UV_WRITABLE) && SSL_want(socket->ssl) != SSL_WRITING) { 128 | socket->change(socket, socket->setPoll(UV_READABLE)); 129 | } 130 | break; 131 | } 132 | } else if (sent <= 0) { 133 | switch (SSL_get_error(socket->ssl, sent)) { 134 | case SSL_ERROR_WANT_READ: 135 | break; 136 | case SSL_ERROR_WANT_WRITE: 137 | if ((socket->getPoll() & UV_WRITABLE) == 0) { 138 | socket->change(socket, socket->setPoll(socket->getPoll() | UV_WRITABLE)); 139 | } 140 | break; 141 | case SSL_ERROR_SSL: 142 | case SSL_ERROR_SYSCALL: 143 | ERR_clear_error(); 144 | STATE::onEnd(static_cast(p)); 145 | return; 146 | default: 147 | STATE::onEnd(static_cast(p)); 148 | return; 149 | } 150 | break; 151 | } 152 | } 153 | } 154 | 155 | if (events & UV_READABLE) { 156 | do { 157 | int length = SSL_read(socket->ssl, socket->nodeData->recvBuffer, socket->nodeData->recvLength); 158 | if (length <= 0) { 159 | switch (SSL_get_error(socket->ssl, length)) { 160 | case SSL_ERROR_WANT_READ: 161 | break; 162 | case SSL_ERROR_WANT_WRITE: 163 | if ((socket->getPoll() & UV_WRITABLE) == 0) { 164 | socket->change(socket, socket->setPoll(socket->getPoll() | UV_WRITABLE)); 165 | } 166 | break; 167 | case SSL_ERROR_SSL: 168 | case SSL_ERROR_SYSCALL: 169 | ERR_clear_error(); 170 | STATE::onEnd(static_cast(p)); 171 | return; 172 | default: 173 | STATE::onEnd(static_cast(p)); 174 | return; 175 | } 176 | break; 177 | } else { 178 | // Warning: onData can delete the socket! Happens when WebSocket upgrades 179 | socket = STATE::onData(static_cast(p), socket->nodeData->recvBuffer, length); 180 | if (socket->isClosed() || socket->isShuttingDown()) { 181 | return; 182 | } 183 | } 184 | } while (SSL_pending(socket->ssl)); 185 | } 186 | } 187 | 188 | template 189 | static void ioHandler(Poll *p, int status, int events) { 190 | Socket *socket = static_cast(p); 191 | NodeData *nodeData = socket->nodeData; 192 | Context *netContext = nodeData->netContext; 193 | 194 | if (status < 0) { 195 | STATE::onEnd(static_cast(p)); 196 | return; 197 | } 198 | 199 | if (events & UV_WRITABLE) { 200 | if (!socket->messageQueue.empty() && (events & UV_WRITABLE)) { 201 | while (true) { 202 | Queue::Message *messagePtr = socket->messageQueue.front(); 203 | ssize_t sent = ::send(socket->getFd(), messagePtr->data, messagePtr->length, MSG_NOSIGNAL); 204 | if (sent == (ssize_t) messagePtr->length) { 205 | if (messagePtr->callback) { 206 | messagePtr->callback(p, messagePtr->callbackData, false, messagePtr->reserved); 207 | } 208 | socket->messageQueue.pop(); 209 | if (socket->messageQueue.empty()) { 210 | // todo, remove bit, don't set directly 211 | socket->change(socket, socket->setPoll(UV_READABLE)); 212 | break; 213 | } 214 | } else if (sent == SOCKET_ERROR) { 215 | if (!netContext->wouldBlock()) { 216 | STATE::onEnd(static_cast(p)); 217 | return; 218 | } 219 | break; 220 | } else { 221 | messagePtr->length -= sent; 222 | messagePtr->data += sent; 223 | break; 224 | } 225 | } 226 | } 227 | } 228 | 229 | if (events & UV_READABLE) { 230 | int length = static_cast(recv(socket->getFd(), nodeData->recvBuffer, nodeData->recvLength, 0)); 231 | if (length > 0) { 232 | STATE::onData(static_cast(p), nodeData->recvBuffer, length); 233 | } else if (length <= 0 || (length == SOCKET_ERROR && !netContext->wouldBlock())) { 234 | STATE::onEnd(static_cast(p)); 235 | } 236 | } 237 | } 238 | 239 | template 240 | void setState() { 241 | if (ssl) { 242 | setCb(sslIoHandler); 243 | } else { 244 | setCb(ioHandler); 245 | } 246 | } 247 | 248 | bool hasEmptyQueue() const { 249 | return messageQueue.empty(); 250 | } 251 | 252 | void enqueue(Queue::Message *message) { 253 | messageQueue.push(message); 254 | } 255 | 256 | Queue::Message *allocMessage(size_t length, const char *data = 0) { 257 | Queue::Message *messagePtr = (Queue::Message *) new char[sizeof(Queue::Message) + length]; 258 | messagePtr->length = length; 259 | messagePtr->data = (reinterpret_cast(messagePtr)) + sizeof(Queue::Message); 260 | messagePtr->nextMessage = nullptr; 261 | 262 | if (data) { 263 | memcpy(const_cast(messagePtr->data), data, messagePtr->length); 264 | } 265 | 266 | return messagePtr; 267 | } 268 | 269 | static void freeMessage(Queue::Message *message) { 270 | delete [] reinterpret_cast(message); 271 | } 272 | 273 | bool write(Queue::Message *message, bool &waiting) { 274 | 275 | if (messageQueue.empty()) { 276 | ssize_t sent = 0; 277 | if (ssl) { 278 | sent = SSL_write(ssl, message->data, static_cast(message->length)); 279 | if (sent == (ssize_t) message->length) { 280 | waiting = false; 281 | return true; 282 | } else if (sent < 0) { 283 | switch (SSL_get_error(ssl, static_cast(sent))) { 284 | case SSL_ERROR_WANT_READ: 285 | break; 286 | case SSL_ERROR_WANT_WRITE: 287 | if ((getPoll() & UV_WRITABLE) == 0) { 288 | setPoll(getPoll() | UV_WRITABLE); 289 | changePoll(this); 290 | } 291 | break; 292 | case SSL_ERROR_SSL: 293 | case SSL_ERROR_SYSCALL: 294 | ERR_clear_error(); 295 | return false; 296 | default: 297 | return false; 298 | } 299 | } 300 | } else { 301 | sent = ::send(getFd(), message->data, message->length, MSG_NOSIGNAL); 302 | if (sent == (ssize_t) message->length) { 303 | waiting = false; 304 | return true; 305 | } else if (sent == SOCKET_ERROR) { 306 | if (!nodeData->netContext->wouldBlock()) { 307 | return false; 308 | } 309 | } else { 310 | message->length -= sent; 311 | message->data += sent; 312 | } 313 | 314 | if ((getPoll() & UV_WRITABLE) == 0) { 315 | setPoll(getPoll() | UV_WRITABLE); 316 | changePoll(this); 317 | } 318 | } 319 | } 320 | messageQueue.push(message); 321 | waiting = true; 322 | return true; 323 | } 324 | 325 | template 326 | void sendTransformed(const char *message, size_t length, void(*callback)(void *socket, void *data, bool cancelled, void *reserved), void *callbackData, D transformData) { 327 | size_t estimatedLength = length + HEADER_LENGTH + sizeof(Queue::Message); 328 | 329 | if (hasEmptyQueue()) { 330 | if (estimatedLength <= uS::NodeData::preAllocMaxSize) { 331 | int memoryLength = static_cast(estimatedLength); 332 | int memoryIndex = nodeData->getMemoryBlockIndex(memoryLength); 333 | 334 | Queue::Message *messagePtr = (Queue::Message *) nodeData->getSmallMemoryBlock(memoryIndex); 335 | messagePtr->data = (reinterpret_cast(messagePtr)) + sizeof(Queue::Message); 336 | messagePtr->length = T::transform(message, const_cast(messagePtr->data), length, transformData); 337 | 338 | bool waiting; 339 | if (write(messagePtr, waiting)) { 340 | if (!waiting) { 341 | nodeData->freeSmallMemoryBlock(reinterpret_cast(messagePtr), memoryIndex); 342 | if (callback) { 343 | callback(this, callbackData, false, nullptr); 344 | } 345 | } else { 346 | messagePtr->callback = callback; 347 | messagePtr->callbackData = callbackData; 348 | } 349 | } else { 350 | nodeData->freeSmallMemoryBlock(reinterpret_cast(messagePtr), memoryIndex); 351 | if (callback) { 352 | callback(this, callbackData, true, nullptr); 353 | } 354 | } 355 | } else { 356 | Queue::Message *messagePtr = allocMessage(estimatedLength - sizeof(Queue::Message)); 357 | messagePtr->length = T::transform(message, const_cast(messagePtr->data), length, transformData); 358 | 359 | bool waiting; 360 | if (write(messagePtr, waiting)) { 361 | if (!waiting) { 362 | freeMessage(messagePtr); 363 | if (callback) { 364 | callback(this, callbackData, false, nullptr); 365 | } 366 | } else { 367 | messagePtr->callback = callback; 368 | messagePtr->callbackData = callbackData; 369 | } 370 | } else { 371 | freeMessage(messagePtr); 372 | if (callback) { 373 | callback(this, callbackData, true, nullptr); 374 | } 375 | } 376 | } 377 | } else { 378 | Queue::Message *messagePtr = allocMessage(estimatedLength - sizeof(Queue::Message)); 379 | messagePtr->length = T::transform(message, const_cast(messagePtr->data), length, transformData); 380 | messagePtr->callback = callback; 381 | messagePtr->callbackData = callbackData; 382 | enqueue(messagePtr); 383 | } 384 | } 385 | 386 | public: 387 | Socket(NodeData *nodeData, Loop *loop, uv_os_sock_t fd, SSL *ssl) : Poll(loop, fd), ssl(ssl), nodeData(nodeData) { 388 | if (ssl) { 389 | // OpenSSL treats SOCKETs as int 390 | SSL_set_fd(ssl, static_cast(fd)); 391 | SSL_set_mode(ssl, SSL_MODE_RELEASE_BUFFERS); 392 | } 393 | } 394 | 395 | NodeData *getNodeData() { 396 | return nodeData; 397 | } 398 | 399 | Poll *next = nullptr, *prev = nullptr; 400 | 401 | void *getUserData() { 402 | return user; 403 | } 404 | 405 | void setUserData(void *user) { 406 | this->user = user; 407 | } 408 | 409 | struct Address { 410 | unsigned int port; 411 | const char *address; 412 | const char *family; 413 | }; 414 | 415 | Address getAddress() const; 416 | 417 | void setNoDelay(int enable) const { 418 | setsockopt(getFd(), IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); 419 | } 420 | 421 | void shutdown() { 422 | if (ssl) { 423 | //todo: poll in/out - have the io_cb recall shutdown if failed 424 | SSL_shutdown(ssl); 425 | } else { 426 | ::shutdown(getFd(), SHUT_WR); 427 | } 428 | } 429 | 430 | template 431 | void closeSocket() { 432 | uv_os_sock_t fd = getFd(); 433 | Context *netContext = nodeData->netContext; 434 | stop(); 435 | netContext->closeSocket(fd); 436 | 437 | if (ssl) { 438 | SSL_free(ssl); 439 | } 440 | 441 | Poll::close([](Poll *p) { 442 | delete reinterpret_cast(p); 443 | }); 444 | } 445 | 446 | bool isShuttingDown() { 447 | return state.shuttingDown; 448 | } 449 | 450 | friend class Node; 451 | friend struct NodeData; 452 | }; 453 | } 454 | 455 | #endif // SOCKET_UWS_H 456 | -------------------------------------------------------------------------------- /uWebSockets/src/WebSocket.cpp: -------------------------------------------------------------------------------- 1 | #include "WebSocket.h" 2 | #include "Hub.h" 3 | #include 4 | #include 5 | #include 6 | 7 | namespace eioWS { 8 | WebSocket::WebSocket(unsigned int maxP, bool perMessageDeflate, uS::Socket *socket) : 9 | uS::Socket(std::move(*socket)) { 10 | maxPayload = maxP; 11 | compressionStatus = perMessageDeflate ? CompressionStatus::ENABLED : CompressionStatus::DISABLED; 12 | 13 | // if we are created in a group with sliding deflate window allocate it here 14 | if (Group::from(this)->extensionOptions & SLIDING_DEFLATE_WINDOW) { 15 | slidingDeflateWindow = Hub::allocateDefaultCompressor(new z_stream{}); 16 | } 17 | } 18 | 19 | /* 20 | * Frames and sends a WebSocket message. 21 | * 22 | * Hints: Consider using any of the prepare function if any of their 23 | * use cases match what you are trying to achieve (pub/sub, broadcast) 24 | * 25 | */ 26 | void WebSocket::send(const char *message, size_t length, OpCode opCode, void(*callback)(WebSocket *webSocket, void *data, bool cancelled, void *reserved), void *callbackData, bool compress) { 27 | struct TransformData { 28 | OpCode opCode; 29 | bool compress; 30 | WebSocket *s; 31 | } transformData = {opCode, compress && compressionStatus == WebSocket::CompressionStatus::ENABLED && opCode < 3, this}; 32 | 33 | struct WebSocketTransformer { 34 | static size_t transform(const char *src, char *dst, size_t length, TransformData transformData) { 35 | if (transformData.compress) { 36 | char *deflated = Group::from(transformData.s)->hub->deflate(const_cast(src), length, reinterpret_cast(transformData.s->slidingDeflateWindow)); 37 | return WebSocketProtocol::formatMessage(dst, deflated, length, transformData.opCode, length, true); 38 | } 39 | return WebSocketProtocol::formatMessage(dst, src, length, transformData.opCode, length, false); 40 | } 41 | }; 42 | 43 | sendTransformed(const_cast(message), length, (void(*)(void *, void *, bool, void *)) callback, callbackData, transformData); 44 | } 45 | 46 | uS::Socket *WebSocket::onData(uS::Socket *s, char *data, size_t length) { 47 | WebSocket *webSocket = static_cast(s); 48 | 49 | webSocket->hasOutstandingPong = false; 50 | if (!webSocket->isShuttingDown()) { 51 | WebSocketProtocol::consume(data, (unsigned int) length, webSocket); 52 | } 53 | 54 | return webSocket; 55 | } 56 | 57 | /* 58 | * Immediately terminates this WebSocket. Will call onDisconnection of its Group. 59 | * 60 | * Hints: Close code will be 1006 and message will be empty. 61 | * 62 | */ 63 | void WebSocket::terminate() { 64 | WebSocket::onEnd(this); 65 | } 66 | 67 | /* 68 | * Immediately calls onDisconnection of its Group and begins a passive 69 | * WebSocket closedown handshake in the background (might succeed or not, 70 | * we don't care). 71 | * 72 | * Hints: Close code and message will be what you pass yourself. 73 | * 74 | */ 75 | 76 | void WebSocket::close(int code, const char *message, size_t length) { 77 | static const int MAX_CLOSE_PAYLOAD = 123; 78 | if (isShuttingDown()) { 79 | return; 80 | } 81 | setShuttingDown(true); 82 | length = std::min(MAX_CLOSE_PAYLOAD, length); 83 | Group::from(this)->removeWebSocket(this); 84 | Group::from(this)->disconnectionHandler(this, code, const_cast(message), length); 85 | 86 | 87 | startTimeout(); 88 | 89 | char closePayload[MAX_CLOSE_PAYLOAD + 2]; 90 | int closePayloadLength = static_cast(WebSocketProtocol::formatClosePayload(closePayload, code, message, length)); 91 | send(closePayload, closePayloadLength, OpCode::CLOSE, [](WebSocket *p, void *data, bool cancelled, void *reserved) { 92 | if (!cancelled) { 93 | p->shutdown(); 94 | } 95 | }); 96 | } 97 | 98 | void WebSocket::onEnd(uS::Socket *s) { 99 | WebSocket *webSocket = static_cast(s); 100 | if (!webSocket->isShuttingDown()) { 101 | Group::from(webSocket)->removeWebSocket(webSocket); 102 | Group::from(webSocket)->disconnectionHandler(webSocket, 1006, nullptr, 0); 103 | } else { 104 | webSocket->cancelTimeout(); 105 | } 106 | 107 | webSocket->template closeSocket(); 108 | 109 | while (!webSocket->messageQueue.empty()) { 110 | Queue::Message *message = webSocket->messageQueue.front(); 111 | if (message->callback) { 112 | message->callback(nullptr, message->callbackData, true, nullptr); 113 | } 114 | webSocket->messageQueue.pop(); 115 | } 116 | 117 | webSocket->nodeData->clearPendingPollChanges(webSocket); 118 | 119 | // remove any per-websocket zlib memory 120 | if (webSocket->slidingDeflateWindow) { 121 | // this relates to Hub::allocateDefaultCompressor 122 | deflateEnd(reinterpret_cast(webSocket->slidingDeflateWindow)); 123 | delete reinterpret_cast(webSocket->slidingDeflateWindow); 124 | webSocket->slidingDeflateWindow = nullptr; 125 | } 126 | } 127 | 128 | 129 | bool WebSocket::handleFragment(char *data, size_t length, unsigned int remainingBytes, int opCode, bool fin, WebSocketState *webSocketState) { 130 | WebSocket *webSocket = static_cast(webSocketState); 131 | Group *group = Group::from(webSocket); 132 | 133 | if (opCode < 3) { 134 | if (!remainingBytes && fin && !webSocket->fragmentBuffer.length()) { 135 | if (webSocket->compressionStatus == WebSocket::CompressionStatus::COMPRESSED_FRAME) { 136 | webSocket->compressionStatus = WebSocket::CompressionStatus::ENABLED; 137 | data = group->hub->inflate(data, length, group->maxPayload); 138 | if (!data) { 139 | forceClose(webSocketState); 140 | return true; 141 | } 142 | } 143 | 144 | if (opCode == 1 && !WebSocketProtocol::isValidUtf8((unsigned char *) data, length)) { 145 | forceClose(webSocketState); 146 | return true; 147 | } 148 | 149 | group->messageHandler(webSocket, data, length, (OpCode) opCode); 150 | if (webSocket->isClosed() || webSocket->isShuttingDown()) { 151 | return true; 152 | } 153 | } else { 154 | webSocket->fragmentBuffer.append(data, length); 155 | if (!remainingBytes && fin) { 156 | length = webSocket->fragmentBuffer.length(); 157 | if (webSocket->compressionStatus == WebSocket::CompressionStatus::COMPRESSED_FRAME) { 158 | webSocket->compressionStatus = WebSocket::CompressionStatus::ENABLED; 159 | webSocket->fragmentBuffer.append("...."); 160 | data = group->hub->inflate(reinterpret_cast(webSocket->fragmentBuffer.data()), length, group->maxPayload); 161 | if (!data) { 162 | forceClose(webSocketState); 163 | return true; 164 | } 165 | } else { 166 | data = reinterpret_cast(webSocket->fragmentBuffer.data()); 167 | } 168 | 169 | if (opCode == 1 && !WebSocketProtocol::isValidUtf8((unsigned char *) data, length)) { 170 | forceClose(webSocketState); 171 | return true; 172 | } 173 | 174 | group->messageHandler(webSocket, data, length, (OpCode) opCode); 175 | if (webSocket->isClosed() || webSocket->isShuttingDown()) { 176 | return true; 177 | } 178 | webSocket->fragmentBuffer.clear(); 179 | } 180 | } 181 | } else { 182 | if (!remainingBytes && fin && !webSocket->controlTipLength) { 183 | if (opCode == CLOSE) { 184 | typename WebSocketProtocol::CloseFrame closeFrame = WebSocketProtocol::parseClosePayload(data, length); 185 | webSocket->close(closeFrame.code, closeFrame.message, closeFrame.length); 186 | return true; 187 | } else { 188 | if (opCode == PING) { 189 | webSocket->send(data, length, (OpCode) OpCode::PONG); 190 | if (webSocket->isClosed() || webSocket->isShuttingDown()) { 191 | return true; 192 | } 193 | } else if (opCode == PONG) { 194 | if (webSocket->isClosed() || webSocket->isShuttingDown()) { 195 | return true; 196 | } 197 | } 198 | } 199 | } else { 200 | webSocket->fragmentBuffer.append(data, length); 201 | webSocket->controlTipLength += length; 202 | 203 | if (!remainingBytes && fin) { 204 | char *controlBuffer = reinterpret_cast(webSocket->fragmentBuffer.data()) + webSocket->fragmentBuffer.length() - webSocket->controlTipLength; 205 | if (opCode == CLOSE) { 206 | typename WebSocketProtocol::CloseFrame closeFrame = WebSocketProtocol::parseClosePayload(controlBuffer, webSocket->controlTipLength); 207 | webSocket->close(closeFrame.code, closeFrame.message, closeFrame.length); 208 | return true; 209 | } else { 210 | if (opCode == PING) { 211 | webSocket->send(controlBuffer, webSocket->controlTipLength, (OpCode) OpCode::PONG); 212 | if (webSocket->isClosed() || webSocket->isShuttingDown()) { 213 | return true; 214 | } 215 | } else if (opCode == PONG) { 216 | if (webSocket->isClosed() || webSocket->isShuttingDown()) { 217 | return true; 218 | } 219 | } 220 | } 221 | 222 | webSocket->fragmentBuffer.resize(webSocket->fragmentBuffer.length() - webSocket->controlTipLength); 223 | webSocket->controlTipLength = 0; 224 | } 225 | } 226 | } 227 | 228 | return false; 229 | } 230 | 231 | static void base64(unsigned char *src, char *dst) { 232 | static const char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 233 | for (int i = 0; i < 18; i += 3) { 234 | *dst++ = b64[(src[i] >> 2) & 63]; 235 | *dst++ = b64[((src[i] & 3) << 4) | ((src[i + 1] & 240) >> 4)]; 236 | *dst++ = b64[((src[i + 1] & 15) << 2) | ((src[i + 2] & 192) >> 6)]; 237 | *dst++ = b64[src[i + 2] & 63]; 238 | } 239 | *dst++ = b64[(src[18] >> 2) & 63]; 240 | *dst++ = b64[((src[18] & 3) << 4) | ((src[19] & 240) >> 4)]; 241 | *dst++ = b64[((src[19] & 15) << 2)]; 242 | *dst++ = '='; 243 | } 244 | 245 | void WebSocket::upgrade(const char *secKey, const std::string& extensionsResponse, const char *subprotocol, size_t subprotocolLength) { 246 | Queue::Message *messagePtr; 247 | 248 | unsigned char shaInput[] = "XXXXXXXXXXXXXXXXXXXXXXXX258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 249 | memcpy(shaInput, secKey, 24); 250 | unsigned char shaDigest[SHA_DIGEST_LENGTH]; 251 | SHA1(shaInput, sizeof(shaInput) - 1, shaDigest); 252 | 253 | char upgradeBuffer[1024]; 254 | memcpy(upgradeBuffer, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ", 97); 255 | base64(shaDigest, upgradeBuffer + 97); 256 | memcpy(upgradeBuffer + 125, "\r\n", 2); 257 | size_t upgradeResponseLength = 127; 258 | 259 | if (extensionsResponse.length() && extensionsResponse.length() < 200) { 260 | memcpy(upgradeBuffer + upgradeResponseLength, "Sec-WebSocket-Extensions: ", 26); 261 | memcpy(upgradeBuffer + upgradeResponseLength + 26, extensionsResponse.data(), extensionsResponse.length()); 262 | memcpy(upgradeBuffer + upgradeResponseLength + 26 + extensionsResponse.length(), "\r\n", 2); 263 | upgradeResponseLength += 26 + extensionsResponse.length() + 2; 264 | } 265 | // select first protocol 266 | for (unsigned int i = 0; i < subprotocolLength; i++) { 267 | if (subprotocol[i] == ',') { 268 | subprotocolLength = i; 269 | break; 270 | } 271 | } 272 | if (subprotocolLength && subprotocolLength < 200) { 273 | memcpy(upgradeBuffer + upgradeResponseLength, "Sec-WebSocket-Protocol: ", 24); 274 | memcpy(upgradeBuffer + upgradeResponseLength + 24, subprotocol, subprotocolLength); 275 | memcpy(upgradeBuffer + upgradeResponseLength + 24 + subprotocolLength, "\r\n", 2); 276 | upgradeResponseLength += 24 + subprotocolLength + 2; 277 | } 278 | memcpy(upgradeBuffer + upgradeResponseLength, "\r\n", 2); 279 | upgradeResponseLength += 2; 280 | 281 | messagePtr = allocMessage(upgradeResponseLength, upgradeBuffer); 282 | 283 | bool waiting; 284 | if (write(messagePtr, waiting)) { 285 | if (!waiting) { 286 | freeMessage(messagePtr); 287 | } else { 288 | messagePtr->callback = nullptr; 289 | } 290 | } else { 291 | freeMessage(messagePtr); 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /uWebSockets/src/WebSocket.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBSOCKET_UWS_H 2 | #define WEBSOCKET_UWS_H 3 | 4 | #include 5 | #include "WebSocketProtocol.h" 6 | #include "Socket.h" 7 | 8 | namespace eioWS { 9 | struct Group; 10 | 11 | struct WebSocket : uS::Socket, WebSocketState { 12 | protected: 13 | unsigned int maxPayload; 14 | std::string fragmentBuffer; 15 | enum CompressionStatus : char { 16 | DISABLED, 17 | ENABLED, 18 | COMPRESSED_FRAME 19 | } compressionStatus; 20 | unsigned char controlTipLength = 0, hasOutstandingPong = false; 21 | 22 | void *slidingDeflateWindow = nullptr; 23 | 24 | WebSocket(unsigned int maxP, bool perMessageDeflate, uS::Socket *socket); 25 | 26 | static uS::Socket *onData(uS::Socket *s, char *data, size_t length); 27 | static void onEnd(uS::Socket *s); 28 | using uS::Socket::closeSocket; 29 | 30 | static bool refusePayloadLength(uint64_t length, WebSocketState *webSocketState) { 31 | WebSocket *webSocket = static_cast(webSocketState); 32 | return length > webSocket->maxPayload; 33 | } 34 | 35 | static bool setCompressed(WebSocketState *webSocketState) { 36 | WebSocket *webSocket = static_cast(webSocketState); 37 | 38 | if (webSocket->compressionStatus == WebSocket::CompressionStatus::ENABLED) { 39 | webSocket->compressionStatus = WebSocket::CompressionStatus::COMPRESSED_FRAME; 40 | return true; 41 | } else { 42 | return false; 43 | } 44 | } 45 | 46 | static void forceClose(WebSocketState *webSocketState) { 47 | WebSocket *webSocket = static_cast(webSocketState); 48 | webSocket->terminate(); 49 | } 50 | 51 | static bool handleFragment(char *data, size_t length, unsigned int remainingBytes, int opCode, bool fin, WebSocketState *webSocketState); 52 | 53 | void upgrade(const char *secKey, const std::string& extensionsResponse, const char *subprotocol, size_t subprotocolLength); 54 | 55 | public: 56 | struct PreparedMessage { 57 | char *buffer; 58 | size_t length; 59 | int references; 60 | void(*callback)(void *webSocket, void *data, bool cancelled, void *reserved); 61 | }; 62 | 63 | void close(int code = 1000, const char *message = nullptr, size_t length = 0); 64 | void terminate(); 65 | void ping(const char *message) {send(message, OpCode::PING);} 66 | void send(const char *message, OpCode opCode = OpCode::TEXT) {send(message, strlen(message), opCode);} 67 | void send(const char *message, size_t length, OpCode opCode, void(*callback)(WebSocket *webSocket, void *data, bool cancelled, void *reserved) = nullptr, void *callbackData = nullptr, bool compress = false); 68 | 69 | friend struct Hub; 70 | friend struct Group; 71 | friend struct uS::Socket; 72 | friend class WebSocketProtocol; 73 | }; 74 | } 75 | 76 | #endif // WEBSOCKET_UWS_H 77 | -------------------------------------------------------------------------------- /uWebSockets/src/WebSocketProtocol.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBSOCKETPROTOCOL_UWS_H 2 | #define WEBSOCKETPROTOCOL_UWS_H 3 | 4 | #include 5 | #include 6 | #ifdef __APPLE__ 7 | #include 8 | #define htobe64(x) OSSwapHostToBigInt64(x) 9 | #define be64toh(x) OSSwapBigToHostInt64(x) 10 | #endif 11 | 12 | namespace eioWS { 13 | enum OpCode : unsigned char { 14 | NONE = 0, 15 | TEXT = 1, 16 | BINARY = 2, 17 | CLOSE = 8, 18 | PING = 9, 19 | PONG = 10 20 | }; 21 | 22 | // 24 bytes perfectly 23 | struct WebSocketState { 24 | public: 25 | static const unsigned int SHORT_MESSAGE_HEADER = 6; 26 | static const unsigned int MEDIUM_MESSAGE_HEADER = 8; 27 | static const unsigned int LONG_MESSAGE_HEADER = 14; 28 | 29 | // 16 bytes 30 | struct State { 31 | unsigned int wantsHead : 1; 32 | unsigned int spillLength : 4; 33 | int opStack : 2; // -1, 0, 1 34 | unsigned int lastFin : 1; 35 | 36 | // 15 bytes 37 | unsigned char spill[LONG_MESSAGE_HEADER - 1] = { 0 }; 38 | OpCode opCode[2] = { NONE }; 39 | 40 | State() { 41 | wantsHead = true; 42 | spillLength = 0; 43 | opStack = -1; 44 | lastFin = true; 45 | } 46 | 47 | } state; 48 | 49 | // 8 bytes 50 | unsigned int remainingBytes = 0; 51 | char mask[4]; 52 | }; 53 | 54 | template 55 | class WebSocketProtocol { 56 | public: 57 | static const unsigned int SHORT_MESSAGE_HEADER = 6; 58 | static const unsigned int MEDIUM_MESSAGE_HEADER = 8; 59 | static const unsigned int LONG_MESSAGE_HEADER = 14; 60 | 61 | protected: 62 | static inline bool isFin(char *frame) {return *((unsigned char *) frame) & 128;} 63 | static inline unsigned char getOpCode(char *frame) {return *((unsigned char *) frame) & 15;} 64 | static inline unsigned char payloadLength(char *frame) {return ((unsigned char *) frame)[1] & 127;} 65 | static inline bool rsv23(char *frame) {return *((unsigned char *) frame) & 48;} 66 | static inline bool rsv1(char *frame) {return *((unsigned char *) frame) & 64;} 67 | 68 | static inline void unmaskImprecise(char *dst, char *src, char *mask, unsigned int length) { 69 | for (unsigned int n = (length >> 2) + 1; n; n--) { 70 | *(dst++) = *(src++) ^ mask[0]; 71 | *(dst++) = *(src++) ^ mask[1]; 72 | *(dst++) = *(src++) ^ mask[2]; 73 | *(dst++) = *(src++) ^ mask[3]; 74 | } 75 | } 76 | 77 | static inline void unmaskImpreciseCopyMask(char *dst, char *src, char *maskPtr, unsigned int length) { 78 | char mask[4] = {maskPtr[0], maskPtr[1], maskPtr[2], maskPtr[3]}; 79 | unmaskImprecise(dst, src, mask, length); 80 | } 81 | 82 | static inline void rotateMask(unsigned int offset, char *mask) { 83 | char originalMask[4] = {mask[0], mask[1], mask[2], mask[3]}; 84 | mask[(0 + offset) & 3] = originalMask[0]; 85 | mask[(1 + offset) & 3] = originalMask[1]; 86 | mask[(2 + offset) & 3] = originalMask[2]; 87 | mask[(3 + offset) & 3] = originalMask[3]; 88 | } 89 | 90 | static inline void unmaskInplace(char *data, char *stop, char *mask) { 91 | while (data < stop) { 92 | *(data++) ^= mask[0]; 93 | *(data++) ^= mask[1]; 94 | *(data++) ^= mask[2]; 95 | *(data++) ^= mask[3]; 96 | } 97 | } 98 | 99 | enum { 100 | SND_COMPRESSED = 64 101 | }; 102 | 103 | template 104 | static inline bool consumeMessage(T payLength, char *&src, unsigned int &length, WebSocketState *wState) { 105 | if (getOpCode(src)) { 106 | if (wState->state.opStack == 1 || (!wState->state.lastFin && getOpCode(src) < 2)) { 107 | Impl::forceClose(wState); 108 | return true; 109 | } 110 | wState->state.opCode[++wState->state.opStack] = (OpCode) getOpCode(src); 111 | } else if (wState->state.opStack == -1) { 112 | Impl::forceClose(wState); 113 | return true; 114 | } 115 | wState->state.lastFin = isFin(src); 116 | 117 | if (Impl::refusePayloadLength(payLength, wState)) { 118 | Impl::forceClose(wState); 119 | return true; 120 | } 121 | 122 | if (payLength + MESSAGE_HEADER <= length) { 123 | unmaskImpreciseCopyMask(src + MESSAGE_HEADER - 4, src + MESSAGE_HEADER, src + MESSAGE_HEADER - 4, (unsigned int) payLength); 124 | if (Impl::handleFragment(src + MESSAGE_HEADER - 4, payLength, 0, wState->state.opCode[wState->state.opStack], isFin(src), wState)) { 125 | return true; 126 | } 127 | 128 | if (isFin(src)) { 129 | wState->state.opStack--; 130 | } 131 | 132 | src += payLength + MESSAGE_HEADER; 133 | length -= payLength + MESSAGE_HEADER; 134 | wState->state.spillLength = 0; 135 | return false; 136 | } else { 137 | wState->state.spillLength = 0; 138 | wState->state.wantsHead = false; 139 | wState->remainingBytes = (unsigned int) (payLength - length + MESSAGE_HEADER); 140 | bool fin = isFin(src); 141 | memcpy(wState->mask, src + MESSAGE_HEADER - 4, 4); 142 | unmaskImprecise(src, src + MESSAGE_HEADER, wState->mask, length - MESSAGE_HEADER); 143 | rotateMask(4 - ((length - MESSAGE_HEADER) & 3), wState->mask); 144 | Impl::handleFragment(src, length - MESSAGE_HEADER, wState->remainingBytes, wState->state.opCode[wState->state.opStack], fin, wState); 145 | return true; 146 | } 147 | } 148 | 149 | static inline bool consumeContinuation(char *&src, unsigned int &length, WebSocketState *wState) { 150 | if (wState->remainingBytes <= length) { 151 | int n = wState->remainingBytes >> 2; 152 | unmaskInplace(src, src + n * 4, wState->mask); 153 | for (int i = 0, s = wState->remainingBytes & 3; i < s; i++) { 154 | src[n * 4 + i] ^= wState->mask[i]; 155 | } 156 | 157 | if (Impl::handleFragment(src, wState->remainingBytes, 0, wState->state.opCode[wState->state.opStack], wState->state.lastFin, wState)) { 158 | return false; 159 | } 160 | 161 | if (wState->state.lastFin) { 162 | wState->state.opStack--; 163 | } 164 | 165 | src += wState->remainingBytes; 166 | length -= wState->remainingBytes; 167 | wState->state.wantsHead = true; 168 | return true; 169 | } else { 170 | unmaskInplace(src, src + ((length >> 2) + 1) * 4, wState->mask); 171 | 172 | wState->remainingBytes -= length; 173 | if (Impl::handleFragment(src, length, wState->remainingBytes, wState->state.opCode[wState->state.opStack], wState->state.lastFin, wState)) { 174 | return false; 175 | } 176 | 177 | if (length & 3) { 178 | rotateMask(4 - (length & 3), wState->mask); 179 | } 180 | return false; 181 | } 182 | } 183 | 184 | public: 185 | WebSocketProtocol() { 186 | 187 | } 188 | 189 | // Based on utf8_check.c by Markus Kuhn, 2005 190 | // https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c 191 | // Optimized for predominantly 7-bit content by Alex Hultman, 2016 192 | // Licensed as Zlib, like the rest of this project 193 | static bool isValidUtf8(unsigned char *s, size_t length) { 194 | for (unsigned char *e = s + length; s != e; ) { 195 | if (s + 4 <= e && ((*reinterpret_cast(s)) & 0x80808080) == 0) { 196 | s += 4; 197 | } else { 198 | while (!(*s & 0x80)) { 199 | if (++s == e) { 200 | return true; 201 | } 202 | } 203 | 204 | if ((s[0] & 0x60) == 0x40) { 205 | if (s + 1 >= e || (s[1] & 0xc0) != 0x80 || (s[0] & 0xfe) == 0xc0) { 206 | return false; 207 | } 208 | s += 2; 209 | } else if ((s[0] & 0xf0) == 0xe0) { 210 | if (s + 2 >= e || (s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 || 211 | (s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || (s[0] == 0xed && (s[1] & 0xe0) == 0xa0)) { 212 | return false; 213 | } 214 | s += 3; 215 | } else if ((s[0] & 0xf8) == 0xf0) { 216 | if (s + 3 >= e || (s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 || (s[3] & 0xc0) != 0x80 || 217 | (s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || (s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4) { 218 | return false; 219 | } 220 | s += 4; 221 | } else { 222 | return false; 223 | } 224 | } 225 | } 226 | return true; 227 | } 228 | 229 | struct CloseFrame { 230 | uint16_t code; 231 | char const *message; 232 | size_t length; 233 | }; 234 | 235 | static inline CloseFrame parseClosePayload(char *src, size_t length) { 236 | CloseFrame cf = {1005, "", 0}; 237 | 238 | if (length >= 2) { 239 | memcpy(&cf.code, src, 2); 240 | cf = {ntohs(cf.code), src + 2, length - 2}; 241 | if (cf.code < 1000 || cf.code > 4999 || (cf.code > 1011 && cf.code < 4000) || 242 | (cf.code >= 1004 && cf.code <= 1006) || !isValidUtf8((unsigned char *) cf.message, cf.length)) { 243 | return {1006, "", 0}; 244 | } 245 | } 246 | return cf; 247 | } 248 | 249 | static inline size_t formatClosePayload(char *dst, uint16_t code, const char *message, size_t length) { 250 | if (code && code != 1005 && code != 1006) { 251 | code = htons(code); 252 | memcpy(dst, &code, 2); 253 | if (message) { 254 | memcpy(dst + 2, message, length); 255 | } 256 | return length + 2; 257 | } 258 | return 0; 259 | } 260 | 261 | static inline size_t formatMessage(char *dst, const char *src, size_t length, OpCode opCode, size_t reportedLength, bool compressed) { 262 | size_t messageLength; 263 | size_t headerLength; 264 | if (reportedLength < 126) { 265 | headerLength = 2; 266 | dst[1] = reportedLength; 267 | } else if (reportedLength <= UINT16_MAX) { 268 | headerLength = 4; 269 | dst[1] = 126; 270 | *(reinterpret_cast(&dst[2])) = htons(reportedLength); 271 | } else { 272 | headerLength = 10; 273 | dst[1] = 127; 274 | *(reinterpret_cast(&dst[2])) = htobe64(reportedLength); 275 | } 276 | 277 | dst[0] = 128 | (compressed ? SND_COMPRESSED : 0) | opCode; 278 | 279 | messageLength = headerLength + length; 280 | memcpy(dst + headerLength, src, length); 281 | 282 | return messageLength; 283 | } 284 | 285 | static inline void consume(char *src, unsigned int length, WebSocketState *wState) { 286 | if (wState->state.spillLength) { 287 | src -= wState->state.spillLength; 288 | length += wState->state.spillLength; 289 | memcpy(src, wState->state.spill, wState->state.spillLength); 290 | } 291 | if (wState->state.wantsHead) { 292 | parseNext: 293 | while (length >= SHORT_MESSAGE_HEADER) { 294 | 295 | // invalid reserved bits / invalid opcodes / invalid control frames / set compressed frame 296 | if ((rsv1(src) && !Impl::setCompressed(wState)) || rsv23(src) || (getOpCode(src) > 2 && getOpCode(src) < 8) || 297 | getOpCode(src) > 10 || (getOpCode(src) > 2 && (!isFin(src) || payloadLength(src) > 125))) { 298 | Impl::forceClose(wState); 299 | return; 300 | } 301 | 302 | if (payloadLength(src) < 126) { 303 | if (consumeMessage(payloadLength(src), src, length, wState)) { 304 | return; 305 | } 306 | } else if (payloadLength(src) == 126) { 307 | if (length < MEDIUM_MESSAGE_HEADER) { 308 | break; 309 | } else if (consumeMessage(ntohs(*reinterpret_cast(&src[2])), src, length, wState)) { 310 | return; 311 | } 312 | } else if (length < LONG_MESSAGE_HEADER) { 313 | break; 314 | } else if (consumeMessage(be64toh(*reinterpret_cast(&src[2])), src, length, wState)) { 315 | return; 316 | } 317 | } 318 | if (length) { 319 | memcpy(wState->state.spill, src, length); 320 | wState->state.spillLength = length; 321 | } 322 | } else if (consumeContinuation(src, length, wState)) { 323 | goto parseNext; 324 | } 325 | } 326 | 327 | static const int CONSUME_POST_PADDING = 4; 328 | static const int CONSUME_PRE_PADDING = LONG_MESSAGE_HEADER - 1; 329 | }; 330 | } 331 | 332 | #endif // WEBSOCKETPROTOCOL_UWS_H 333 | --------------------------------------------------------------------------------