@encharm/cws 4.6.0 → 4.8.3

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/CLAUDE.md +5 -3
  3. package/README.md +13 -1
  4. package/binding.gyp +8 -2
  5. package/dist/bindings/cws_darwin_arm64_node115.node +0 -0
  6. package/dist/bindings/cws_darwin_arm64_node127.node +0 -0
  7. package/dist/bindings/cws_darwin_arm64_node137.node +0 -0
  8. package/dist/bindings/cws_darwin_arm64_node147.node +0 -0
  9. package/dist/bindings/cws_linux_arm64_node115.node +0 -0
  10. package/dist/bindings/cws_linux_arm64_node127.node +0 -0
  11. package/dist/bindings/cws_linux_arm64_node137.node +0 -0
  12. package/dist/bindings/cws_linux_arm64_node147.node +0 -0
  13. package/dist/bindings/cws_linux_x64_node115.node +0 -0
  14. package/dist/bindings/cws_linux_x64_node127.node +0 -0
  15. package/dist/bindings/cws_linux_x64_node137.node +0 -0
  16. package/dist/bindings/cws_linux_x64_node147.node +0 -0
  17. package/dist/bindings/cws_win32_x64_node115.node +0 -0
  18. package/dist/bindings/cws_win32_x64_node127.node +0 -0
  19. package/dist/bindings/cws_win32_x64_node137.node +0 -0
  20. package/dist/bindings/cws_win32_x64_node147.node +0 -0
  21. package/dist/client.d.ts +2 -0
  22. package/dist/client.js +20 -4
  23. package/dist/index.d.ts +6 -1
  24. package/dist/index.js +2 -0
  25. package/dist/server.d.ts +1 -0
  26. package/dist/server.js +16 -2
  27. package/dist/shared.d.ts +1 -0
  28. package/dist/shared.js +3 -2
  29. package/package.json +1 -1
  30. package/src/Addon.cpp +2 -0
  31. package/src/Addon.h +10 -1
  32. package/src/Extensions.cpp +7 -1
  33. package/src/Extensions.h +2 -1
  34. package/src/Group.cpp +1 -1
  35. package/src/Group.h +4 -1
  36. package/src/HTTPSocket.cpp +2 -2
  37. package/src/Hub.cpp +11 -76
  38. package/src/Hub.h +11 -10
  39. package/src/Socket.h +37 -7
  40. package/src/WebSocket.cpp +16 -7
  41. package/src/Zlib.cpp +141 -0
  42. package/src/Zlib.h +38 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## Released 4.8.3
2
+ * Corked sends up to 1 KB use the per-loop block pool instead of malloc/free per message; queued messages remember their pool block. Also initialise the `reserved` callback argument, which was passed uninitialised for messages that hit the write queue.
3
+ * JS: resolve the native namespace once per socket instead of on every `send`/`close`/`ping`.
4
+
5
+ ## Released 4.8.2
6
+ * Accept compressed messages that end with a BFINAL=1 DEFLATE block (RFC 7692 section 7.2.3.4). Such clients (libdeflate-based and some non-zlib implementations) were disconnected on their first compressed message.
7
+
8
+ ## Released 4.8.1
9
+ * Skip the kernel-level cork/uncork (two `setsockopt` calls) around every socket read when write corking is active; those writes leave in the end-of-tick gathered write anyway. Saves 1-2 us of server CPU per read.
10
+
11
+ ## Released 4.8.0
12
+ * Prebuilt bindings now compress with zlib-ng 2.2.4 (vendored in `deps/zlib-ng`, statically linked, native `zng_` API so it cannot collide with Node's zlib). Measured on a real RPC stream: level 1 costs 2.2-2.5 us/KB vs 5.6-8 us/KB with zlib. The node-gyp fallback build keeps using Node's zlib through the same `src/Zlib.cpp` wrapper.
13
+ * New `perMessageDeflate.level` option (default 2) and a `zlibBackend` export naming the compiled-in implementation.
14
+ * Fix the node-gyp fallback build (`npm install` on a platform without a prebuilt binding): it lacked the vendored Node header include path, `HAVE_OPENSSL`/`NODE_WANT_INTERNALS`, and a macOS 10.15 deployment target, so it has failed silently since the Node 20 headers were introduced.
15
+
16
+ ## Released 4.7.0
17
+ * permessage-deflate: messages are now compressed by default once negotiated (previously only `send(..., { compress: true })` compressed anything). New options `threshold` (minimum size to compress), `windowBits` and `memLevel` (per-socket memory tier for the sliding window, advertised as `server_max_window_bits` when below 15).
18
+ * Remove a leftover debug `console.log` on module load.
19
+
1
20
  ## Released 4.6.0
2
21
  * Add Node 26 support (ABI 147, V8 14.6): vendored `src/headers/26`, `MakeCallback` with explicit `async_context`, tagged internal-field reads via `BaseObject::FromJSObject`.
3
22
  * Cork writes per event-loop iteration: sends to the same socket within one tick go out in a single gathered write. Disable with `CWS_CORK=0`.
package/CLAUDE.md CHANGED
@@ -21,14 +21,15 @@ Tests bind ports 3000 (ws) and 3001 (wss, certs in `tests/certs/`). The test fil
21
21
 
22
22
  ### Building the release binaries (multi-ABI)
23
23
 
24
- `binding.gyp` is only used by `npm install` for a local build against the running Node. Release binaries are built by the `Makefile` (macOS/Linux) and `make.bat` (Windows), which:
24
+ `binding.gyp` is only used by `npm install` for a local build against the running Node (Node's zlib, no zlib-ng); it must carry the same include path and defines as the Makefile or it fails silently (the install script discards the exit code). Release binaries are built by the `Makefile` (macOS/Linux) and `make.bat` (Windows), which:
25
25
 
26
26
  1. Download official Node header tarballs for one pinned version per supported major into `targets/` (`VER_115`=Node 20, `VER_127`=Node 22, `VER_137`=Node 24, `VER_147`=Node 26; the number is the Node ABI / `process.versions.modules`).
27
27
  2. Compile `src/*.cpp` once per ABI with `g++`/`cl` directly, with `-I src/headers/$V` for the matching Node major.
28
- 3. Emit `dist/bindings/cws_<platform>_<arch>_node<ABI>.node`.
28
+ 3. Build the vendored zlib-ng (`deps/zlib-ng`, native `zng_` API) once per OS/arch: CMake into `deps/zlib-ng/build-<OS>-<arch>/` on macOS/Linux, `nmake -f win32\Makefile.msc` on Windows. The bindings are compiled with `-DCWS_ZLIB_NG` and link it statically. `src/Zlib.cpp` is the only file that includes a zlib header; without the define (node-gyp fallback) it uses Node's zlib.
29
+ 4. Emit `dist/bindings/cws_<platform>_<arch>_node<ABI>.node`.
29
30
 
30
31
  ```sh
31
- make # all three ABIs for the host OS/arch (needs curl + g++/clang)
32
+ make # all four ABIs for the host OS/arch (needs curl, cmake, g++/clang)
32
33
  ./build-on-docker.sh linux/arm64 # Linux arm64 (native on Apple Silicon, ~1 min)
33
34
  ./build-on-docker.sh linux/amd64 # Linux x64 (default; emulated on Apple Silicon, slower)
34
35
  make.bat # Windows x64; auto-detects VS 2022 (Build Tools or any edition)
@@ -78,4 +79,5 @@ The client side never touches Node sockets: `native.connect(clientGroup, url, ws
78
79
  - Every handler, including `message`, is invoked through `node::MakeCallback`, which establishes an async-context scope and drains nextTick/microtasks after the handler returns. Because that drain happens before `onMessage` detaches the binary `ArrayBuffer`, a continuation that resolves within it still sees the bytes; anything that waits on real I/O sees a detached buffer.
79
80
  - `shared.ts` currently has a leftover `console.log('log', ...)` printing the binding path on load.
80
81
  - The addon exposes one `Group` per server plus a single shared client group; `ws.external` is the C++ `WebSocket*` and is nulled on disconnect, so every native call in `client.ts` guards on it.
82
+ - permessage-deflate: `Group` carries `deflateWindowBits`/`deflateMemLevel` (tier of the per-socket compressor allocated in the `WebSocket` ctor); the negotiator advertises `server_max_window_bits` when below 15 but ignores a smaller value requested by the client. The JS `send` decides `compress` per message: explicit option, else `compressThreshold` from the server config (undefined when deflate is off). Client-to-server inflate is one shared stream reset per message (`client_no_context_takeover` is always offered).
81
83
  - Close code defaults to 1005 when the peer sends none (matches `ws`). App-level ping uses the single byte `'9'` (`APP_PING_CODE`) for browser clients that can't see protocol pings.
package/README.md CHANGED
@@ -14,6 +14,11 @@ This table is true if you run ssl directly with `cws` (`Node.js`). In case if yo
14
14
 
15
15
  | cWS Version | Node 26 | Node 24 | Node 22 | Node 20 |
16
16
  |-------------|---------|----------|---------|----------
17
+ | 4.8.3 | X | X | X | X |
18
+ | 4.8.2 | X | X | X | X |
19
+ | 4.8.1 | X | X | X | X |
20
+ | 4.8.0 | X | X | X | X |
21
+ | 4.7.0 | X | X | X | X |
17
22
  | 4.6.0 | X | X | X | X |
18
23
  | 4.5.3 | | X | X | X |
19
24
 
@@ -174,7 +179,14 @@ const wsServer = new WebSocket.Server({
174
179
  * noDelay?: boolean (set socket no delay)
175
180
  * noServer?: boolean (use this when upgrade done outside of cws)
176
181
  * maxPayload?: number
177
- * perMessageDeflate?: boolean | { serverNoContextTakeover: boolean;}
182
+ * perMessageDeflate?: boolean | { serverNoContextTakeover?: boolean; windowBits?: number; memLevel?: number; threshold?: number }
183
+ * true = shared compressor, no context takeover. serverNoContextTakeover: false = per-socket
184
+ * sliding window (streaming compression; far better on small messages). windowBits 9..15 and
185
+ * memLevel 1..9 pick its memory tier: 15/8 = ~256 KB per socket (default), 12/5 = ~32 KB, 10/3 = ~8 KB.
186
+ * threshold = minimum message size in bytes to compress (default 0); send(..., { compress }) overrides.
187
+ * level 1..9 (default 2): deflate level of the per-socket compressor. The prebuilt bindings use zlib-ng
188
+ * (see `zlibBackend` export): level 1 is its very fast "quick" strategy (~3-4x less CPU than zlib
189
+ * level 1 for ~8% worse ratio), level 2 matches zlib level 1's ratio.
178
190
  * verifyClient?: (info: ConnectionInfo, next: VerifyClientNext) => void (use to allow or decline connections)
179
191
  **/
180
192
  }, () => {
package/binding.gyp CHANGED
@@ -12,8 +12,14 @@
12
12
  'src/cSNode.cpp',
13
13
  'src/WebSocket.cpp',
14
14
  'src/HTTPSocket.cpp',
15
- 'src/Socket.cpp'
15
+ 'src/Socket.cpp',
16
+ 'src/Zlib.cpp'
16
17
  ],
18
+ # Node private headers vendored per major (see src/headers/); the Makefile passes the same -I.
19
+ 'include_dirs': [
20
+ 'src/headers/<!(node -p "parseInt(process.versions.node)")'
21
+ ],
22
+ 'defines': ['HAVE_OPENSSL=1', 'NODE_WANT_INTERNALS=1'],
17
23
  'conditions': [
18
24
  ['OS=="linux"', {
19
25
  'cflags_cc': ['-std=c++17', '-DUSE_LIBUV'],
@@ -31,7 +37,7 @@
31
37
  }],
32
38
  ['OS=="mac"', {
33
39
  'xcode_settings': {
34
- 'MACOSX_DEPLOYMENT_TARGET': '10.7',
40
+ 'MACOSX_DEPLOYMENT_TARGET': '10.15',
35
41
  'CLANG_CXX_LANGUAGE_STANDARD': 'c++20',
36
42
  'CLANG_CXX_LIBRARY': 'libc++',
37
43
  'GCC_GENERATE_DEBUGGING_SYMBOLS': 'NO',
package/dist/client.d.ts CHANGED
@@ -12,6 +12,8 @@ export declare class WebSocket {
12
12
  registeredEvents: any;
13
13
  private external;
14
14
  private socketType;
15
+ private compressThreshold;
16
+ private nativeApi;
15
17
  constructor(url: string, options?: any);
16
18
  get bufferedAmount(): number;
17
19
  get _socket(): SocketAddress;
package/dist/client.js CHANGED
@@ -4,6 +4,12 @@ exports.WebSocket = void 0;
4
4
  const server_1 = require("./server");
5
5
  const shared_1 = require("./shared");
6
6
  const clientGroup = shared_1.native.client.group.create(0, shared_1.DEFAULT_PAYLOAD_LIMIT);
7
+ function messageByteLength(message) {
8
+ if (typeof message === 'string') {
9
+ return Buffer.byteLength(message);
10
+ }
11
+ return message && typeof message.byteLength === 'number' ? message.byteLength : 0;
12
+ }
7
13
  shared_1.setupNative(clientGroup, 'client');
8
14
  class WebSocket {
9
15
  constructor(url, options = {}) {
@@ -20,9 +26,12 @@ class WebSocket {
20
26
  message: shared_1.noop
21
27
  };
22
28
  this.socketType = 'client';
29
+ this.nativeApi = shared_1.native.client;
23
30
  if (!this.url && this.options.external) {
24
31
  this.socketType = 'server';
32
+ this.nativeApi = shared_1.native.server;
25
33
  this.external = this.options.external;
34
+ this.compressThreshold = this.options.compressThreshold;
26
35
  }
27
36
  else {
28
37
  shared_1.native.connect(clientGroup, url, this);
@@ -76,7 +85,14 @@ class WebSocket {
76
85
  if (options && options.binary === true) {
77
86
  opCode = shared_1.OPCODE_BINARY;
78
87
  }
79
- shared_1.native[this.socketType].send(this.external, message, opCode, cb ? () => process.nextTick(cb) : null, options && options.compress);
88
+ let compress = false;
89
+ if (options && options.compress !== undefined) {
90
+ compress = !!options.compress;
91
+ }
92
+ else if (this.compressThreshold !== undefined) {
93
+ compress = messageByteLength(message) >= this.compressThreshold;
94
+ }
95
+ this.nativeApi.send(this.external, message, opCode, cb ? () => process.nextTick(cb) : null, compress);
80
96
  }
81
97
  else if (cb) {
82
98
  cb(new Error('Socket not connected'));
@@ -84,18 +100,18 @@ class WebSocket {
84
100
  }
85
101
  ping(message) {
86
102
  if (this.external) {
87
- shared_1.native[this.socketType].send(this.external, message, shared_1.OPCODE_PING);
103
+ this.nativeApi.send(this.external, message, shared_1.OPCODE_PING);
88
104
  }
89
105
  }
90
106
  close(code = 1000, reason) {
91
107
  if (this.external) {
92
- shared_1.native[this.socketType].close(this.external, code, reason);
108
+ this.nativeApi.close(this.external, code, reason);
93
109
  this.external = null;
94
110
  }
95
111
  }
96
112
  terminate() {
97
113
  if (this.external) {
98
- shared_1.native[this.socketType].terminate(this.external);
114
+ this.nativeApi.terminate(this.external);
99
115
  this.external = null;
100
116
  }
101
117
  }
package/dist/index.d.ts CHANGED
@@ -21,10 +21,15 @@ export declare type ServerConfigs = {
21
21
  noServer?: boolean;
22
22
  maxPayload?: number;
23
23
  perMessageDeflate?: boolean | {
24
- serverNoContextTakeover: boolean;
24
+ serverNoContextTakeover?: boolean;
25
+ windowBits?: number;
26
+ memLevel?: number;
27
+ level?: number;
28
+ threshold?: number;
25
29
  };
26
30
  verifyClient?: (info: ConnectionInfo, next: VerifyClientNext) => void;
27
31
  };
28
32
  export { WebSocket } from './client';
29
33
  export { WebSocketServer } from './server';
30
34
  export declare const secureProtocol: string;
35
+ export { zlibBackend } from './shared';
package/dist/index.js CHANGED
@@ -6,3 +6,5 @@ Object.defineProperty(exports, "WebSocket", { enumerable: true, get: function ()
6
6
  var server_1 = require("./server");
7
7
  Object.defineProperty(exports, "WebSocketServer", { enumerable: true, get: function () { return server_1.WebSocketServer; } });
8
8
  exports.secureProtocol = 'TLSv1_2_method';
9
+ var shared_1 = require("./shared");
10
+ Object.defineProperty(exports, "zlibBackend", { enumerable: true, get: function () { return shared_1.zlibBackend; } });
package/dist/server.d.ts CHANGED
@@ -7,6 +7,7 @@ export declare class WebSocketServer {
7
7
  private options;
8
8
  upgradeCb: (ws: WebSocket) => void;
9
9
  upgradeReq: HTTP.IncomingMessage;
10
+ compressThreshold: number | undefined;
10
11
  registeredEvents: any;
11
12
  private httpServer;
12
13
  private serverGroup;
package/dist/server.js CHANGED
@@ -12,13 +12,27 @@ class WebSocketServer {
12
12
  connection: shared_1.noop,
13
13
  };
14
14
  let nativeOptions = 0;
15
+ let windowBits = 15;
16
+ let memLevel = 8;
17
+ let level = 2;
15
18
  if (this.options.perMessageDeflate) {
16
19
  nativeOptions |= shared_1.PERMESSAGE_DEFLATE;
17
- if (this.options.perMessageDeflate.serverNoContextTakeover === false) {
20
+ const deflate = typeof this.options.perMessageDeflate === 'object' ? this.options.perMessageDeflate : {};
21
+ if (deflate.serverNoContextTakeover === false) {
18
22
  nativeOptions |= shared_1.SLIDING_DEFLATE_WINDOW;
19
23
  }
24
+ if (typeof deflate.windowBits === 'number') {
25
+ windowBits = deflate.windowBits;
26
+ }
27
+ if (typeof deflate.memLevel === 'number') {
28
+ memLevel = deflate.memLevel;
29
+ }
30
+ if (typeof deflate.level === 'number') {
31
+ level = deflate.level;
32
+ }
33
+ this.compressThreshold = typeof deflate.threshold === 'number' ? deflate.threshold : 0;
20
34
  }
21
- this.serverGroup = shared_1.native.server.group.create(nativeOptions, this.options.maxPayload || shared_1.DEFAULT_PAYLOAD_LIMIT);
35
+ this.serverGroup = shared_1.native.server.group.create(nativeOptions, this.options.maxPayload || shared_1.DEFAULT_PAYLOAD_LIMIT, windowBits, memLevel, level);
22
36
  shared_1.setupNative(this.serverGroup, 'server', this);
23
37
  if (this.options.noServer) {
24
38
  return;
package/dist/shared.d.ts CHANGED
@@ -9,4 +9,5 @@ export declare const PERMESSAGE_DEFLATE: number;
9
9
  export declare const SLIDING_DEFLATE_WINDOW: number;
10
10
  export declare const DEFAULT_PAYLOAD_LIMIT: number;
11
11
  export declare const native: any;
12
+ export declare const zlibBackend: string;
12
13
  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.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.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;
@@ -19,11 +19,12 @@ exports.native = (() => {
19
19
  throw err;
20
20
  }
21
21
  })();
22
+ exports.zlibBackend = exports.native.zlibBackend;
22
23
  function setupNative(group, type, wsServer) {
23
24
  exports.native.setNoop(exports.noop);
24
25
  exports.native[type].group.onConnection(group, (external) => {
25
26
  if (type === 'server' && wsServer) {
26
- const socket = new client_1.WebSocket(null, { external });
27
+ const socket = new client_1.WebSocket(null, { external, compressThreshold: wsServer.compressThreshold });
27
28
  exports.native.setUserData(external, socket);
28
29
  if (wsServer.upgradeCb) {
29
30
  wsServer.upgradeCb(socket);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@encharm/cws",
3
- "version": "4.6.0",
3
+ "version": "4.8.3",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "description": "cWS - fast C++ WebSocket implementation for Node.js",
package/src/Addon.cpp CHANGED
@@ -20,6 +20,8 @@ void Initialize(Local<Object> exports) {
20
20
  NODE_SET_METHOD(exports, "upgrade", upgrade);
21
21
  NODE_SET_METHOD(exports, "connect", connect);
22
22
  NODE_SET_METHOD(exports, "setNoop", setNoop);
23
+ exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "zlibBackend").ToLocalChecked(),
24
+ String::NewFromUtf8(isolate, cWS::zlib::backend()).ToLocalChecked()).Check();
23
25
  hub.getNodeData()->corkState->enabled = corkEnabledFromEnv();
24
26
  registerCheck(isolate);
25
27
  }
package/src/Addon.h CHANGED
@@ -185,8 +185,17 @@ struct GroupData {
185
185
 
186
186
  template <bool isServer>
187
187
  void createGroup(const FunctionCallbackInfo<Value> &args) {
188
+ int windowBits = args.Length() > 2 && args[2]->IsNumber() ? (int) args[2].As<Integer>()->Value() : 15;
189
+ int memLevel = args.Length() > 3 && args[3]->IsNumber() ? (int) args[3].As<Integer>()->Value() : 8;
190
+ if (windowBits < 9) windowBits = 9;
191
+ if (windowBits > 15) windowBits = 15;
192
+ if (memLevel < 1) memLevel = 1;
193
+ if (memLevel > 9) memLevel = 9;
194
+ int level = args.Length() > 4 && args[4]->IsNumber() ? (int) args[4].As<Integer>()->Value() : 1;
195
+ if (level < 1) level = 1;
196
+ if (level > 9) level = 9;
188
197
  cWS::Group<isServer> *group = hub.createGroup<isServer>(
189
- args[0].As<Integer>()->Value(), args[1].As<Integer>()->Value());
198
+ args[0].As<Integer>()->Value(), args[1].As<Integer>()->Value(), windowBits, memLevel, level);
190
199
  group->setUserData(new GroupData);
191
200
  args.GetReturnValue().Set(External::New(args.GetIsolate(), group));
192
201
  }
@@ -76,7 +76,7 @@ ExtensionsParser::ExtensionsParser(const char *data, size_t length) {
76
76
  }
77
77
 
78
78
  template <bool isServer>
79
- ExtensionsNegotiator<isServer>::ExtensionsNegotiator(int wantedOptions) {
79
+ ExtensionsNegotiator<isServer>::ExtensionsNegotiator(int wantedOptions, int serverMaxWindowBits) : serverMaxWindowBits(serverMaxWindowBits) {
80
80
  options = wantedOptions;
81
81
  }
82
82
 
@@ -100,6 +100,12 @@ std::string ExtensionsNegotiator<isServer>::generateOffer() {
100
100
  if (options & Options::SERVER_NO_CONTEXT_TAKEOVER) {
101
101
  //extensionsOffer += "; server_no_context_takeover";
102
102
  }
103
+
104
+ // Tell the peer our LZ77 window is smaller than the default so it can
105
+ // size its inflate window accordingly (RFC 7692 section 7.1.2.1).
106
+ if (isServer && serverMaxWindowBits < 15) {
107
+ extensionsOffer += "; server_max_window_bits=" + std::to_string(serverMaxWindowBits);
108
+ }
103
109
  }
104
110
 
105
111
  return extensionsOffer;
package/src/Extensions.h CHANGED
@@ -18,8 +18,9 @@ template <bool isServer>
18
18
  class ExtensionsNegotiator {
19
19
  protected:
20
20
  int options;
21
+ int serverMaxWindowBits;
21
22
  public:
22
- ExtensionsNegotiator(int wantedOptions);
23
+ ExtensionsNegotiator(int wantedOptions, int serverMaxWindowBits = 15);
23
24
  std::string generateOffer();
24
25
  void readOffer(std::string offer);
25
26
  int getNegotiatedOptions();
package/src/Group.cpp CHANGED
@@ -114,7 +114,7 @@ void Group<isServer>::removeWebSocket(WebSocket<isServer> *webSocket) {
114
114
  }
115
115
 
116
116
  template <bool isServer>
117
- Group<isServer>::Group(int extensionOptions, unsigned int maxPayload, Hub *hub, cS::NodeData *nodeData) : cS::NodeData(*nodeData), maxPayload(maxPayload), hub(hub), extensionOptions(extensionOptions) {
117
+ Group<isServer>::Group(int extensionOptions, unsigned int maxPayload, Hub *hub, cS::NodeData *nodeData, int deflateWindowBits, int deflateMemLevel, int deflateLevel) : cS::NodeData(*nodeData), maxPayload(maxPayload), hub(hub), extensionOptions(extensionOptions), deflateWindowBits(deflateWindowBits), deflateMemLevel(deflateMemLevel), deflateLevel(deflateLevel) {
118
118
  connectionHandler = [](WebSocket<isServer> *, HttpRequest) {};
119
119
  transferHandler = [](WebSocket<isServer> *) {};
120
120
  messageHandler = [](WebSocket<isServer> *, char *, size_t, OpCode) {};
package/src/Group.h CHANGED
@@ -41,6 +41,9 @@ protected:
41
41
  unsigned int maxPayload;
42
42
  Hub *hub;
43
43
  int extensionOptions;
44
+ int deflateWindowBits = 15;
45
+ int deflateMemLevel = 8;
46
+ int deflateLevel = 1;
44
47
  cS::Timer *timer = nullptr, *httpTimer = nullptr;
45
48
  const char *userPingMessage;
46
49
  size_t userPingMessageLength;
@@ -61,7 +64,7 @@ protected:
61
64
  void addHttpSocket(HttpSocket<isServer> *httpSocket);
62
65
  void removeHttpSocket(HttpSocket<isServer> *httpSocket);
63
66
 
64
- Group(int extensionOptions, unsigned int maxPayload, Hub *hub, cS::NodeData *nodeData);
67
+ Group(int extensionOptions, unsigned int maxPayload, Hub *hub, cS::NodeData *nodeData, int deflateWindowBits = 15, int deflateMemLevel = 8, int deflateLevel = 1);
65
68
  void stopListening();
66
69
 
67
70
  public:
@@ -210,7 +210,7 @@ void HttpSocket<isServer>::upgrade(const char *secKey, const char *extensions, s
210
210
  std::string extensionsResponse;
211
211
  if (extensionsLength) {
212
212
  Group<isServer> *group = Group<isServer>::from(this);
213
- ExtensionsNegotiator<cWS::SERVER> extensionsNegotiator(group->extensionOptions);
213
+ ExtensionsNegotiator<cWS::SERVER> extensionsNegotiator(group->extensionOptions, group->deflateWindowBits);
214
214
  extensionsNegotiator.readOffer(std::string(extensions, extensionsLength));
215
215
  extensionsResponse = extensionsNegotiator.generateOffer();
216
216
  if (extensionsNegotiator.getNegotiatedOptions() & PERMESSAGE_DEFLATE) {
@@ -289,7 +289,7 @@ void HttpSocket<isServer>::onEnd(cS::Socket *s) {
289
289
  if (message->callback) {
290
290
  message->callback(nullptr, message->callbackData, true, nullptr);
291
291
  }
292
- httpSocket->messageQueue.pop();
292
+ httpSocket->messageQueue.pop(httpSocket->nodeData);
293
293
  }
294
294
 
295
295
  while (httpSocket->outstandingResponsesHead) {
package/src/Hub.cpp CHANGED
@@ -5,87 +5,22 @@
5
5
 
6
6
  namespace cWS {
7
7
 
8
- z_stream *Hub::allocateDefaultCompressor(z_stream *zStream) {
9
- deflateInit2(zStream, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
10
- return zStream;
8
+ // windowBits 9..15 and memLevel 1..9 select the per-socket memory tier (zlib uses
9
+ // about 4x the window plus a 2^(memLevel+9) byte hash table): 15/8 = ~256 KB,
10
+ // 12/5 = ~32 KB, 10/3 = ~8 KB, 9/2 = ~4 KB. Smaller tiers trade ratio for memory.
11
+ // level 1..9: with zlib-ng level 1 is the very fast "quick" strategy (~8% worse
12
+ // ratio than zlib level 1), level 2 matches zlib level 1's ratio at higher speed.
13
+ zlib::Stream *Hub::allocateDefaultCompressor(int level, int windowBits, int memLevel) {
14
+ return zlib::createDeflate(level, windowBits, memLevel);
11
15
  }
12
16
 
13
- char *Hub::deflate(char *data, size_t &length, z_stream *slidingDeflateWindow) {
14
- dynamicZlibBuffer.clear();
15
-
16
- z_stream *compressor = slidingDeflateWindow ? slidingDeflateWindow : &deflationStream;
17
-
18
- compressor->next_in = (Bytef *) data;
19
- compressor->avail_in = (unsigned int) length;
20
-
21
- // note: zlib requires more than 6 bytes with Z_SYNC_FLUSH
22
- const int DEFLATE_OUTPUT_CHUNK = LARGE_BUFFER_SIZE;
23
-
24
- int err;
25
- do {
26
- compressor->next_out = (Bytef *) zlibBuffer;
27
- compressor->avail_out = DEFLATE_OUTPUT_CHUNK;
28
-
29
- err = ::deflate(compressor, Z_SYNC_FLUSH);
30
- if (Z_OK == err && compressor->avail_out == 0) {
31
- dynamicZlibBuffer.append(zlibBuffer, DEFLATE_OUTPUT_CHUNK - compressor->avail_out);
32
- continue;
33
- } else {
34
- break;
35
- }
36
- } while (true);
37
-
38
- // note: should not change avail_out
39
- if (!slidingDeflateWindow) {
40
- deflateReset(compressor);
41
- }
42
-
43
- if (dynamicZlibBuffer.length()) {
44
- dynamicZlibBuffer.append(zlibBuffer, DEFLATE_OUTPUT_CHUNK - compressor->avail_out);
45
-
46
- length = dynamicZlibBuffer.length() - 4;
47
- return (char *) dynamicZlibBuffer.data();
48
- }
49
-
50
- length = DEFLATE_OUTPUT_CHUNK - compressor->avail_out - 4;
51
- return zlibBuffer;
17
+ char *Hub::deflate(char *data, size_t &length, zlib::Stream *slidingDeflateWindow) {
18
+ zlib::Stream *compressor = slidingDeflateWindow ? slidingDeflateWindow : deflationStream;
19
+ return zlib::deflate(compressor, data, length, zlibBuffer, LARGE_BUFFER_SIZE, dynamicZlibBuffer, !slidingDeflateWindow);
52
20
  }
53
21
 
54
- // todo: let's go through this code once more some time!
55
22
  char *Hub::inflate(char *data, size_t &length, size_t maxPayload) {
56
- dynamicZlibBuffer.clear();
57
-
58
- inflationStream.next_in = (Bytef *) data;
59
- inflationStream.avail_in = (unsigned int) length;
60
-
61
- int err;
62
- do {
63
- inflationStream.next_out = (Bytef *) zlibBuffer;
64
- inflationStream.avail_out = LARGE_BUFFER_SIZE;
65
- err = ::inflate(&inflationStream, Z_FINISH);
66
- if (!inflationStream.avail_in) {
67
- break;
68
- }
69
-
70
- dynamicZlibBuffer.append(zlibBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out);
71
- } while (err == Z_BUF_ERROR && dynamicZlibBuffer.length() <= maxPayload);
72
-
73
- inflateReset(&inflationStream);
74
-
75
- if ((err != Z_BUF_ERROR && err != Z_OK) || dynamicZlibBuffer.length() > maxPayload) {
76
- length = 0;
77
- return nullptr;
78
- }
79
-
80
- if (dynamicZlibBuffer.length()) {
81
- dynamicZlibBuffer.append(zlibBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out);
82
-
83
- length = dynamicZlibBuffer.length();
84
- return (char *) dynamicZlibBuffer.data();
85
- }
86
-
87
- length = LARGE_BUFFER_SIZE - inflationStream.avail_out;
88
- return zlibBuffer;
23
+ return zlib::inflate(inflationStream, data, length, maxPayload, zlibBuffer, LARGE_BUFFER_SIZE, dynamicZlibBuffer);
89
24
  }
90
25
 
91
26
  void Hub::onServerAccept(cS::Socket *s) {
package/src/Hub.h CHANGED
@@ -4,7 +4,7 @@
4
4
  #include "Group.h"
5
5
  #include "cSNode.h"
6
6
  #include <string>
7
- #include <zlib.h>
7
+ #include "Zlib.h"
8
8
  #include <mutex>
9
9
  #include <map>
10
10
 
@@ -18,10 +18,10 @@ protected:
18
18
  Group<CLIENT> *group;
19
19
  };
20
20
 
21
- static z_stream *allocateDefaultCompressor(z_stream *zStream);
21
+ static zlib::Stream *allocateDefaultCompressor(int level = 1, int windowBits = 15, int memLevel = 8);
22
22
 
23
- z_stream inflationStream = {}, deflationStream = {};
24
- char *deflate(char *data, size_t &length, z_stream *slidingDeflateWindow);
23
+ zlib::Stream *inflationStream = nullptr, *deflationStream = nullptr;
24
+ char *deflate(char *data, size_t &length, zlib::Stream *slidingDeflateWindow);
25
25
  char *inflate(char *data, size_t &length, size_t maxPayload);
26
26
  char *zlibBuffer;
27
27
  std::string dynamicZlibBuffer;
@@ -32,8 +32,8 @@ protected:
32
32
 
33
33
  public:
34
34
  template <bool isServer>
35
- Group<isServer> *createGroup(int extensionOptions = 0, unsigned int maxPayload = 16777216) {
36
- return new Group<isServer>(extensionOptions, maxPayload, this, nodeData);
35
+ Group<isServer> *createGroup(int extensionOptions = 0, unsigned int maxPayload = 16777216, int deflateWindowBits = 15, int deflateMemLevel = 8, int deflateLevel = 1) {
36
+ return new Group<isServer>(extensionOptions, maxPayload, this, nodeData, deflateWindowBits, deflateMemLevel, deflateLevel);
37
37
  }
38
38
 
39
39
  template <bool isServer>
@@ -48,10 +48,11 @@ public:
48
48
 
49
49
  Hub(int extensionOptions = 0, bool useDefaultLoop = false, unsigned int maxPayload = 16777216) : cS::Node(LARGE_BUFFER_SIZE, WebSocketProtocol<SERVER, WebSocket<SERVER>>::CONSUME_PRE_PADDING, WebSocketProtocol<SERVER, WebSocket<SERVER>>::CONSUME_POST_PADDING, useDefaultLoop),
50
50
  Group<SERVER>(extensionOptions, maxPayload, this, nodeData), Group<CLIENT>(0, maxPayload, this, nodeData) {
51
- inflateInit2(&inflationStream, -15);
51
+ inflationStream = zlib::createInflate(15);
52
52
  zlibBuffer = new char[LARGE_BUFFER_SIZE];
53
53
 
54
- allocateDefaultCompressor(&deflationStream);
54
+ // shared compressor for groups without a per-socket sliding window
55
+ deflationStream = allocateDefaultCompressor();
55
56
 
56
57
  #ifdef CWS_THREADSAFE
57
58
  getLoop()->preCbData = nodeData;
@@ -67,8 +68,8 @@ public:
67
68
  }
68
69
 
69
70
  ~Hub() {
70
- inflateEnd(&inflationStream);
71
- deflateEnd(&deflationStream);
71
+ zlib::destroy(inflationStream);
72
+ zlib::destroy(deflationStream);
72
73
  delete [] zlibBuffer;
73
74
  }
74
75
 
package/src/Socket.h CHANGED
@@ -50,20 +50,32 @@ protected:
50
50
  Message *nextMessage = nullptr;
51
51
  void (*callback)(void *socket, void *data, bool cancelled, void *reserved) = nullptr;
52
52
  void *callbackData = nullptr, *reserved = nullptr;
53
+ // >= 0: block came from NodeData's small-block pool (index for freeSmallMemoryBlock);
54
+ // -1: heap (new char[]). Messages are allocated as raw char arrays, so every
55
+ // allocation site must set this explicitly (the initializers above never run).
56
+ int poolIndex = -1;
53
57
  };
54
58
 
55
59
  Message *head = nullptr, *tail = nullptr;
56
60
  size_t totalLength = 0;
57
61
 
58
- void pop()
62
+ static void release(NodeData *nodeData, Message *message) {
63
+ if (message->poolIndex >= 0) {
64
+ nodeData->freeSmallMemoryBlock((char *) message, message->poolIndex);
65
+ } else {
66
+ delete [] (char *) message;
67
+ }
68
+ }
69
+
70
+ void pop(NodeData *nodeData)
59
71
  {
60
72
  Message *nextMessage;
61
73
  if ((nextMessage = head->nextMessage)) {
62
74
  totalLength -= head->length;
63
- delete [] (char *) head;
75
+ release(nodeData, head);
64
76
  head = nextMessage;
65
77
  } else {
66
- delete [] (char *) head;
78
+ release(nodeData, head);
67
79
  head = tail = nullptr;
68
80
  totalLength = 0;
69
81
  }
@@ -174,7 +186,7 @@ protected:
174
186
  // which frees the queue (use-after-free otherwise).
175
187
  auto callback = messagePtr->callback;
176
188
  void *callbackData = messagePtr->callbackData, *reserved = messagePtr->reserved;
177
- socket->messageQueue.pop();
189
+ socket->messageQueue.pop(socket->nodeData);
178
190
  if (callback) {
179
191
  callback(p, callbackData, false, reserved);
180
192
  if (socket->isClosed()) {
@@ -256,7 +268,7 @@ protected:
256
268
  // which frees the queue (use-after-free otherwise).
257
269
  auto callback = messagePtr->callback;
258
270
  void *callbackData = messagePtr->callbackData, *reserved = messagePtr->reserved;
259
- socket->messageQueue.pop();
271
+ socket->messageQueue.pop(socket->nodeData);
260
272
  if (callback) {
261
273
  callback(p, callbackData, false, reserved);
262
274
  if (socket->isClosed()) {
@@ -318,6 +330,10 @@ protected:
318
330
  messagePtr->length = length;
319
331
  messagePtr->data = ((char *) messagePtr) + sizeof(Queue::Message);
320
332
  messagePtr->nextMessage = nullptr;
333
+ messagePtr->callback = nullptr;
334
+ messagePtr->callbackData = nullptr;
335
+ messagePtr->reserved = nullptr;
336
+ messagePtr->poolIndex = -1;
321
337
 
322
338
  if (data) {
323
339
  memcpy((char *) messagePtr->data, data, messagePtr->length);
@@ -385,10 +401,21 @@ protected:
385
401
  if (corkActive()) {
386
402
  // Frame now, write later: everything sent to this socket during the
387
403
  // current loop iteration goes out in one gathered write (uncork()).
388
- Queue::Message *messagePtr = allocMessage(estimatedLength - sizeof(Queue::Message));
404
+ // Small messages come from the per-loop block pool (pop() returns them).
405
+ Queue::Message *messagePtr;
406
+ if (estimatedLength <= cS::NodeData::preAllocMaxSize) {
407
+ int memoryIndex = nodeData->getMemoryBlockIndex((int) estimatedLength);
408
+ messagePtr = (Queue::Message *) nodeData->getSmallMemoryBlock(memoryIndex);
409
+ messagePtr->data = ((char *) messagePtr) + sizeof(Queue::Message);
410
+ messagePtr->poolIndex = memoryIndex;
411
+ } else {
412
+ messagePtr = allocMessage(estimatedLength - sizeof(Queue::Message));
413
+ }
389
414
  messagePtr->length = T::transform(message, (char *) messagePtr->data, length, transformData);
415
+ messagePtr->nextMessage = nullptr;
390
416
  messagePtr->callback = callback;
391
417
  messagePtr->callbackData = callbackData;
418
+ messagePtr->reserved = nullptr;
392
419
  enqueue(messagePtr);
393
420
  if (!corkPending) {
394
421
  corkPending = true;
@@ -405,6 +432,9 @@ protected:
405
432
  Queue::Message *messagePtr = (Queue::Message *) nodeData->getSmallMemoryBlock(memoryIndex);
406
433
  messagePtr->data = ((char *) messagePtr) + sizeof(Queue::Message);
407
434
  messagePtr->length = T::transform(message, (char *) messagePtr->data, length, transformData);
435
+ messagePtr->nextMessage = nullptr;
436
+ messagePtr->reserved = nullptr;
437
+ messagePtr->poolIndex = memoryIndex;
408
438
 
409
439
  bool wasTransferred;
410
440
  if (write(messagePtr, wasTransferred)) {
@@ -589,7 +619,7 @@ public:
589
619
  if (callbacks && m->callback) {
590
620
  callbacks->push_back({m->callback, m->callbackData, m->reserved});
591
621
  }
592
- messageQueue.pop();
622
+ messageQueue.pop(nodeData);
593
623
  } else {
594
624
  m->length -= sent;
595
625
  m->data += sent;
package/src/WebSocket.cpp CHANGED
@@ -11,7 +11,8 @@ WebSocket<isServer>::WebSocket(bool perMessageDeflate, cS::Socket *socket) : cS:
11
11
 
12
12
  // if we are created in a group with sliding deflate window allocate it here
13
13
  if (Group<isServer>::from(this)->extensionOptions & SLIDING_DEFLATE_WINDOW) {
14
- slidingDeflateWindow = Hub::allocateDefaultCompressor(new z_stream{});
14
+ Group<isServer> *group = Group<isServer>::from(this);
15
+ slidingDeflateWindow = Hub::allocateDefaultCompressor(group->deflateLevel, group->deflateWindowBits, group->deflateMemLevel);
15
16
  }
16
17
  }
17
18
 
@@ -52,7 +53,7 @@ void WebSocket<isServer>::send(const char *message, size_t length, OpCode opCode
52
53
 
53
54
  static size_t transform(const char *src, char *dst, size_t length, TransformData transformData) {
54
55
  if (transformData.compress) {
55
- char *deflated = Group<isServer>::from(transformData.s)->hub->deflate((char *) src, length, (z_stream *) transformData.s->slidingDeflateWindow);
56
+ char *deflated = Group<isServer>::from(transformData.s)->hub->deflate((char *) src, length, (zlib::Stream *) transformData.s->slidingDeflateWindow);
56
57
  return WebSocketProtocol<isServer, WebSocket<isServer>>::formatMessage(dst, deflated, length, transformData.opCode, length, true);
57
58
  }
58
59
 
@@ -150,6 +151,9 @@ void WebSocket<isServer>::sendPrepared(typename WebSocket<isServer>::PreparedMes
150
151
  Queue::Message *messagePtr = (Queue::Message *) nodeData->getSmallMemoryBlock(memoryIndex);
151
152
  messagePtr->data = preparedMessage->buffer;
152
153
  messagePtr->length = preparedMessage->length;
154
+ messagePtr->nextMessage = nullptr;
155
+ messagePtr->reserved = nullptr;
156
+ messagePtr->poolIndex = memoryIndex;
153
157
 
154
158
  bool wasTransferred;
155
159
  if (write(messagePtr, wasTransferred)) {
@@ -194,9 +198,15 @@ cS::Socket *WebSocket<isServer>::onData(cS::Socket *s, char *data, size_t length
194
198
 
195
199
  webSocket->hasOutstandingPong = false;
196
200
  if (!webSocket->isShuttingDown()) {
197
- webSocket->cork(true);
201
+ // The kernel-level cork (TCP_CORK/TCP_NOPUSH) around a read only helps when
202
+ // replies are written immediately; with write corking they leave in one gathered
203
+ // write at the end of the tick, so skip the two setsockopt calls per read.
204
+ bool kernelCork = !webSocket->corkActive();
205
+ if (kernelCork) {
206
+ webSocket->cork(true);
207
+ }
198
208
  WebSocketProtocol<isServer, WebSocket<isServer>>::consume(data, (unsigned int) length, webSocket);
199
- if (!webSocket->isClosed()) {
209
+ if (kernelCork && !webSocket->isClosed()) {
200
210
  webSocket->cork(false);
201
211
  }
202
212
  }
@@ -305,7 +315,7 @@ void WebSocket<isServer>::onEnd(cS::Socket *s) {
305
315
  if (message->callback) {
306
316
  message->callback(nullptr, message->callbackData, true, nullptr);
307
317
  }
308
- webSocket->messageQueue.pop();
318
+ webSocket->messageQueue.pop(webSocket->nodeData);
309
319
  }
310
320
 
311
321
  webSocket->nodeData->clearPendingPollChanges(webSocket);
@@ -313,8 +323,7 @@ void WebSocket<isServer>::onEnd(cS::Socket *s) {
313
323
  // remove any per-websocket zlib memory
314
324
  if (webSocket->slidingDeflateWindow) {
315
325
  // this relates to Hub::allocateDefaultCompressor
316
- deflateEnd((z_stream *) webSocket->slidingDeflateWindow);
317
- delete (z_stream *) webSocket->slidingDeflateWindow;
326
+ zlib::destroy((zlib::Stream *) webSocket->slidingDeflateWindow);
318
327
  webSocket->slidingDeflateWindow = nullptr;
319
328
  }
320
329
  }
package/src/Zlib.cpp ADDED
@@ -0,0 +1,141 @@
1
+ #include "Zlib.h"
2
+
3
+ #ifdef CWS_ZLIB_NG
4
+ #include "zlib-ng.h"
5
+ typedef zng_stream ZStream;
6
+ #define ZFN(name) zng_##name
7
+ #define CWS_ZLIB_BACKEND "zlib-ng " ZLIBNG_VERSION
8
+ #else
9
+ #include <zlib.h>
10
+ typedef z_stream ZStream;
11
+ #define ZFN(name) name
12
+ #define CWS_ZLIB_BACKEND "zlib " ZLIB_VERSION
13
+ #endif
14
+
15
+ namespace cWS {
16
+ namespace zlib {
17
+
18
+ struct Stream {
19
+ ZStream zs;
20
+ bool isDeflate;
21
+ };
22
+
23
+ typedef decltype(ZStream::next_in) InPtr;
24
+ typedef decltype(ZStream::next_out) OutPtr;
25
+
26
+ Stream *createDeflate(int level, int windowBits, int memLevel) {
27
+ Stream *stream = new Stream();
28
+ stream->isDeflate = true;
29
+ ZFN(deflateInit2)(&stream->zs, level, Z_DEFLATED, -windowBits, memLevel, Z_DEFAULT_STRATEGY);
30
+ return stream;
31
+ }
32
+
33
+ Stream *createInflate(int windowBits) {
34
+ Stream *stream = new Stream();
35
+ stream->isDeflate = false;
36
+ ZFN(inflateInit2)(&stream->zs, -windowBits);
37
+ return stream;
38
+ }
39
+
40
+ void reset(Stream *stream) {
41
+ if (stream->isDeflate) {
42
+ ZFN(deflateReset)(&stream->zs);
43
+ } else {
44
+ ZFN(inflateReset)(&stream->zs);
45
+ }
46
+ }
47
+
48
+ void destroy(Stream *stream) {
49
+ if (!stream) {
50
+ return;
51
+ }
52
+ if (stream->isDeflate) {
53
+ ZFN(deflateEnd)(&stream->zs);
54
+ } else {
55
+ ZFN(inflateEnd)(&stream->zs);
56
+ }
57
+ delete stream;
58
+ }
59
+
60
+ char *deflate(Stream *stream, char *data, size_t &length, char *buffer, size_t bufferSize, std::string &dynamic, bool resetAfter) {
61
+ ZStream &zs = stream->zs;
62
+ dynamic.clear();
63
+
64
+ zs.next_in = (InPtr) data;
65
+ zs.avail_in = (unsigned int) length;
66
+
67
+ // note: zlib requires more than 6 bytes of output space with Z_SYNC_FLUSH
68
+ int err;
69
+ do {
70
+ zs.next_out = (OutPtr) buffer;
71
+ zs.avail_out = (unsigned int) bufferSize;
72
+
73
+ err = ZFN(deflate)(&zs, Z_SYNC_FLUSH);
74
+ if (Z_OK == err && zs.avail_out == 0) {
75
+ dynamic.append(buffer, bufferSize - zs.avail_out);
76
+ continue;
77
+ } else {
78
+ break;
79
+ }
80
+ } while (true);
81
+
82
+ // note: must not change avail_out
83
+ if (resetAfter) {
84
+ ZFN(deflateReset)(&zs);
85
+ }
86
+
87
+ if (dynamic.length()) {
88
+ dynamic.append(buffer, bufferSize - zs.avail_out);
89
+ length = dynamic.length() - 4;
90
+ return (char *) dynamic.data();
91
+ }
92
+
93
+ length = bufferSize - zs.avail_out - 4;
94
+ return buffer;
95
+ }
96
+
97
+ char *inflate(Stream *stream, char *data, size_t &length, size_t maxPayload, char *buffer, size_t bufferSize, std::string &dynamic) {
98
+ ZStream &zs = stream->zs;
99
+ dynamic.clear();
100
+
101
+ zs.next_in = (InPtr) data;
102
+ zs.avail_in = (unsigned int) length;
103
+
104
+ int err;
105
+ do {
106
+ zs.next_out = (OutPtr) buffer;
107
+ zs.avail_out = (unsigned int) bufferSize;
108
+ err = ZFN(inflate)(&zs, Z_FINISH);
109
+ // Z_STREAM_END: the sender ended the message with a BFINAL=1 block, which
110
+ // RFC 7692 section 7.2.3.4 permits (libdeflate and some non-zlib clients do
111
+ // this); anything left in the input is the 4-byte tail and is ignored.
112
+ if (!zs.avail_in || err == Z_STREAM_END) {
113
+ break;
114
+ }
115
+
116
+ dynamic.append(buffer, bufferSize - zs.avail_out);
117
+ } while (err == Z_BUF_ERROR && dynamic.length() <= maxPayload);
118
+
119
+ ZFN(inflateReset)(&zs);
120
+
121
+ if ((err != Z_BUF_ERROR && err != Z_OK && err != Z_STREAM_END) || dynamic.length() > maxPayload) {
122
+ length = 0;
123
+ return nullptr;
124
+ }
125
+
126
+ if (dynamic.length()) {
127
+ dynamic.append(buffer, bufferSize - zs.avail_out);
128
+ length = dynamic.length();
129
+ return (char *) dynamic.data();
130
+ }
131
+
132
+ length = bufferSize - zs.avail_out;
133
+ return buffer;
134
+ }
135
+
136
+ const char *backend() {
137
+ return CWS_ZLIB_BACKEND;
138
+ }
139
+
140
+ }
141
+ }
package/src/Zlib.h ADDED
@@ -0,0 +1,38 @@
1
+ #ifndef CWS_ZLIB_H
2
+ #define CWS_ZLIB_H
3
+
4
+ #include <cstddef>
5
+ #include <string>
6
+
7
+ // Thin backend-neutral wrapper around the DEFLATE library. Built with
8
+ // -DCWS_ZLIB_NG (the prebuilt bindings) it uses the vendored zlib-ng through
9
+ // its native zng_ API, so nothing collides with the zlib inside Node; without
10
+ // the define (node-gyp fallback) it uses zlib.h from the Node headers. Zlib.cpp
11
+ // is the only translation unit that includes either header.
12
+ namespace cWS {
13
+ namespace zlib {
14
+
15
+ struct Stream;
16
+
17
+ Stream *createDeflate(int level, int windowBits, int memLevel);
18
+ Stream *createInflate(int windowBits = 15);
19
+ void reset(Stream *stream);
20
+ void destroy(Stream *stream);
21
+
22
+ // Deflates one message with a sync flush. Output lands in `buffer` (bufferSize
23
+ // bytes) or, when it does not fit, is accumulated in `dynamic`. Returns the output
24
+ // pointer and sets `length` to its size minus the 4-byte sync-flush tail
25
+ // (RFC 7692). With `resetAfter` the stream forgets the message (no context takeover).
26
+ char *deflate(Stream *stream, char *data, size_t &length, char *buffer, size_t bufferSize, std::string &dynamic, bool resetAfter);
27
+
28
+ // Inflates one message (the 4-byte tail must already be appended). Returns nullptr
29
+ // and length 0 on error or when the output would exceed maxPayload. Always resets.
30
+ char *inflate(Stream *stream, char *data, size_t &length, size_t maxPayload, char *buffer, size_t bufferSize, std::string &dynamic);
31
+
32
+ // e.g. "zlib-ng 2.2.4" or "zlib 1.3.1"
33
+ const char *backend();
34
+
35
+ }
36
+ }
37
+
38
+ #endif // CWS_ZLIB_H