@encharm/cws 4.8.4 → 4.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/CLAUDE.md +9 -1
- package/README.md +27 -0
- package/binding.gyp +4 -2
- package/deps/readerwriterqueue/LICENSE.md +28 -0
- package/deps/readerwriterqueue/README.md +186 -0
- package/deps/readerwriterqueue/atomicops.h +761 -0
- package/deps/readerwriterqueue/readerwriterqueue.h +979 -0
- package/dist/bindings/cws_darwin_arm64_node115.node +0 -0
- package/dist/bindings/cws_darwin_arm64_node127.node +0 -0
- package/dist/bindings/cws_darwin_arm64_node137.node +0 -0
- package/dist/bindings/cws_darwin_arm64_node147.node +0 -0
- package/dist/bindings/cws_linux_arm64_node115.node +0 -0
- package/dist/bindings/cws_linux_arm64_node127.node +0 -0
- package/dist/bindings/cws_linux_arm64_node137.node +0 -0
- package/dist/bindings/cws_linux_arm64_node147.node +0 -0
- package/dist/bindings/cws_linux_x64_node115.node +0 -0
- package/dist/bindings/cws_linux_x64_node127.node +0 -0
- package/dist/bindings/cws_linux_x64_node137.node +0 -0
- package/dist/bindings/cws_linux_x64_node147.node +0 -0
- package/dist/bindings/cws_win32_x64_node115.node +0 -0
- package/dist/bindings/cws_win32_x64_node127.node +0 -0
- package/dist/bindings/cws_win32_x64_node137.node +0 -0
- package/dist/bindings/cws_win32_x64_node147.node +0 -0
- package/dist/client.d.ts +3 -1
- package/dist/client.js +10 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -0
- package/dist/prepared.d.ts +6 -0
- package/dist/prepared.js +17 -0
- package/dist/shared.d.ts +1 -0
- package/dist/shared.js +2 -1
- package/package.json +1 -1
- package/src/Addon.cpp +2 -0
- package/src/Addon.h +47 -0
- package/src/Hub.cpp +4 -2
- package/src/MicroDeflate.h +109 -0
- package/src/Networking.h +5 -2
- package/src/SendWorker.cpp +229 -0
- package/src/SendWorker.h +25 -0
- package/src/Socket.h +195 -4
- package/src/WebSocket.cpp +169 -2
- package/src/WebSocket.h +22 -0
- package/src/Zlib.cpp +33 -1
- package/src/Zlib.h +6 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { WebSocketServer } from './server';
|
|
3
3
|
import { SocketAddress, ServerConfigs } from './index';
|
|
4
|
+
import { PreparedMessage } from './prepared';
|
|
4
5
|
export declare class WebSocket {
|
|
5
6
|
url: string;
|
|
6
7
|
private options;
|
|
@@ -28,9 +29,10 @@ export declare class WebSocket {
|
|
|
28
29
|
on(event: 'error', listener: (err: Error) => void): void;
|
|
29
30
|
on(event: 'message', listener: (message: string | any) => void): void;
|
|
30
31
|
on(event: 'close', listener: (code?: number, reason?: string) => void): void;
|
|
31
|
-
send(message: string | Buffer, options?: {
|
|
32
|
+
send(message: string | Buffer | ArrayBufferView | PreparedMessage, options?: {
|
|
32
33
|
binary?: boolean;
|
|
33
34
|
compress?: boolean;
|
|
35
|
+
prefix?: string | Buffer | ArrayBufferView;
|
|
34
36
|
}, cb?: (err?: Error) => void): void;
|
|
35
37
|
ping(message?: string | Buffer): void;
|
|
36
38
|
close(code?: number, reason?: string): void;
|
package/dist/client.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.WebSocket = void 0;
|
|
4
4
|
const server_1 = require("./server");
|
|
5
5
|
const shared_1 = require("./shared");
|
|
6
|
+
const prepared_1 = require("./prepared");
|
|
6
7
|
const clientGroup = shared_1.native.client.group.create(0, shared_1.DEFAULT_PAYLOAD_LIMIT);
|
|
7
8
|
function messageByteLength(message) {
|
|
8
9
|
if (typeof message === 'string') {
|
|
@@ -78,6 +79,7 @@ class WebSocket {
|
|
|
78
79
|
}
|
|
79
80
|
send(message, options, cb) {
|
|
80
81
|
if (this.external) {
|
|
82
|
+
const prepared = message instanceof prepared_1.PreparedMessage;
|
|
81
83
|
let opCode = typeof message === 'string' ? shared_1.OPCODE_TEXT : shared_1.OPCODE_BINARY;
|
|
82
84
|
if (options && options.binary === false) {
|
|
83
85
|
opCode = shared_1.OPCODE_TEXT;
|
|
@@ -90,9 +92,15 @@ class WebSocket {
|
|
|
90
92
|
compress = !!options.compress;
|
|
91
93
|
}
|
|
92
94
|
else if (this.compressThreshold !== undefined) {
|
|
93
|
-
compress = messageByteLength(message) >= this.compressThreshold;
|
|
95
|
+
compress = messageByteLength(message) + (options && options.prefix ? messageByteLength(options.prefix) : 0) >= this.compressThreshold;
|
|
96
|
+
}
|
|
97
|
+
const callback = cb ? () => process.nextTick(cb) : null;
|
|
98
|
+
if (prepared) {
|
|
99
|
+
this.nativeApi.sendShared(this.external, options && options.prefix, message.external, opCode, callback, compress);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this.nativeApi.send(this.external, message, opCode, callback, compress);
|
|
94
103
|
}
|
|
95
|
-
this.nativeApi.send(this.external, message, opCode, cb ? () => process.nextTick(cb) : null, compress);
|
|
96
104
|
}
|
|
97
105
|
else if (cb) {
|
|
98
106
|
cb(new Error('Socket not connected'));
|
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare type ServerConfigs = {
|
|
|
30
30
|
verifyClient?: (info: ConnectionInfo, next: VerifyClientNext) => void;
|
|
31
31
|
};
|
|
32
32
|
export { WebSocket } from './client';
|
|
33
|
+
export { PreparedMessage } from './prepared';
|
|
33
34
|
export { WebSocketServer } from './server';
|
|
34
35
|
export declare const secureProtocol: string;
|
|
35
|
-
export { zlibBackend } from './shared';
|
|
36
|
+
export { zlibBackend, sendThread } from './shared';
|
package/dist/index.js
CHANGED
|
@@ -3,8 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.secureProtocol = void 0;
|
|
4
4
|
var client_1 = require("./client");
|
|
5
5
|
Object.defineProperty(exports, "WebSocket", { enumerable: true, get: function () { return client_1.WebSocket; } });
|
|
6
|
+
var prepared_1 = require("./prepared");
|
|
7
|
+
Object.defineProperty(exports, "PreparedMessage", { enumerable: true, get: function () { return prepared_1.PreparedMessage; } });
|
|
6
8
|
var server_1 = require("./server");
|
|
7
9
|
Object.defineProperty(exports, "WebSocketServer", { enumerable: true, get: function () { return server_1.WebSocketServer; } });
|
|
8
10
|
exports.secureProtocol = 'TLSv1_2_method';
|
|
9
11
|
var shared_1 = require("./shared");
|
|
10
12
|
Object.defineProperty(exports, "zlibBackend", { enumerable: true, get: function () { return shared_1.zlibBackend; } });
|
|
13
|
+
Object.defineProperty(exports, "sendThread", { enumerable: true, get: function () { return shared_1.sendThread; } });
|
package/dist/prepared.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PreparedMessage = void 0;
|
|
4
|
+
const shared_1 = require("./shared");
|
|
5
|
+
const preparedRegistry = typeof global.FinalizationRegistry === 'function'
|
|
6
|
+
? new global.FinalizationRegistry((external) => shared_1.native.server.releaseShared(external))
|
|
7
|
+
: undefined;
|
|
8
|
+
class PreparedMessage {
|
|
9
|
+
constructor(data) {
|
|
10
|
+
this.byteLength = data.byteLength;
|
|
11
|
+
this.external = shared_1.native.server.prepareShared(data);
|
|
12
|
+
if (preparedRegistry) {
|
|
13
|
+
preparedRegistry.register(this, this.external);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.PreparedMessage = PreparedMessage;
|
package/dist/shared.d.ts
CHANGED
|
@@ -10,4 +10,5 @@ export declare const SLIDING_DEFLATE_WINDOW: number;
|
|
|
10
10
|
export declare const DEFAULT_PAYLOAD_LIMIT: number;
|
|
11
11
|
export declare const native: any;
|
|
12
12
|
export declare const zlibBackend: string;
|
|
13
|
+
export declare const sendThread: string;
|
|
13
14
|
export declare function setupNative(group: any, type: string, wsServer?: WebSocketServer): void;
|
package/dist/shared.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setupNative = exports.zlibBackend = exports.native = exports.DEFAULT_PAYLOAD_LIMIT = exports.SLIDING_DEFLATE_WINDOW = exports.PERMESSAGE_DEFLATE = exports.APP_PING_CODE = exports.OPCODE_BINARY = exports.OPCODE_PING = exports.OPCODE_TEXT = exports.noop = void 0;
|
|
3
|
+
exports.setupNative = exports.sendThread = exports.zlibBackend = exports.native = exports.DEFAULT_PAYLOAD_LIMIT = exports.SLIDING_DEFLATE_WINDOW = exports.PERMESSAGE_DEFLATE = exports.APP_PING_CODE = exports.OPCODE_BINARY = exports.OPCODE_PING = exports.OPCODE_TEXT = exports.noop = void 0;
|
|
4
4
|
const client_1 = require("./client");
|
|
5
5
|
exports.noop = () => { };
|
|
6
6
|
exports.OPCODE_TEXT = 1;
|
|
@@ -20,6 +20,7 @@ exports.native = (() => {
|
|
|
20
20
|
}
|
|
21
21
|
})();
|
|
22
22
|
exports.zlibBackend = exports.native.zlibBackend;
|
|
23
|
+
exports.sendThread = exports.native.sendThread;
|
|
23
24
|
function setupNative(group, type, wsServer) {
|
|
24
25
|
exports.native.setNoop(exports.noop);
|
|
25
26
|
exports.native[type].group.onConnection(group, (external) => {
|
package/package.json
CHANGED
package/src/Addon.cpp
CHANGED
|
@@ -24,6 +24,8 @@ void Initialize(Local<Object> exports) {
|
|
|
24
24
|
String::NewFromUtf8(isolate, cWS::zlib::backend()).ToLocalChecked()).Check();
|
|
25
25
|
hub.getNodeData()->corkState->enabled = corkEnabledFromEnv();
|
|
26
26
|
registerCheck(isolate);
|
|
27
|
+
exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "sendThread").ToLocalChecked(),
|
|
28
|
+
String::NewFromUtf8(isolate, cS::SendWorker::status()).ToLocalChecked()).Check();
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
NODE_MODULE(addon, Initialize)
|
package/src/Addon.h
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
#include <openssl/ssl.h>
|
|
7
7
|
#include <uv.h>
|
|
8
8
|
#include <cstring>
|
|
9
|
+
#include "SendWorker.h"
|
|
9
10
|
|
|
10
11
|
#define NODE_WANT_INTERNALS 1
|
|
11
12
|
|
|
@@ -97,6 +98,11 @@ void registerCheck(Isolate *isolate) {
|
|
|
97
98
|
});
|
|
98
99
|
uv_unref((uv_handle_t *)&check);
|
|
99
100
|
|
|
101
|
+
// Send worker thread: only meaningful with corking, whose end-of-tick flush feeds it.
|
|
102
|
+
if (hub.getNodeData()->corkState->enabled) {
|
|
103
|
+
cS::SendWorker::init((uv_loop_t *)hub.getLoop());
|
|
104
|
+
}
|
|
105
|
+
|
|
100
106
|
uv_prepare_init((uv_loop_t *)hub.getLoop(), &corkPrepare);
|
|
101
107
|
uv_prepare_start(&corkPrepare, [](uv_prepare_t *) {
|
|
102
108
|
cS::Socket::flushCorked(hub.getNodeData());
|
|
@@ -638,6 +644,44 @@ void sendPrepared(const FunctionCallbackInfo<Value> &args) {
|
|
|
638
644
|
->Value());
|
|
639
645
|
}
|
|
640
646
|
|
|
647
|
+
template <bool isServer>
|
|
648
|
+
void prepareShared(const FunctionCallbackInfo<Value> &args) {
|
|
649
|
+
NativeString nativeString(args.GetIsolate(), args[0]);
|
|
650
|
+
args.GetReturnValue().Set(External::New(
|
|
651
|
+
args.GetIsolate(),
|
|
652
|
+
cWS::WebSocket<isServer>::prepareShared(nativeString.getData(),
|
|
653
|
+
nativeString.getLength())));
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
template <bool isServer>
|
|
657
|
+
void releaseShared(const FunctionCallbackInfo<Value> &args) {
|
|
658
|
+
cWS::SharedPayload::unref(
|
|
659
|
+
(cWS::SharedPayload *)args[0].As<External>()->Value());
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// (socket, prefix, payload, opCode, callback, compress)
|
|
663
|
+
template <bool isServer>
|
|
664
|
+
void sendShared(const FunctionCallbackInfo<Value> &args) {
|
|
665
|
+
NativeString prefix(args.GetIsolate(), args[1]);
|
|
666
|
+
cWS::SharedPayload *payload =
|
|
667
|
+
(cWS::SharedPayload *)args[2].As<External>()->Value();
|
|
668
|
+
cWS::OpCode opCode = (cWS::OpCode)args[3].As<Integer>()->Value();
|
|
669
|
+
|
|
670
|
+
SendCallbackData *sc = nullptr;
|
|
671
|
+
void (*callback)(cWS::WebSocket<isServer> *, void *, bool, void *) = nullptr;
|
|
672
|
+
if (args[4]->IsFunction()) {
|
|
673
|
+
callback = sendCallback;
|
|
674
|
+
sc = new SendCallbackData;
|
|
675
|
+
sc->jsCallback.Reset(args.GetIsolate(), Local<Function>::Cast(args[4]));
|
|
676
|
+
sc->isolate = args.GetIsolate();
|
|
677
|
+
}
|
|
678
|
+
bool compress = args[5].As<Boolean>()->Value();
|
|
679
|
+
|
|
680
|
+
unwrapSocket<isServer>(args[0].As<External>())
|
|
681
|
+
->sendShared(prefix.getData(), prefix.getLength(), payload, opCode,
|
|
682
|
+
compress, callback, sc);
|
|
683
|
+
}
|
|
684
|
+
|
|
641
685
|
template <bool isServer>
|
|
642
686
|
void finalizeMessage(const FunctionCallbackInfo<Value> &args) {
|
|
643
687
|
cWS::WebSocket<isServer>::finalizeMessage(
|
|
@@ -717,6 +761,9 @@ struct Namespace {
|
|
|
717
761
|
NODE_SET_METHOD(object, "prepareMessage", prepareMessage<isServer>);
|
|
718
762
|
NODE_SET_METHOD(object, "sendPrepared", sendPrepared<isServer>);
|
|
719
763
|
NODE_SET_METHOD(object, "finalizeMessage", finalizeMessage<isServer>);
|
|
764
|
+
NODE_SET_METHOD(object, "prepareShared", prepareShared<isServer>);
|
|
765
|
+
NODE_SET_METHOD(object, "sendShared", sendShared<isServer>);
|
|
766
|
+
NODE_SET_METHOD(object, "releaseShared", releaseShared<isServer>);
|
|
720
767
|
|
|
721
768
|
Local<Object> group = Object::New(isolate);
|
|
722
769
|
NODE_SET_METHOD(group, "onConnection", onConnection<isServer>);
|
package/src/Hub.cpp
CHANGED
|
@@ -15,8 +15,10 @@ zlib::Stream *Hub::allocateDefaultCompressor(int level, int windowBits, int memL
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
char *Hub::deflate(char *data, size_t &length, zlib::Stream *slidingDeflateWindow) {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
if (!slidingDeflateWindow) {
|
|
19
|
+
return zlib::deflateIndependent(deflationStream, data, length, zlibBuffer, LARGE_BUFFER_SIZE, dynamicZlibBuffer);
|
|
20
|
+
}
|
|
21
|
+
return zlib::deflate(slidingDeflateWindow, data, length, zlibBuffer, LARGE_BUFFER_SIZE, dynamicZlibBuffer, false);
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
char *Hub::inflate(char *data, size_t &length, size_t maxPayload) {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#ifndef CWS_MICRODEFLATE_H
|
|
2
|
+
#define CWS_MICRODEFLATE_H
|
|
3
|
+
|
|
4
|
+
// Minimal raw-DEFLATE encoder for independent messages (permessage-deflate without
|
|
5
|
+
// context takeover, i.e. the shared-compressor mode). One fixed-Huffman block, greedy
|
|
6
|
+
// LZ77 over a 4-byte hash, no lazy matching, no stream state, and no per-message table
|
|
7
|
+
// clearing: stale entries are validated by position and content. Output ends the way
|
|
8
|
+
// Z_SYNC_FLUSH ends (empty stored block); the returned length already excludes the
|
|
9
|
+
// trailing 00 00 ff ff, as RFC 7692 requires. Measured on a real BSON RPC stream: same
|
|
10
|
+
// ratio as zlib-ng level 1 at ~1.7x its speed, mostly by skipping the 64 KB hash reset
|
|
11
|
+
// zlib performs per message. Decodes with any inflater.
|
|
12
|
+
//
|
|
13
|
+
// Not for context takeover: matches never reference earlier messages.
|
|
14
|
+
|
|
15
|
+
#include <cstdint>
|
|
16
|
+
#include <cstring>
|
|
17
|
+
#include <cstddef>
|
|
18
|
+
|
|
19
|
+
namespace cWS {
|
|
20
|
+
namespace microdeflate {
|
|
21
|
+
|
|
22
|
+
// Worst case output for `length` input bytes (all literals at 9 bits + block overhead).
|
|
23
|
+
inline size_t bound(size_t length) {
|
|
24
|
+
return length + length / 8 + 32;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class Encoder {
|
|
28
|
+
static const int HASH_BITS = 13;
|
|
29
|
+
uint32_t table[1 << HASH_BITS];
|
|
30
|
+
|
|
31
|
+
static const uint16_t *lenBase() { static const uint16_t t[29] = {3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258}; return t; }
|
|
32
|
+
static const uint8_t *lenExtra() { static const uint8_t t[29] = {0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0}; return t; }
|
|
33
|
+
static const uint16_t *distBase() { static const uint16_t t[30] = {1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577}; return t; }
|
|
34
|
+
static const uint8_t *distExtra() { static const uint8_t t[30] = {0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; return t; }
|
|
35
|
+
|
|
36
|
+
struct Tables {
|
|
37
|
+
uint16_t litCode[288]; uint8_t litBits[288];
|
|
38
|
+
uint8_t lenCode[259];
|
|
39
|
+
uint8_t distCode[32769];
|
|
40
|
+
uint8_t distCodeRev[30];
|
|
41
|
+
static uint32_t rev(uint32_t v, int n) { uint32_t r = 0; for (int i = 0; i < n; i++) { r = (r << 1) | (v & 1); v >>= 1; } return r; }
|
|
42
|
+
Tables() {
|
|
43
|
+
for (int i = 0; i < 288; i++) {
|
|
44
|
+
int bits, code;
|
|
45
|
+
if (i < 144) { bits = 8; code = 0x30 + i; }
|
|
46
|
+
else if (i < 256) { bits = 9; code = 0x190 + (i - 144); }
|
|
47
|
+
else if (i < 280) { bits = 7; code = i - 256; }
|
|
48
|
+
else { bits = 8; code = 0xC0 + (i - 280); }
|
|
49
|
+
litCode[i] = (uint16_t) rev(code, bits); litBits[i] = (uint8_t) bits;
|
|
50
|
+
}
|
|
51
|
+
for (int c = 0; c < 29; c++) for (int l = lenBase()[c]; l < (c < 28 ? lenBase()[c + 1] : 259); l++) lenCode[l] = (uint8_t) c;
|
|
52
|
+
for (int c = 0; c < 30; c++) { for (int d = distBase()[c]; d < (c < 29 ? distBase()[c + 1] : 32769); d++) distCode[d] = (uint8_t) c; distCodeRev[c] = (uint8_t) rev(c, 5); }
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
static const Tables &tables() { static const Tables t; return t; }
|
|
56
|
+
|
|
57
|
+
struct BitWriter {
|
|
58
|
+
uint8_t *out; size_t pos = 0; uint64_t acc = 0; int n = 0;
|
|
59
|
+
inline void put(uint32_t v, int bits) { acc |= (uint64_t) v << n; n += bits; while (n >= 8) { out[pos++] = (uint8_t) acc; acc >>= 8; n -= 8; } }
|
|
60
|
+
inline void flushByte() { if (n) { out[pos++] = (uint8_t) acc; acc = 0; n = 0; } }
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
static inline uint32_t hash4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return (v * 2654435761u) >> (32 - HASH_BITS); }
|
|
64
|
+
|
|
65
|
+
public:
|
|
66
|
+
Encoder() { memset(table, 0xff, sizeof(table)); tables(); }
|
|
67
|
+
|
|
68
|
+
// `out` must have room for bound(length) bytes. Returns the compressed length
|
|
69
|
+
// (without the 4-byte sync-flush tail).
|
|
70
|
+
size_t compress(const uint8_t *in, size_t length, uint8_t *out) {
|
|
71
|
+
const Tables &t = tables();
|
|
72
|
+
BitWriter w{out};
|
|
73
|
+
w.put(0, 1); w.put(1, 2); // BFINAL=0, BTYPE=01 (fixed Huffman)
|
|
74
|
+
size_t i = 0;
|
|
75
|
+
const size_t limit = length >= 4 ? length - 4 : 0;
|
|
76
|
+
while (i < limit) {
|
|
77
|
+
uint32_t h = hash4(in + i);
|
|
78
|
+
uint32_t cand = table[h];
|
|
79
|
+
table[h] = (uint32_t) i;
|
|
80
|
+
if (cand < i && i - cand <= 32768 && memcmp(in + cand, in + i, 4) == 0) {
|
|
81
|
+
size_t maxLen = length - i; if (maxLen > 258) maxLen = 258;
|
|
82
|
+
size_t m = 4;
|
|
83
|
+
while (m < maxLen && in[cand + m] == in[i + m]) m++;
|
|
84
|
+
uint32_t dist = (uint32_t) (i - cand);
|
|
85
|
+
int lc = t.lenCode[m];
|
|
86
|
+
w.put(t.litCode[257 + lc], t.litBits[257 + lc]);
|
|
87
|
+
if (lenExtra()[lc]) w.put((uint32_t) (m - lenBase()[lc]), lenExtra()[lc]);
|
|
88
|
+
int dc = t.distCode[dist];
|
|
89
|
+
w.put(t.distCodeRev[dc], 5);
|
|
90
|
+
if (distExtra()[dc]) w.put(dist - distBase()[dc], distExtra()[dc]);
|
|
91
|
+
if (i + 1 < limit) table[hash4(in + i + 1)] = (uint32_t) (i + 1);
|
|
92
|
+
i += m;
|
|
93
|
+
} else {
|
|
94
|
+
w.put(t.litCode[in[i]], t.litBits[in[i]]);
|
|
95
|
+
i++;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (; i < length; i++) w.put(t.litCode[in[i]], t.litBits[in[i]]);
|
|
99
|
+
w.put(t.litCode[256], t.litBits[256]); // end of block
|
|
100
|
+
w.put(0, 1); w.put(0, 2); w.flushByte(); // empty stored block: BFINAL=0, BTYPE=00, then byte-align
|
|
101
|
+
out[w.pos++] = 0; out[w.pos++] = 0; out[w.pos++] = 0xff; out[w.pos++] = 0xff;
|
|
102
|
+
return w.pos - 4;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#endif // CWS_MICRODEFLATE_H
|
package/src/Networking.h
CHANGED
|
@@ -34,8 +34,11 @@
|
|
|
34
34
|
#define be64toh(x) __builtin_bswap64(x)
|
|
35
35
|
#else
|
|
36
36
|
#define __thread __declspec(thread)
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
// Windows is little-endian; the intrinsic avoids depending on WinSock's htonll/ntohll,
|
|
38
|
+
// whose declaration depends on SDK version macros that not every translation unit sets.
|
|
39
|
+
#include <stdlib.h>
|
|
40
|
+
#define htobe64(x) _byteswap_uint64(x)
|
|
41
|
+
#define be64toh(x) _byteswap_uint64(x)
|
|
39
42
|
#define pthread_t DWORD
|
|
40
43
|
#define pthread_self GetCurrentThreadId
|
|
41
44
|
#endif
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#include "SendWorker.h"
|
|
2
|
+
#include "Socket.h"
|
|
3
|
+
#include "Zlib.h"
|
|
4
|
+
#include "cWS.h"
|
|
5
|
+
#include "readerwriterqueue.h"
|
|
6
|
+
#include <thread>
|
|
7
|
+
#include <cstdlib>
|
|
8
|
+
#include <cstring>
|
|
9
|
+
#include <cerrno>
|
|
10
|
+
#include <cctype>
|
|
11
|
+
#include <string>
|
|
12
|
+
|
|
13
|
+
namespace cS {
|
|
14
|
+
|
|
15
|
+
namespace {
|
|
16
|
+
bool workerActive = false;
|
|
17
|
+
const char *workerStatus = "not initialised";
|
|
18
|
+
moodycamel::BlockingReaderWriterQueue<Socket::SendOp *> *toWorker = nullptr;
|
|
19
|
+
moodycamel::ReaderWriterQueue<Socket::SendOp *> *toMain = nullptr;
|
|
20
|
+
uv_async_t mainWake;
|
|
21
|
+
|
|
22
|
+
void onMainWake(uv_async_t *) {
|
|
23
|
+
Socket::SendOp *op;
|
|
24
|
+
while (toMain->try_dequeue(op)) {
|
|
25
|
+
Socket::sendComplete(op);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
void workerLoop() {
|
|
30
|
+
Socket::SendOp *op;
|
|
31
|
+
for (;;) {
|
|
32
|
+
toWorker->wait_dequeue(op);
|
|
33
|
+
Socket::performSend(op);
|
|
34
|
+
toMain->enqueue(op);
|
|
35
|
+
uv_async_send(&mainWake);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
bool SendWorker::init(uv_loop_t *loop) {
|
|
41
|
+
const char *disabled = getenv("CWS_SEND_THREAD");
|
|
42
|
+
std::string value = disabled ? disabled : "";
|
|
43
|
+
for (char &c : value) c = (char) tolower((unsigned char) c);
|
|
44
|
+
if (value == "0" || value == "false" || value == "off" || value == "no") {
|
|
45
|
+
workerStatus = "disabled by CWS_SEND_THREAD";
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
toWorker = new moodycamel::BlockingReaderWriterQueue<Socket::SendOp *>(4096);
|
|
49
|
+
toMain = new moodycamel::ReaderWriterQueue<Socket::SendOp *>(4096);
|
|
50
|
+
uv_async_init(loop, &mainWake, onMainWake);
|
|
51
|
+
uv_unref((uv_handle_t *) &mainWake); // completions alone must not keep the process alive
|
|
52
|
+
std::thread(workerLoop).detach();
|
|
53
|
+
workerActive = true;
|
|
54
|
+
workerStatus = "active";
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
bool SendWorker::active() { return workerActive; }
|
|
59
|
+
const char *SendWorker::status() { return workerStatus; }
|
|
60
|
+
|
|
61
|
+
bool SendWorker::submit(void *op) {
|
|
62
|
+
// try_enqueue never allocates; a full queue means "send it yourself this tick".
|
|
63
|
+
return toWorker->try_enqueue((Socket::SendOp *) op);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---- Socket side (needs Socket internals) ----
|
|
67
|
+
|
|
68
|
+
// Deflates a pending message with the given stream and replaces its raw payload with a
|
|
69
|
+
// framed compressed message. Shared between the worker (its own compressor) and the
|
|
70
|
+
// main-thread materialize path (the hub's).
|
|
71
|
+
static void deflateAndFrame(Socket::Queue::Message *m, cWS::zlib::Stream *stream, bool resetAfter, char *buffer, size_t bufferSize, std::string &dynamic) {
|
|
72
|
+
size_t compressedLength = m->length;
|
|
73
|
+
// resetAfter == independent message (no context takeover): microdeflate; else the socket's window
|
|
74
|
+
char *deflated = resetAfter ? cWS::zlib::deflateIndependent(stream, (char *) m->data, compressedLength, buffer, bufferSize, dynamic)
|
|
75
|
+
: cWS::zlib::deflate(stream, (char *) m->data, compressedLength, buffer, bufferSize, dynamic, false);
|
|
76
|
+
const size_t HEADER = cWS::WebSocketProtocol<true, cWS::WebSocket<true>>::LONG_MESSAGE_HEADER;
|
|
77
|
+
char *frame = new char[compressedLength + HEADER];
|
|
78
|
+
size_t frameLength = cWS::WebSocketProtocol<true, cWS::WebSocket<true>>::formatMessage(frame, deflated, compressedLength, (cWS::OpCode) m->opCode, compressedLength, true);
|
|
79
|
+
delete [] (char *) m->data;
|
|
80
|
+
m->data = frame;
|
|
81
|
+
m->length = frameLength;
|
|
82
|
+
m->ownsData = true;
|
|
83
|
+
m->compressPending = false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
namespace {
|
|
87
|
+
// worker-thread compression state: its own shared compressor and scratch buffers
|
|
88
|
+
cWS::zlib::Stream *workerDeflate = nullptr;
|
|
89
|
+
const size_t WORKER_BUFFER = 300 * 1024;
|
|
90
|
+
char *workerBuffer = nullptr;
|
|
91
|
+
std::string workerDynamic;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
void Socket::materializeOnMain(Socket *s, Queue::Message *m, cWS::zlib::Stream *stream, bool resetAfter, char *buffer, size_t bufferSize, std::string &dynamic) {
|
|
95
|
+
deflateAndFrame(m, stream, resetAfter, buffer, bufferSize, dynamic);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
void Socket::performSend(SendOp *op) {
|
|
99
|
+
if (!workerDeflate) {
|
|
100
|
+
workerDeflate = cWS::zlib::createDeflate(1, 15, 8);
|
|
101
|
+
workerBuffer = new char[WORKER_BUFFER];
|
|
102
|
+
}
|
|
103
|
+
// 1. deflate + frame anything still pending, with this socket's window or the shared one
|
|
104
|
+
cWS::zlib::Stream *stream = op->deflateWindow ? (cWS::zlib::Stream *) op->deflateWindow : workerDeflate;
|
|
105
|
+
for (Queue::Message *m = op->head; m; m = m->nextMessage) {
|
|
106
|
+
if (m->compressPending) {
|
|
107
|
+
deflateAndFrame(m, stream, !op->deflateWindow, workerBuffer, WORKER_BUFFER, workerDynamic);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// 2. gather
|
|
111
|
+
op->count = 0;
|
|
112
|
+
op->bytes = 0;
|
|
113
|
+
for (Queue::Message *m = op->head; m; m = m->nextMessage) {
|
|
114
|
+
#ifdef _WIN32
|
|
115
|
+
op->bufs[op->count].buf = (CHAR *) m->data;
|
|
116
|
+
op->bufs[op->count].len = (ULONG) m->length;
|
|
117
|
+
#else
|
|
118
|
+
op->iov[op->count].iov_base = (void *) m->data;
|
|
119
|
+
op->iov[op->count].iov_len = m->length;
|
|
120
|
+
#endif
|
|
121
|
+
op->bytes += m->length;
|
|
122
|
+
op->count++;
|
|
123
|
+
}
|
|
124
|
+
#ifdef _WIN32
|
|
125
|
+
DWORD sent = 0;
|
|
126
|
+
if (WSASend(op->fd, op->bufs, (DWORD) op->count, &sent, 0, nullptr, nullptr) == SOCKET_ERROR) {
|
|
127
|
+
op->result = -1;
|
|
128
|
+
op->error = WSAGetLastError();
|
|
129
|
+
} else {
|
|
130
|
+
op->result = (ssize_t) sent;
|
|
131
|
+
op->error = 0;
|
|
132
|
+
}
|
|
133
|
+
#else
|
|
134
|
+
msghdr msg = {};
|
|
135
|
+
msg.msg_iov = op->iov;
|
|
136
|
+
msg.msg_iovlen = op->count;
|
|
137
|
+
op->result = ::sendmsg(op->fd, &msg, MSG_NOSIGNAL);
|
|
138
|
+
op->error = op->result < 0 ? errno : 0;
|
|
139
|
+
#endif
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static bool sendWouldBlock(int error) {
|
|
143
|
+
#ifdef _WIN32
|
|
144
|
+
return error == WSAEWOULDBLOCK;
|
|
145
|
+
#else
|
|
146
|
+
return error == EWOULDBLOCK || error == EAGAIN;
|
|
147
|
+
#endif
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
void Socket::sendComplete(SendOp *op) {
|
|
151
|
+
Socket *s = op->socket;
|
|
152
|
+
if (!s) {
|
|
153
|
+
// socket closed while the op was in flight: release its messages, then the fd we kept open
|
|
154
|
+
for (Queue::Message *m = op->head; m;) {
|
|
155
|
+
Queue::Message *next = m->nextMessage;
|
|
156
|
+
if (m->callback) {
|
|
157
|
+
m->callback(nullptr, m->callbackData, true, m->reserved);
|
|
158
|
+
}
|
|
159
|
+
Queue::release(op->nodeData, m);
|
|
160
|
+
m = next;
|
|
161
|
+
}
|
|
162
|
+
if (op->closeFd) {
|
|
163
|
+
op->nodeData->netContext->closeSocket(op->fd);
|
|
164
|
+
}
|
|
165
|
+
if (op->destroyWindow) {
|
|
166
|
+
cWS::zlib::destroy((cWS::zlib::Stream *) op->destroyWindow);
|
|
167
|
+
}
|
|
168
|
+
delete op;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
s->sendOp = nullptr;
|
|
172
|
+
|
|
173
|
+
ssize_t res = op->result;
|
|
174
|
+
if (res < 0) {
|
|
175
|
+
if (sendWouldBlock(op->error)) {
|
|
176
|
+
res = 0;
|
|
177
|
+
} else {
|
|
178
|
+
s->requeueFront(op);
|
|
179
|
+
delete op;
|
|
180
|
+
if (s->endCb) {
|
|
181
|
+
s->endCb(s);
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
std::vector<PendingCallback> callbacks;
|
|
188
|
+
size_t sent = (size_t) res, opBytes = op->bytes;
|
|
189
|
+
while (sent > 0 && op->head) {
|
|
190
|
+
Queue::Message *m = op->head;
|
|
191
|
+
if (sent >= m->length) {
|
|
192
|
+
sent -= m->length;
|
|
193
|
+
op->bytes -= m->length;
|
|
194
|
+
if (m->callback) {
|
|
195
|
+
callbacks.push_back({m->callback, m->callbackData, m->reserved});
|
|
196
|
+
}
|
|
197
|
+
op->head = m->nextMessage;
|
|
198
|
+
Queue::release(op->nodeData, m);
|
|
199
|
+
} else {
|
|
200
|
+
m->length -= sent;
|
|
201
|
+
m->data += sent;
|
|
202
|
+
op->bytes -= sent;
|
|
203
|
+
sent = 0;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (!op->head) {
|
|
207
|
+
op->tail = nullptr;
|
|
208
|
+
}
|
|
209
|
+
bool complete = (size_t) res == opBytes;
|
|
210
|
+
s->requeueFront(op);
|
|
211
|
+
delete op;
|
|
212
|
+
|
|
213
|
+
if (!s->messageQueue.empty()) {
|
|
214
|
+
if (complete) {
|
|
215
|
+
s->submitToWorker(); // more arrived while in flight
|
|
216
|
+
} else if ((s->getPoll() & UV_WRITABLE) == 0) {
|
|
217
|
+
s->setPoll(s->getPoll() | UV_WRITABLE); // kernel buffer full: the drain loop resumes when writable
|
|
218
|
+
s->changePoll(s);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
for (PendingCallback &c : callbacks) {
|
|
222
|
+
c.callback(s, c.callbackData, false, c.reserved);
|
|
223
|
+
if (s->isClosed()) {
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
}
|
package/src/SendWorker.h
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#ifndef CWS_SEND_WORKER_H
|
|
2
|
+
#define CWS_SEND_WORKER_H
|
|
3
|
+
|
|
4
|
+
// Send worker thread. The end-of-tick flush hands each socket's queued frames to
|
|
5
|
+
// one worker thread as an op; the worker does the gathered send syscall (the
|
|
6
|
+
// kernel's TCP work is charged to it, not to the JS thread) and posts the op back.
|
|
7
|
+
// Both directions are single-producer/single-consumer lock-free queues
|
|
8
|
+
// (deps/readerwriterqueue); the worker parks on a semaphore when idle and the main
|
|
9
|
+
// thread is woken through a uv_async. Disabled with CWS_SEND_THREAD=0.
|
|
10
|
+
|
|
11
|
+
#include <uv.h>
|
|
12
|
+
|
|
13
|
+
namespace cS {
|
|
14
|
+
|
|
15
|
+
struct SendWorker {
|
|
16
|
+
static bool init(uv_loop_t *loop);
|
|
17
|
+
static bool active();
|
|
18
|
+
static const char *status(); // "active", or why not
|
|
19
|
+
// Main thread only. Returns false if the queue is full; the caller then sends synchronously.
|
|
20
|
+
static bool submit(void *op);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#endif // CWS_SEND_WORKER_H
|