diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6fb4f7dc493b6a..e78612d424d09a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -59,12 +59,12 @@ # quic -./deps/ngtcp2/* @nodejs/quic -./deps/nghttp3/* @nodejs/quic -./doc/api/quic.md @nodejs/quic -./lib/internal/quic/* @nodejs/quic -./src/node_bob* @nodejs/quic -./src/quic/* @nodejs/quic +/deps/ngtcp2/ @nodejs/quic +/deps/nghttp3/ @nodejs/quic +/doc/api/quic.md @nodejs/quic +/lib/internal/quic/ @nodejs/quic +/src/node_bob* @nodejs/quic +/src/quic/ @nodejs/quic # modules diff --git a/CHANGELOG.md b/CHANGELOG.md index 64c64e36327b3c..e0bbb28129d87b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,8 @@ release. -14.4.0
+14.5.0
+14.4.0
14.3.0
14.2.0
14.1.0
@@ -57,7 +58,8 @@ release. 13.0.0
-12.18.1
+12.18.2
+12.18.1
12.18.0
12.17.0
12.16.3
diff --git a/doc/api/child_process.md b/doc/api/child_process.md index 1c1ea3b5547386..0d00a2a2433616 100644 --- a/doc/api/child_process.md +++ b/doc/api/child_process.md @@ -194,9 +194,9 @@ metacharacters may be used to trigger arbitrary command execution.** If a `callback` function is provided, it is called with the arguments `(error, stdout, stderr)`. On success, `error` will be `null`. On error, `error` will be an instance of [`Error`][]. The `error.code` property will be -the exit code of the child process while `error.signal` will be set to the -signal that terminated the process. Any exit code other than `0` is considered -to be an error. +the exit code of the process. By convention, any exit code other than `0` +indicates an error. `error.signal` will be the signal that terminated the +process. The `stdout` and `stderr` arguments passed to the callback will contain the stdout and stderr output of the child process. By default, Node.js will decode diff --git a/doc/api/cli.md b/doc/api/cli.md index 8606054148ed40..33c1e94fafb7c4 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -1385,7 +1385,7 @@ Overriding this value to an empty string (`''`) will use the built-in REPL. ### `NODE_SKIP_PLATFORM_CHECK=value` If `value` equals `'1'`, the check for a supported platform is skipped during diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 49273db7608dc6..0621ee62d3246d 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -1216,7 +1216,7 @@ This can be called many times with new data as it is streamed. Type: Documentation-only (supports [`--pending-deprecation`][]) @@ -2751,6 +2751,19 @@ const moduleParents = Object.values(require.cache) .filter((m) => m.children.includes(module)); ``` + +### DEP0XXX: `socket.bufferSize` + + +Type: Documentation-only + +[`socket.bufferSize`][] is just an alias for [`writable.writableLength`][]. + [`--pending-deprecation`]: cli.html#cli_pending_deprecation [`--throw-deprecation`]: cli.html#cli_throw_deprecation [`Buffer.allocUnsafeSlow(size)`]: buffer.html#buffer_class_method_buffer_allocunsafeslow_size @@ -2824,6 +2837,7 @@ const moduleParents = Object.values(require.cache) [`script.createCachedData()`]: vm.html#vm_script_createcacheddata [`setInterval()`]: timers.html#timers_setinterval_callback_delay_args [`setTimeout()`]: timers.html#timers_settimeout_callback_delay_args +[`socket.bufferSize`]: net.html#net_socket_buffersize [`timeout.ref()`]: timers.html#timers_timeout_ref [`timeout.refresh()`]: timers.html#timers_timeout_refresh [`timeout.unref()`]: timers.html#timers_timeout_unref @@ -2860,6 +2874,7 @@ const moduleParents = Object.values(require.cache) [`util`]: util.html [`worker.exitedAfterDisconnect`]: cluster.html#cluster_worker_exitedafterdisconnect [`worker.terminate()`]: worker_threads.html#worker_threads_worker_terminate +[`writable.writableLength`]: stream.html#stream_writable_writablelength [`zlib.bytesWritten`]: zlib.html#zlib_zlib_byteswritten [Legacy URL API]: url.html#url_legacy_url_api [NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf diff --git a/doc/api/dgram.md b/doc/api/dgram.md index e3c75e026c2d74..be0323a306b27e 100644 --- a/doc/api/dgram.md +++ b/doc/api/dgram.md @@ -399,7 +399,7 @@ if the socket is not connected. A message posted to a [`MessagePort`][] could not be deserialized in the target diff --git a/doc/api/esm.md b/doc/api/esm.md index eca866e452b736..06a9f89ee57579 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -124,12 +124,12 @@ as ES modules and `.cjs` files are always treated as CommonJS. ### Package scope and file extensions A folder containing a `package.json` file, and all subfolders below that folder -down until the next folder containing another `package.json`, is considered a -_package scope_. The `"type"` field defines how `.js` files should be treated -within a particular `package.json` file’s package scope. Every package in a +until the next folder containing another `package.json`, are a +_package scope_. The `"type"` field defines how to treat `.js` files +within the package scope. Every package in a project’s `node_modules` folder contains its own `package.json` file, so each -project’s dependencies have their own package scopes. A `package.json` lacking a -`"type"` field is treated as if it contained `"type": "commonjs"`. +project’s dependencies have their own package scopes. If a `package.json` file +does not have a `"type"` field, the default `"type"` is `"commonjs"`. The package scope applies not only to initial entry points (`node my-app.js`) but also to files referenced by `import` statements and `import()` expressions. diff --git a/doc/api/events.md b/doc/api/events.md index d008b95641ea11..39950b5c12f581 100644 --- a/doc/api/events.md +++ b/doc/api/events.md @@ -939,7 +939,7 @@ composed of the emitted event arguments. ## `EventTarget` and `Event` API > Stability: 1 - Experimental @@ -1056,7 +1056,7 @@ The `EventTarget` does not implement any special default handling for ### Class: `Event` The `Event` object is an adaptation of the [`Event` Web API][]. Instances @@ -1064,7 +1064,7 @@ are created internally by Node.js. #### `event.bubbles` * Type: {boolean} Always returns `false`. @@ -1073,7 +1073,7 @@ This is not used in Node.js and is provided purely for completeness. #### `event.cancelBubble()` Alias for `event.stopPropagation()`. This is not used in Node.js and is @@ -1081,14 +1081,14 @@ provided purely for completeness. #### `event.cancelable` * Type: {boolean} True if the event was created with the `cancelable` option. #### `event.composed` * Type: {boolean} Always returns `false`. @@ -1097,7 +1097,7 @@ This is not used in Node.js and is provided purely for completeness. #### `event.composedPath()` Returns an array containing the current `EventTarget` as the only entry or @@ -1106,7 +1106,7 @@ Node.js and is provided purely for completeness. #### `event.currentTarget` * Type: {EventTarget} The `EventTarget` dispatching the event. @@ -1115,7 +1115,7 @@ Alias for `event.target`. #### `event.defaultPrevented` * Type: {boolean} @@ -1125,7 +1125,7 @@ called. #### `event.eventPhase` * Type: {number} Returns `0` while an event is not being dispatched, `2` while @@ -1135,7 +1135,7 @@ This is not used in Node.js and is provided purely for completeness. #### `event.isTrusted` * Type: {boolean} Always returns `false`. @@ -1144,14 +1144,14 @@ This is not used in Node.js and is provided purely for completeness. #### `event.preventDefault()` Sets the `defaultPrevented` property to `true` if `cancelable` is `true`. #### `event.returnValue` * Type: {boolean} True if the event has not been canceled. @@ -1160,7 +1160,7 @@ This is not used in Node.js and is provided purely for completeness. #### `event.srcElement` * Type: {EventTarget} The `EventTarget` dispatching the event. @@ -1169,28 +1169,28 @@ Alias for `event.target`. #### `event.stopImmediatePropagation()` Stops the invocation of event listeners after the current one completes. #### `event.stopPropagation()` This is not used in Node.js and is provided purely for completeness. #### `event.target` * Type: {EventTarget} The `EventTarget` dispatching the event. #### `event.timeStamp` * Type: {number} @@ -1199,7 +1199,7 @@ The millisecond timestamp when the `Event` object was created. #### `event.type` * Type: {string} @@ -1208,12 +1208,12 @@ The event type identifier. ### Class: `EventTarget` #### `eventTarget.addEventListener(type, listener[, options])` * `type` {string} @@ -1255,7 +1255,7 @@ target.removeEventListener('foo', handler, { capture: true }); #### `eventTarget.dispatchEvent(event)` * `event` {Object|Event} @@ -1269,7 +1269,7 @@ were registered. #### `eventTarget.removeEventListener(type, listener)` * `type` {string} @@ -1281,7 +1281,7 @@ Removes the `listener` from the list of handlers for event `type`. ### Class: `NodeEventTarget` * Extends: {EventTarget} @@ -1291,7 +1291,7 @@ that emulates a subset of the `EventEmitter` API. #### `nodeEventTarget.addListener(type, listener[, options])` * `type` {string} @@ -1308,7 +1308,7 @@ equivalent `EventEmitter` API. The only difference between `addListener()` and #### `nodeEventTarget.eventNames()` * Returns: {string[]} @@ -1318,7 +1318,7 @@ of event `type` names for which event listeners are registered. #### `nodeEventTarget.listenerCount(type)` * `type` {string} @@ -1330,7 +1330,7 @@ of event listeners registered for the `type`. #### `nodeEventTarget.off(type, listener)` * `type` {string} @@ -1342,7 +1342,7 @@ Node.js-speciic alias for `eventTarget.removeListener()`. #### `nodeEventTarget.on(type, listener[, options])` * `type` {string} @@ -1356,7 +1356,7 @@ Node.js-specific alias for `eventTarget.addListener()`. #### `nodeEventTarget.once(type, listener[, options])` * `type` {string} @@ -1371,7 +1371,7 @@ with the `once` option set to `true`. #### `nodeEventTarget.removeAllListeners([type])` * `type` {string} @@ -1382,7 +1382,7 @@ listeners. #### `nodeEventTarget.removeListener(type, listener)` * `type` {string} diff --git a/doc/api/fs.md b/doc/api/fs.md index dc6b11f25933d1..9969412dbdb48f 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -888,7 +888,7 @@ The numeric group identifier of the group that owns the file (POSIX). * {number|bigint} -A numeric device identifier if the file is considered "special". +A numeric device identifier if the file represents a device. ### `stats.size` @@ -2400,7 +2400,7 @@ Synchronous lchown(2). Returns `undefined`. ## `fs.lutimes(path, atime, mtime, callback)` * `path` {string|Buffer|URL} @@ -2419,7 +2419,7 @@ callback. ## `fs.lutimesSync(path, atime, mtime)` * `path` {string|Buffer|URL} @@ -5071,7 +5071,7 @@ no arguments upon success. ### `fsPromises.lutimes(path, atime, mtime)` * `path` {string|Buffer|URL} @@ -5841,10 +5841,10 @@ are available from `fs.constants`. On Windows, flags are translated to their equivalent ones where applicable, e.g. `O_WRONLY` to `FILE_GENERIC_WRITE`, or `O_EXCL|O_CREAT` to `CREATE_NEW`, as accepted by `CreateFileW`. -The exclusive flag `'x'` (`O_EXCL` flag in open(2)) ensures that path is newly -created. On POSIX systems, path is considered to exist even if it is a symlink -to a non-existent file. The exclusive flag may or may not work with network -file systems. +The exclusive flag `'x'` (`O_EXCL` flag in open(2)) causes the operation to +return an error if the path already exists. On POSIX, if the path is a symbolic +link, using `O_EXCL` returns an error even if the link is to a path that does +not exist. The exclusive flag may or may not work with network file systems. On Linux, positional writes don't work when the file is opened in append mode. The kernel ignores the position argument and always appends the data to diff --git a/doc/api/http.md b/doc/api/http.md index f6447ed35a3ba0..5429bdb297c7e9 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -113,7 +113,7 @@ http.get({ * {number} @@ -660,7 +660,7 @@ is finished. * {string} The request host. ### `request.protocol` * {string} The request protocol. @@ -1917,7 +1917,7 @@ const req = http.request({ @@ -1459,7 +1459,7 @@ server.on('stream', (stream) => { +> Stability: 0 - Deprecated: Use [`writable.writableLength`][] instead. + * {integer} This property shows the number of characters buffered for writing. The buffer @@ -1268,6 +1272,7 @@ Returns `true` if input is a version 6 IP address, otherwise returns `false`. [`socket.setEncoding()`]: #net_socket_setencoding_encoding [`socket.setTimeout()`]: #net_socket_settimeout_timeout_callback [`socket.setTimeout(timeout)`]: #net_socket_settimeout_timeout_callback +[`writable.writableLength`]: stream.html#stream_writable_writablelength [`writable.destroyed`]: stream.html#stream_writable_destroyed [`writable.destroy()`]: stream.html#stream_writable_destroy_error [`writable.end()`]: stream.html#stream_writable_end_chunk_encoding_callback diff --git a/doc/api/repl.md b/doc/api/repl.md index bf0727e47719ca..9c12d8e8c6782a 100644 --- a/doc/api/repl.md +++ b/doc/api/repl.md @@ -546,7 +546,7 @@ with REPL instances programmatically. ## `repl.builtinModules` * {string[]} diff --git a/doc/api/wasi.md b/doc/api/wasi.md index 46673af9e7fb34..9fe004ea599cfc 100644 --- a/doc/api/wasi.md +++ b/doc/api/wasi.md @@ -132,6 +132,23 @@ Attempt to begin execution of `instance` as a WASI command by invoking its If `start()` is called more than once, an exception is thrown. +### `wasi.initialize(instance)` + + +* `instance` {WebAssembly.Instance} + +Attempt to initialize `instance` as a WASI reactor by invoking its +`_initialize()` export, if it is present. If `instance` contains a `_start()` +export, then an exception is thrown. + +`initialize()` requires that `instance` exports a [`WebAssembly.Memory`][] named +`memory`. If `instance` does not have a `memory` export an exception is thrown. + +If `initialize()` is called more than once, an exception is thrown. + ### `wasi.wasiImport` Mark an object as not transferable. If `object` occurs in the transfer list of @@ -343,7 +343,7 @@ to `postMessage()` and no further arguments. ### Event: `'messageerror'` * `error` {Error} An Error object @@ -366,10 +366,10 @@ are part of the channel. @@ -736,7 +736,7 @@ All messages sent from the worker thread will be emitted before the ### Event: `'messageerror'` * `error` {Error} An Error object diff --git a/doc/api/zlib.md b/doc/api/zlib.md index a64ae57c7bc233..34adda9788dfe4 100644 --- a/doc/api/zlib.md +++ b/doc/api/zlib.md @@ -488,7 +488,7 @@ These advanced options are available for controlling decompression: diff --git a/doc/changelogs/CHANGELOG_V12.md b/doc/changelogs/CHANGELOG_V12.md index b1d41808f66c46..0255b907174629 100644 --- a/doc/changelogs/CHANGELOG_V12.md +++ b/doc/changelogs/CHANGELOG_V12.md @@ -11,6 +11,7 @@ +12.18.2
12.18.1
12.18.0
12.17.0
@@ -62,6 +63,21 @@ * [io.js](CHANGELOG_IOJS.md) * [Archive](CHANGELOG_ARCHIVE.md) + +## 2020-06-30, Version 12.18.2 'Erbium' (LTS), @BethGriggs + +### Notable changes + +* **deps**: V8: backport fb26d0bb1835 (Matheus Marchini) [#33573](https://github.com/nodejs/node/pull/33573) + * Fixes memory leak in `PrototypeUsers::Add` +* **src**: use symbol to store `AsyncWrap` resource (Anna Henningsen) [#31745](https://github.com/nodejs/node/pull/31745) + * Fixes reported memory leak in [#33468](https://github.com/nodejs/node/issues/33468) + +### Commits + +* [[`97a3f7b702`](https://github.com/nodejs/node/commit/97a3f7b702)] - **deps**: V8: backport fb26d0bb1835 (Matheus Marchini) [#33573](https://github.com/nodejs/node/pull/33573) +* [[`30b0339061`](https://github.com/nodejs/node/commit/30b0339061)] - **src**: use symbol to store `AsyncWrap` resource (Anna Henningsen) [#31745](https://github.com/nodejs/node/pull/31745) + ## 2020-06-17, Version 12.18.1 'Erbium' (LTS), @codebytere diff --git a/doc/changelogs/CHANGELOG_V14.md b/doc/changelogs/CHANGELOG_V14.md index 2646eae48ca714..cdcb574cb6b060 100644 --- a/doc/changelogs/CHANGELOG_V14.md +++ b/doc/changelogs/CHANGELOG_V14.md @@ -10,6 +10,7 @@ +14.5.0
14.4.0
14.3.0
14.2.0
@@ -35,6 +36,370 @@ * [io.js](CHANGELOG_IOJS.md) * [Archive](CHANGELOG_ARCHIVE.md) + +## 2020-06-30, Version 14.5.0 (Current), @codebytere + +### Notable Changes + +#### V8 engine is updated to version 8.3 + +This version includes performance improvements and now allows WebAssembly +modules to request memories up to 4GB in size. + +For more information, have a look at the [official V8 blog post](https://v8.dev/blog/v8-release-83). + +Contributed by Matheus Marchini and Michaël Zasso - [#33376](https://github.com/nodejs/node/pull/33376). + +#### Initial experimental implementation of EventTarget + +This version introduces an new experimental API `EventTarget`, which provides a DOM interface implemented by objects that can receive events and may have listeners for them. + +It is an adaptation of the Web API EventTarget. + +Example Usage: + +```js +const target = getEventTargetSomehow(); + +target.addEventListener('foo', (event) => { + console.log('foo event happened!'); +}); +``` + +Contributed by James Snell - [#33556](https://github.com/nodejs/node/pull/33556). + +### Semver-Minor Commits + +* [[`4ccaa537d4`](https://github.com/nodejs/node/commit/4ccaa537d4)] - **(SEMVER-MINOR)** **build**: reset embedder string to "-node.0" (Michaël Zasso) [#33376](https://github.com/nodejs/node/pull/33376) +* [[`d194d20828`](https://github.com/nodejs/node/commit/d194d20828)] - **(SEMVER-MINOR)** **cli**: add alias for report-directory to make it consistent (AshCripps) [#33587](https://github.com/nodejs/node/pull/33587) +* [[`70398dbf60`](https://github.com/nodejs/node/commit/70398dbf60)] - **(SEMVER-MINOR)** **crypto**: allow KeyObjects in postMessage (Tobias Nießen) [#33360](https://github.com/nodejs/node/pull/33360) +* [[`9b7ba87aa6`](https://github.com/nodejs/node/commit/9b7ba87aa6)] - **(SEMVER-MINOR)** **deps**: V8: cherry-pick 0d6debcc5f08 (Michaël Zasso) [#33376](https://github.com/nodejs/node/pull/33376) +* [[`ce1a1ae621`](https://github.com/nodejs/node/commit/ce1a1ae621)] - **(SEMVER-MINOR)** **deps**: V8: cherry-pick 74d50c5063b3 (Michaël Zasso) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`aa7267a344`](https://github.com/nodejs/node/commit/aa7267a344)] - **(SEMVER-MINOR)** **deps**: V8: cherry-pick e29c62b74854 (Michaël Zasso) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`1512757a22`](https://github.com/nodejs/node/commit/1512757a22)] - **(SEMVER-MINOR)** **deps**: V8: cherry-pick 3f8dc4b2e5ba (Michaël Zasso) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`3d9cf4bde6`](https://github.com/nodejs/node/commit/3d9cf4bde6)] - **(SEMVER-MINOR)** **deps**: V8: cherry-pick e1eac1b16c96 (Milad Farazmand) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`cdeade308e`](https://github.com/nodejs/node/commit/cdeade308e)] - **(SEMVER-MINOR)** **deps**: fix V8 8.3 on SmartOS (Colin Ihrig) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`883840bc17`](https://github.com/nodejs/node/commit/883840bc17)] - **(SEMVER-MINOR)** **deps**: patch V8 to run on Xcode 8 (Matheus Marchini) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`3831a541fb`](https://github.com/nodejs/node/commit/3831a541fb)] - **(SEMVER-MINOR)** **deps**: V8: silence irrelevant warnings (Michaël Zasso) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`e2fc08f216`](https://github.com/nodejs/node/commit/e2fc08f216)] - **(SEMVER-MINOR)** **deps**: make v8.h compatible with VS2015 (Joao Reis) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`74b623bd51`](https://github.com/nodejs/node/commit/74b623bd51)] - **(SEMVER-MINOR)** **deps**: V8: forward declaration of `Rtl\*FunctionTable` (Refael Ackermann) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`0f5764aec2`](https://github.com/nodejs/node/commit/0f5764aec2)] - **(SEMVER-MINOR)** **deps**: V8: patch register-arm64.h (Refael Ackermann) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`be773fc3cf`](https://github.com/nodejs/node/commit/be773fc3cf)] - **(SEMVER-MINOR)** **deps**: patch V8 to run on older XCode versions (Ujjwal Sharma) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`7aa41c6e6f`](https://github.com/nodejs/node/commit/7aa41c6e6f)] - **(SEMVER-MINOR)** **deps**: V8: un-cherry-pick bd019bd (Refael Ackermann) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`ce901e3906`](https://github.com/nodejs/node/commit/ce901e3906)] - **(SEMVER-MINOR)** **deps**: update V8 dtrace & postmortem metadata (Colin Ihrig) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`1123425dd1`](https://github.com/nodejs/node/commit/1123425dd1)] - **(SEMVER-MINOR)** **deps**: update V8 to 8.3.110.9 (Michaël Zasso) [#33376](https://github.com/nodejs/node/pull/33376) +* [[`1c70b18da8`](https://github.com/nodejs/node/commit/1c70b18da8)] - **(SEMVER-MINOR)** **events**: initial implementation of experimental EventTarget (James M Snell) [#33556](https://github.com/nodejs/node/pull/33556) +* [[`cf97c56dab`](https://github.com/nodejs/node/commit/cf97c56dab)] - **(SEMVER-MINOR)** **fs**: implement lutimes (Maël Nison) [#33399](https://github.com/nodejs/node/pull/33399) +* [[`a24b8df7fb`](https://github.com/nodejs/node/commit/a24b8df7fb)] - **(SEMVER-MINOR)** **http**: expose host and protocol on ClientRequest (wenningplus) [#33803](https://github.com/nodejs/node/pull/33803) +* [[`507a2ef31c`](https://github.com/nodejs/node/commit/507a2ef31c)] - **(SEMVER-MINOR)** **http**: add maxTotalSockets to agent class (rickyes) [#33617](https://github.com/nodejs/node/pull/33617) +* [[`e1e3ae1567`](https://github.com/nodejs/node/commit/e1e3ae1567)] - **(SEMVER-MINOR)** **http**: return this from OutgoingMessage#destroy() (Colin Ihrig) [#32789](https://github.com/nodejs/node/pull/32789) +* [[`d87031def4`](https://github.com/nodejs/node/commit/d87031def4)] - **(SEMVER-MINOR)** **http**: return this from ClientRequest#destroy() (Colin Ihrig) [#32789](https://github.com/nodejs/node/pull/32789) +* [[`c7959557db`](https://github.com/nodejs/node/commit/c7959557db)] - **(SEMVER-MINOR)** **http**: return this from IncomingMessage#destroy() (Colin Ihrig) [#32789](https://github.com/nodejs/node/pull/32789) +* [[`a3a0c0e0fc`](https://github.com/nodejs/node/commit/a3a0c0e0fc)] - **(SEMVER-MINOR)** **http**: added scheduling option to http agent (delvedor) [#33278](https://github.com/nodejs/node/pull/33278) +* [[`e3fd2f5a48`](https://github.com/nodejs/node/commit/e3fd2f5a48)] - **(SEMVER-MINOR)** **http2**: return this for Http2ServerRequest#setTimeout (Pranshu Srivastava) [#33994](https://github.com/nodejs/node/pull/33994) +* [[`7ccb021ffc`](https://github.com/nodejs/node/commit/7ccb021ffc)] - **(SEMVER-MINOR)** **http2**: do not modify explicity set date headers (Pranshu Srivastava) [#33160](https://github.com/nodejs/node/pull/33160) +* [[`f66bb57c13`](https://github.com/nodejs/node/commit/f66bb57c13)] - **(SEMVER-MINOR)** **process**: add unhandled-rejection throw and warn-with-error-code (Dan Fabulich) [#33475](https://github.com/nodejs/node/pull/33475) +* [[`33020256de`](https://github.com/nodejs/node/commit/33020256de)] - **(SEMVER-MINOR)** **src**: store key data in separate class (Tobias Nießen) [#33360](https://github.com/nodejs/node/pull/33360) +* [[`44b9d08344`](https://github.com/nodejs/node/commit/44b9d08344)] - **(SEMVER-MINOR)** **src**: add NativeKeyObject base class (Tobias Nießen) [#33360](https://github.com/nodejs/node/pull/33360) +* [[`13e633873e`](https://github.com/nodejs/node/commit/13e633873e)] - **(SEMVER-MINOR)** **src**: rename internal key handles to KeyObjectHandle (Tobias Nießen) [#33360](https://github.com/nodejs/node/pull/33360) +* [[`a3d0b0e2d7`](https://github.com/nodejs/node/commit/a3d0b0e2d7)] - **(SEMVER-MINOR)** **src**: add equality operators for BaseObjectPtr (Anna Henningsen) [#33772](https://github.com/nodejs/node/pull/33772) +* [[`0720d1ff24`](https://github.com/nodejs/node/commit/0720d1ff24)] - **(SEMVER-MINOR)** **src**: introduce BaseObject base FunctionTemplate (Anna Henningsen) [#33772](https://github.com/nodejs/node/pull/33772) +* [[`5362fef3f5`](https://github.com/nodejs/node/commit/5362fef3f5)] - **(SEMVER-MINOR)** **src**: add public APIs to manage v8::TracingController (Anna Henningsen) [#33850](https://github.com/nodejs/node/pull/33850) +* [[`db2d1ca51b`](https://github.com/nodejs/node/commit/db2d1ca51b)] - **(SEMVER-MINOR)** **stream**: runtime deprecate Transform.\_transformState (Robert Nagy) [#32763](https://github.com/nodejs/node/pull/32763) +* [[`b6da77756e`](https://github.com/nodejs/node/commit/b6da77756e)] - **(SEMVER-MINOR)** **test**: stop testing --interpreted-frames-native-stack for s390x (Michaël Zasso) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`5cad007408`](https://github.com/nodejs/node/commit/5cad007408)] - **(SEMVER-MINOR)** **test**: fix test-zlib-unused-weak on V8 8.2 (Matheus Marchini) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`2c59f9bbe2`](https://github.com/nodejs/node/commit/2c59f9bbe2)] - **(SEMVER-MINOR)** **tools**: update V8 gypfiles for V8 8.3 (Michaël Zasso) [#32831](https://github.com/nodejs/node/pull/32831) +* [[`0ef6e0426f`](https://github.com/nodejs/node/commit/0ef6e0426f)] - **(SEMVER-MINOR)** **win**: allow skipping the supported platform check (João Reis) [#33176](https://github.com/nodejs/node/pull/33176) +* [[`4e42eb5e14`](https://github.com/nodejs/node/commit/4e42eb5e14)] - **(SEMVER-MINOR)** **worker**: add public method for marking objects as untransferable (Anna Henningsen) [#33979](https://github.com/nodejs/node/pull/33979) +* [[`4a37180b09`](https://github.com/nodejs/node/commit/4a37180b09)] - **(SEMVER-MINOR)** **worker**: emit `'messagerror'` events for failed deserialization (Anna Henningsen) [#33772](https://github.com/nodejs/node/pull/33772) +* [[`9692208a91`](https://github.com/nodejs/node/commit/9692208a91)] - **(SEMVER-MINOR)** **worker**: allow passing JS wrapper objects via postMessage (Anna Henningsen) [#33772](https://github.com/nodejs/node/pull/33772) +* [[`eaccf842eb`](https://github.com/nodejs/node/commit/eaccf842eb)] - **(SEMVER-MINOR)** **worker**: allow transferring/cloning generic BaseObjects (Anna Henningsen) [#33772](https://github.com/nodejs/node/pull/33772) +* [[`5b1fd10048`](https://github.com/nodejs/node/commit/5b1fd10048)] - **(SEMVER-MINOR)** **worker,fs**: make FileHandle transferable (Anna Henningsen) [#33772](https://github.com/nodejs/node/pull/33772) +* [[`c1f625fe1f`](https://github.com/nodejs/node/commit/c1f625fe1f)] - **(SEMVER-MINOR)** **zlib**: add `maxOutputLength` option (unknown) [#33516](https://github.com/nodejs/node/pull/33516) + +### Semver-Patch Commits + +* [[`ef05e1526a`](https://github.com/nodejs/node/commit/ef05e1526a)] - **async_hooks**: callback trampoline for MakeCallback (Stephen Belanger) [#33801](https://github.com/nodejs/node/pull/33801) +* [[`0eed22d6ed`](https://github.com/nodejs/node/commit/0eed22d6ed)] - **benchmark**: fix EventTarget benchmark (Brian White) [#34015](https://github.com/nodejs/node/pull/34015) +* [[`bf56decc79`](https://github.com/nodejs/node/commit/bf56decc79)] - **benchmark**: fix async-resource benchmark (Anna Henningsen) [#33642](https://github.com/nodejs/node/pull/33642) +* [[`26269be510`](https://github.com/nodejs/node/commit/26269be510)] - **benchmark**: fixing http\_server\_for\_chunky\_client.js (Adrian Estrada) [#33271](https://github.com/nodejs/node/pull/33271) +* [[`c31d5145d9`](https://github.com/nodejs/node/commit/c31d5145d9)] - **buffer**: remove hoisted variable (Nikolai Vavilov) [#33470](https://github.com/nodejs/node/pull/33470) +* [[`43fd4746e9`](https://github.com/nodejs/node/commit/43fd4746e9)] - **build**: configure byte order for mips targets (Ben Noordhuis) [#33898](https://github.com/nodejs/node/pull/33898) +* [[`ebb2fb81fa`](https://github.com/nodejs/node/commit/ebb2fb81fa)] - **build**: add target specific build\_type variable (Daniel Bevenius) [#33925](https://github.com/nodejs/node/pull/33925) +* [[`e8f7670b77`](https://github.com/nodejs/node/commit/e8f7670b77)] - **build**: add LINT\_CPP\_FILES to checkimports check (Daniel Bevenius) [#33697](https://github.com/nodejs/node/pull/33697) +* [[`1355d35a61`](https://github.com/nodejs/node/commit/1355d35a61)] - **build**: output dots in "Build from tarball" action (Michaël Zasso) [#33696](https://github.com/nodejs/node/pull/33696) +* [[`153f5eda0e`](https://github.com/nodejs/node/commit/153f5eda0e)] - **build**: fix compiling addons with older versions of Node.js (Richard Lau) [#33688](https://github.com/nodejs/node/pull/33688) +* [[`7a4c689912`](https://github.com/nodejs/node/commit/7a4c689912)] - **build**: fix node.gyp config (gengjiawen) [#33685](https://github.com/nodejs/node/pull/33685) +* [[`1f7a65529d`](https://github.com/nodejs/node/commit/1f7a65529d)] - **build**: add --v8-lite-mode flag (Maciej Kacper Jagiełło) [#33541](https://github.com/nodejs/node/pull/33541) +* [[`3ac05b75ca`](https://github.com/nodejs/node/commit/3ac05b75ca)] - **build**: zlib build error on Windows on Arm (Richard Townsend) [#33511](https://github.com/nodejs/node/pull/33511) +* [[`fc032247e0`](https://github.com/nodejs/node/commit/fc032247e0)] - **build**: fix GetCurrentThreadStackLimits error on Windows on Arm (Richard Townsend) [#33511](https://github.com/nodejs/node/pull/33511) +* [[`e393e879cf`](https://github.com/nodejs/node/commit/e393e879cf)] - **build**: fix python-version selection with actions (Richard Lau) [#33589](https://github.com/nodejs/node/pull/33589) +* [[`8ed25eda60`](https://github.com/nodejs/node/commit/8ed25eda60)] - **build**: fix inability to detect correct python command in configure (Eli Schwartz) [#32925](https://github.com/nodejs/node/pull/32925) +* [[`8b887c4462`](https://github.com/nodejs/node/commit/8b887c4462)] - **build**: fix makefile script on windows (Thomas) [#33136](https://github.com/nodejs/node/pull/33136) +* [[`85ce30fe57`](https://github.com/nodejs/node/commit/85ce30fe57)] - **build**: run full test suite in ASAN action (Anna Henningsen) [#33170](https://github.com/nodejs/node/pull/33170) +* [[`71c4d9174e`](https://github.com/nodejs/node/commit/71c4d9174e)] - **build,win**: add support for MSVC cross-compilation (Richard Townsend) [#32867](https://github.com/nodejs/node/pull/32867) +* [[`ac7946eb08`](https://github.com/nodejs/node/commit/ac7946eb08)] - **build,win**: add support for MSVC cross-compilation (Richard Townsend) [#32867](https://github.com/nodejs/node/pull/32867) +* [[`22b5ec19a2`](https://github.com/nodejs/node/commit/22b5ec19a2)] - **cli**: support --experimental-top-level-await in NODE\_OPTIONS (Dan Fabulich) [#33495](https://github.com/nodejs/node/pull/33495) +* [[`0a7f13e26b`](https://github.com/nodejs/node/commit/0a7f13e26b)] - **configure**: account for CLANG\_VENDOR when checking for llvm version (Nathan Blair) [#33860](https://github.com/nodejs/node/pull/33860) +* [[`a6a74ae1d5`](https://github.com/nodejs/node/commit/a6a74ae1d5)] - **console**: name console functions appropriately (Ruben Bridgewater) [#33524](https://github.com/nodejs/node/pull/33524) +* [[`9d24f71d45`](https://github.com/nodejs/node/commit/9d24f71d45)] - **console**: mark special console properties as non-enumerable (Ruben Bridgewater) [#33524](https://github.com/nodejs/node/pull/33524) +* [[`bce99867f7`](https://github.com/nodejs/node/commit/bce99867f7)] - **console**: remove dead code (Ruben Bridgewater) [#33524](https://github.com/nodejs/node/pull/33524) +* [[`134ed0eea3`](https://github.com/nodejs/node/commit/134ed0eea3)] - **crypto**: fix wrong error message (Ben Bucksch) [#33482](https://github.com/nodejs/node/pull/33482) +* [[`5957afc31a`](https://github.com/nodejs/node/commit/5957afc31a)] - **deps**: V8: cherry-pick 767e65f945e7 (Gus Caplan) [#33859](https://github.com/nodejs/node/pull/33859) +* [[`162092ea2a`](https://github.com/nodejs/node/commit/162092ea2a)] - **deps**: V8: cherry-pick eec10a2fd8fa (Stephen Belanger) [#33778](https://github.com/nodejs/node/pull/33778) +* [[`499c7402b1`](https://github.com/nodejs/node/commit/499c7402b1)] - **deps**: V8: cherry-pick 4e1bf2bc92bd (Milad Farazmand) [#33702](https://github.com/nodejs/node/pull/33702) +* [[`0524c7ad5d`](https://github.com/nodejs/node/commit/0524c7ad5d)] - **deps**: V8: cherry-pick b5939c758924 (Milad Farazmand) [#33702](https://github.com/nodejs/node/pull/33702) +* [[`7ad6cfa005`](https://github.com/nodejs/node/commit/7ad6cfa005)] - **deps**: V8: backport 22014de00115 (Joyee Cheung) [#33300](https://github.com/nodejs/node/pull/33300) +* [[`817befde11`](https://github.com/nodejs/node/commit/817befde11)] - **deps**: V8: backport bb9f0c2b2fe9 (Joyee Cheung) [#33300](https://github.com/nodejs/node/pull/33300) +* [[`8f82692999`](https://github.com/nodejs/node/commit/8f82692999)] - **deps**: V8: backport ea0719b8ed08 (Joyee Cheung) [#33300](https://github.com/nodejs/node/pull/33300) +* [[`773d76ea04`](https://github.com/nodejs/node/commit/773d76ea04)] - **deps**: uvwasi: cherry-pick 9e75217 (Colin Ihrig) [#33521](https://github.com/nodejs/node/pull/33521) +* [[`748720e7b6`](https://github.com/nodejs/node/commit/748720e7b6)] - **deps**: V8: cherry-pick 548f6c81d424 (Dominykas Blyžė) [#33484](https://github.com/nodejs/node/pull/33484) +* [[`b0bce9b2a4`](https://github.com/nodejs/node/commit/b0bce9b2a4)] - **deps**: update node-inspect to v2.0.0 (Jan Krems) [#33447](https://github.com/nodejs/node/pull/33447) +* [[`ac459b34e7`](https://github.com/nodejs/node/commit/ac459b34e7)] - **deps**: V8: cherry-pick fa3e37e511ee (Anna Henningsen) [#32885](https://github.com/nodejs/node/pull/32885) +* [[`2bc79f5b50`](https://github.com/nodejs/node/commit/2bc79f5b50)] - **deps**: V8: cherry-pick 2db93c023379 (Anna Henningsen) [#32885](https://github.com/nodejs/node/pull/32885) +* [[`8d47e8bf7b`](https://github.com/nodejs/node/commit/8d47e8bf7b)] - **deps**: update to uvwasi 0.0.9 (Colin Ihrig) [#33445](https://github.com/nodejs/node/pull/33445) +* [[`9d6fd4599d`](https://github.com/nodejs/node/commit/9d6fd4599d)] - **deps**: upgrade to libuv 1.38.0 (Colin Ihrig) [#33446](https://github.com/nodejs/node/pull/33446) +* [[`33a662ad2d`](https://github.com/nodejs/node/commit/33a662ad2d)] - **deps**: update icu to include tzdata2020a (Shelley Vohr) [#33362](https://github.com/nodejs/node/pull/33362) +* [[`f151bde312`](https://github.com/nodejs/node/commit/f151bde312)] - **(SEMVER-MINOR)** **dgram**: allow typed arrays in .send() (Sarat Addepalli) [#22413](https://github.com/nodejs/node/pull/22413) +* [[`d4442b15bf`](https://github.com/nodejs/node/commit/d4442b15bf)] - **dns**: make dns.Resolver timeout configurable (Ben Noordhuis) [#33472](https://github.com/nodejs/node/pull/33472) +* [[`eb55d9e4b1`](https://github.com/nodejs/node/commit/eb55d9e4b1)] - **dns**: use ternary operator simplify statement (Wenning Zhang) [#33234](https://github.com/nodejs/node/pull/33234) +* [[`d61de303c9`](https://github.com/nodejs/node/commit/d61de303c9)] - **doc**: specify maxHeaderCount alias for maxHeaderListPairs (Pranshu Srivastava) [#33519](https://github.com/nodejs/node/pull/33519) +* [[`4323346f5a`](https://github.com/nodejs/node/commit/4323346f5a)] - **doc**: add allowed info strings to style guide (Derek Lewis) [#34024](https://github.com/nodejs/node/pull/34024) +* [[`0dbad26db4`](https://github.com/nodejs/node/commit/0dbad26db4)] - **doc**: fix lexical sorting of bottom-references in http doc (Pranshu Srivastava) [#34007](https://github.com/nodejs/node/pull/34007) +* [[`ec07e61f6a`](https://github.com/nodejs/node/commit/ec07e61f6a)] - **doc**: clarify thread-safe function references (legendecas) [#33871](https://github.com/nodejs/node/pull/33871) +* [[`5a4dcfcf4c`](https://github.com/nodejs/node/commit/5a4dcfcf4c)] - **doc**: use npm team for npm upgrades in collaborator guide (Rich Trott) [#33999](https://github.com/nodejs/node/pull/33999) +* [[`319707add2`](https://github.com/nodejs/node/commit/319707add2)] - **doc**: correct default values in http2 docs (Rich Trott) [#33997](https://github.com/nodejs/node/pull/33997) +* [[`b4d0eebe7c`](https://github.com/nodejs/node/commit/b4d0eebe7c)] - **doc**: use a single space between sentences (Rich Trott) [#33995](https://github.com/nodejs/node/pull/33995) +* [[`24105a7f44`](https://github.com/nodejs/node/commit/24105a7f44)] - **doc**: piping from async generators using pipeline() (WilliamConnatser) [#33992](https://github.com/nodejs/node/pull/33992) +* [[`9590d81349`](https://github.com/nodejs/node/commit/9590d81349)] - **doc**: revise text in dns module documentation introduction (Rich Trott) [#33986](https://github.com/nodejs/node/pull/33986) +* [[`ed26e8e2fb`](https://github.com/nodejs/node/commit/ed26e8e2fb)] - **doc**: update fs.md (Shakil-Shahadat) [#33820](https://github.com/nodejs/node/pull/33820) +* [[`6dc541778e`](https://github.com/nodejs/node/commit/6dc541778e)] - **doc**: warn that tls.connect() doesn't set SNI (Alba Mendez) [#33855](https://github.com/nodejs/node/pull/33855) +* [[`d9c78ac270`](https://github.com/nodejs/node/commit/d9c78ac270)] - **doc**: fix lexical sorting of bottom-references in dns doc (Rich Trott) [#33987](https://github.com/nodejs/node/pull/33987) +* [[`98228b25af`](https://github.com/nodejs/node/commit/98228b25af)] - **doc**: change "GitHub Repo" to "Code repository" (Rich Trott) [#33985](https://github.com/nodejs/node/pull/33985) +* [[`645cd481e9`](https://github.com/nodejs/node/commit/645cd481e9)] - **doc**: use Class: consistently (Rich Trott) [#33978](https://github.com/nodejs/node/pull/33978) +* [[`72e2fd315e`](https://github.com/nodejs/node/commit/72e2fd315e)] - **doc**: update WASM code sample (Pragyan Das) [#33626](https://github.com/nodejs/node/pull/33626) +* [[`894ec7d5c6`](https://github.com/nodejs/node/commit/894ec7d5c6)] - **doc**: standardize on sentence case for headers (Rich Trott) [#33889](https://github.com/nodejs/node/pull/33889) +* [[`61de26a2f3`](https://github.com/nodejs/node/commit/61de26a2f3)] - **doc**: link readable.\_read in stream.md (Pranshu Srivastava) [#33767](https://github.com/nodejs/node/pull/33767) +* [[`76fe2a93a9`](https://github.com/nodejs/node/commit/76fe2a93a9)] - **doc**: specify default encoding in writable.write (Pranshu Srivastava) [#33765](https://github.com/nodejs/node/pull/33765) +* [[`2427d6544b`](https://github.com/nodejs/node/commit/2427d6544b)] - **doc**: move --force-context-aware option in cli.md (Daniel Bevenius) [#33823](https://github.com/nodejs/node/pull/33823) +* [[`fdaf0ca550`](https://github.com/nodejs/node/commit/fdaf0ca550)] - **doc**: add snippet for AsyncResource and EE integration (Andrey Pechkurov) [#33751](https://github.com/nodejs/node/pull/33751) +* [[`8f5ac3865c`](https://github.com/nodejs/node/commit/8f5ac3865c)] - **doc**: use single quotes in --tls-cipher-list (Daniel Bevenius) [#33709](https://github.com/nodejs/node/pull/33709) +* [[`922c13c6bb`](https://github.com/nodejs/node/commit/922c13c6bb)] - **doc**: fix misc. mislabeled code block info strings (Derek Lewis) [#33548](https://github.com/nodejs/node/pull/33548) +* [[`114d77e30b`](https://github.com/nodejs/node/commit/114d77e30b)] - **doc**: standardize constructor doc header layout (Rich Trott) [#33781](https://github.com/nodejs/node/pull/33781) +* [[`b10d20385e`](https://github.com/nodejs/node/commit/b10d20385e)] - **doc**: update V8 inspector example (Colin Ihrig) [#33758](https://github.com/nodejs/node/pull/33758) +* [[`785760448b`](https://github.com/nodejs/node/commit/785760448b)] - **doc**: fix linting in doc-style-guide.md (Pranshu Srivastava) [#33787](https://github.com/nodejs/node/pull/33787) +* [[`2288840a8f`](https://github.com/nodejs/node/commit/2288840a8f)] - **doc**: remove "currently" from repl.md (Rich Trott) [#33756](https://github.com/nodejs/node/pull/33756) +* [[`cc0f827182`](https://github.com/nodejs/node/commit/cc0f827182)] - **doc**: remove "currently" from events.md (Rich Trott) [#33756](https://github.com/nodejs/node/pull/33756) +* [[`4a738e6462`](https://github.com/nodejs/node/commit/4a738e6462)] - **doc**: remove "currently" from vm.md (Rich Trott) [#33756](https://github.com/nodejs/node/pull/33756) +* [[`bb29a8177f`](https://github.com/nodejs/node/commit/bb29a8177f)] - **doc**: remove "currently" from addons.md (Rich Trott) [#33756](https://github.com/nodejs/node/pull/33756) +* [[`f0597d9a6e`](https://github.com/nodejs/node/commit/f0597d9a6e)] - **doc**: remove "currently" from util.md (Rich Trott) [#33756](https://github.com/nodejs/node/pull/33756) +* [[`095efac2ef`](https://github.com/nodejs/node/commit/095efac2ef)] - **doc**: add formatting for version numbers to doc-style-guide.md (Rich Trott) [#33755](https://github.com/nodejs/node/pull/33755) +* [[`843ab3eb94`](https://github.com/nodejs/node/commit/843ab3eb94)] - **doc**: change "pre Node.js v0.10" to "prior to Node.js 0.10" (Rich Trott) [#33754](https://github.com/nodejs/node/pull/33754) +* [[`b565897996`](https://github.com/nodejs/node/commit/b565897996)] - **doc**: remove default parameter value from header (Rich Trott) [#33752](https://github.com/nodejs/node/pull/33752) +* [[`ebf2378731`](https://github.com/nodejs/node/commit/ebf2378731)] - **doc**: fix typo in cli.md for report-dir (AshCripps) [#33725](https://github.com/nodejs/node/pull/33725) +* [[`16b69818ba`](https://github.com/nodejs/node/commit/16b69818ba)] - **doc**: remove shell dollar signs without output (Nick Schonning) [#33692](https://github.com/nodejs/node/pull/33692) +* [[`b3d500f949`](https://github.com/nodejs/node/commit/b3d500f949)] - **doc**: add lint disabling comment for collaborator list (Rich Trott) [#33719](https://github.com/nodejs/node/pull/33719) +* [[`61bb789fa0`](https://github.com/nodejs/node/commit/61bb789fa0)] - **doc**: use consistent Default: in events (Colin Ihrig) [#33678](https://github.com/nodejs/node/pull/33678) +* [[`1e4edd8d75`](https://github.com/nodejs/node/commit/1e4edd8d75)] - **doc**: remove "it is important" (Colin Ihrig) [#33678](https://github.com/nodejs/node/pull/33678) +* [[`cb8b9ec98a`](https://github.com/nodejs/node/commit/cb8b9ec98a)] - **doc**: fix urls to avoid redirection (sapics) [#33614](https://github.com/nodejs/node/pull/33614) +* [[`c184929975`](https://github.com/nodejs/node/commit/c184929975)] - **doc**: improve buffer.md a tiny bit (Tom Nagle) [#33547](https://github.com/nodejs/node/pull/33547) +* [[`6d25b5753a`](https://github.com/nodejs/node/commit/6d25b5753a)] - **doc**: normalize Markdown code block info strings (Derek Lewis) [#33542](https://github.com/nodejs/node/pull/33542) +* [[`e7c3890901`](https://github.com/nodejs/node/commit/e7c3890901)] - **doc**: normalize JavaScript code block info strings (Derek Lewis) [#33531](https://github.com/nodejs/node/pull/33531) +* [[`352adcb437`](https://github.com/nodejs/node/commit/352adcb437)] - **doc**: outline when origin is set to unhandledRejection (Ruben Bridgewater) [#33530](https://github.com/nodejs/node/pull/33530) +* [[`94177dae8e`](https://github.com/nodejs/node/commit/94177dae8e)] - **doc**: add --experimental-top-level-await to man page (Colin Ihrig) [#33529](https://github.com/nodejs/node/pull/33529) +* [[`8e3a0d7773`](https://github.com/nodejs/node/commit/8e3a0d7773)] - **doc**: update ```txt ```fandamental and ```raw code blocks (Zeke Sikelianos) [#33028](https://github.com/nodejs/node/pull/33028) +* [[`4cc391b495`](https://github.com/nodejs/node/commit/4cc391b495)] - **doc**: normalize shell code block info strings (Derek Lewis) [#33486](https://github.com/nodejs/node/pull/33486) +* [[`24ada7acd4`](https://github.com/nodejs/node/commit/24ada7acd4)] - **doc**: normalize C code block info strings (Derek Lewis) [#33507](https://github.com/nodejs/node/pull/33507) +* [[`8c04e61f16`](https://github.com/nodejs/node/commit/8c04e61f16)] - **doc**: normalize Bash code block info strings (Derek Lewis) [#33510](https://github.com/nodejs/node/pull/33510) +* [[`7c87fc1c48`](https://github.com/nodejs/node/commit/7c87fc1c48)] - **doc**: correct tls.rootCertificates to match implementation (Eric Bickle) [#33313](https://github.com/nodejs/node/pull/33313) +* [[`0c2b7c0adf`](https://github.com/nodejs/node/commit/0c2b7c0adf)] - **doc**: fix Buffer.from(object) documentation (Nikolai Vavilov) [#33327](https://github.com/nodejs/node/pull/33327) +* [[`de608c3124`](https://github.com/nodejs/node/commit/de608c3124)] - **doc**: fix typo in pathToFileURL example (Antoine du HAMEL) [#33418](https://github.com/nodejs/node/pull/33418) +* [[`23cf39ab78`](https://github.com/nodejs/node/commit/23cf39ab78)] - **doc**: eliminate dead space in API section's sidebar (John Gardner) [#33469](https://github.com/nodejs/node/pull/33469) +* [[`95e7a80cbf`](https://github.com/nodejs/node/commit/95e7a80cbf)] - **doc**: mention --experimental-top-level-await flag (dfabulich) [#33473](https://github.com/nodejs/node/pull/33473) +* [[`64410f206e`](https://github.com/nodejs/node/commit/64410f206e)] - **doc**: normalize C++ code block info strings (Derek Lewis) [#33483](https://github.com/nodejs/node/pull/33483) +* [[`c8f79d80a4`](https://github.com/nodejs/node/commit/c8f79d80a4)] - **doc**: fixed a grammatical error in path.md (Deep310) [#33489](https://github.com/nodejs/node/pull/33489) +* [[`500bad1103`](https://github.com/nodejs/node/commit/500bad1103)] - **doc**: correct CommonJS self-resolve spec (Guy Bedford) [#33391](https://github.com/nodejs/node/pull/33391) +* [[`4e74f050a7`](https://github.com/nodejs/node/commit/4e74f050a7)] - **doc**: fix readline key binding documentation (Ruben Bridgewater) [#33361](https://github.com/nodejs/node/pull/33361) +* [[`7c553cd4f6`](https://github.com/nodejs/node/commit/7c553cd4f6)] - **doc**: claim ABI version 85 for Electron 11 (Shelley Vohr) [#33375](https://github.com/nodejs/node/pull/33375) +* [[`4cc5e9668f`](https://github.com/nodejs/node/commit/4cc5e9668f)] - **doc**: document module.path (Antoine du Hamel) [#33323](https://github.com/nodejs/node/pull/33323) +* [[`c1fe152132`](https://github.com/nodejs/node/commit/c1fe152132)] - **doc**: add fs.open() multiple constants example (Ethan Arrowood) [#33281](https://github.com/nodejs/node/pull/33281) +* [[`b02cfef510`](https://github.com/nodejs/node/commit/b02cfef510)] - **doc**: fix typos in handle scope descriptions (Tobias Nießen) [#33267](https://github.com/nodejs/node/pull/33267) +* [[`d4e871424f`](https://github.com/nodejs/node/commit/d4e871424f)] - **doc**: update function description for `decipher.setAAD` (Jonathan Buhacoff) [#33095](https://github.com/nodejs/node/pull/33095) +* [[`e2484b24cb`](https://github.com/nodejs/node/commit/e2484b24cb)] - **doc**: add comment about highWaterMark limit (Benjamin Gruenbaum) [#33432](https://github.com/nodejs/node/pull/33432) +* [[`b8c88891a6`](https://github.com/nodejs/node/commit/b8c88891a6)] - **doc**: clarify about the Node.js-only extensions in perf\_hooks (Joyee Cheung) [#33199](https://github.com/nodejs/node/pull/33199) +* [[`d1efdb29b4`](https://github.com/nodejs/node/commit/d1efdb29b4)] - **doc**: document ICU time zone data update process (Andrew Paprocki) [#30364](https://github.com/nodejs/node/pull/30364) +* [[`1d918b67ca`](https://github.com/nodejs/node/commit/1d918b67ca)] - **doc,stream**: split finish and end events into separate entries (Rich Trott) [#33881](https://github.com/nodejs/node/pull/33881) +* [[`af9fb5969d`](https://github.com/nodejs/node/commit/af9fb5969d)] - **doc,tools**: properly syntax highlight API ref docs (Derek Lewis) [#33442](https://github.com/nodejs/node/pull/33442) +* [[`122d2b5c02`](https://github.com/nodejs/node/commit/122d2b5c02)] - **domain**: remove native domain code (Stephen Belanger) [#33801](https://github.com/nodejs/node/pull/33801) +* [[`e060060aa2`](https://github.com/nodejs/node/commit/e060060aa2)] - **errors**: fully inspect errors on exit (Ruben Bridgewater) [#33523](https://github.com/nodejs/node/pull/33523) +* [[`aca07f428e`](https://github.com/nodejs/node/commit/aca07f428e)] - **errors**: skip fatal error highlighting on windows (Thomas) [#33132](https://github.com/nodejs/node/pull/33132) +* [[`50adccadc1`](https://github.com/nodejs/node/commit/50adccadc1)] - **esm**: fix loader hooks doc annotations (Derek Lewis) [#33563](https://github.com/nodejs/node/pull/33563) +* [[`5bef20c2fc`](https://github.com/nodejs/node/commit/5bef20c2fc)] - **esm**: share package.json cache between ESM and CJS loaders (Kirill Shatskiy) [#33229](https://github.com/nodejs/node/pull/33229) +* [[`828d5d22eb`](https://github.com/nodejs/node/commit/828d5d22eb)] - **esm**: doc & validate source values for formats (Bradley Farias) [#32202](https://github.com/nodejs/node/pull/32202) +* [[`2724514f53`](https://github.com/nodejs/node/commit/2724514f53)] - **event**: cancelBubble is a property (Benjamin Gruenbaum) [#34015](https://github.com/nodejs/node/pull/34015) +* [[`c9dec0c0f0`](https://github.com/nodejs/node/commit/c9dec0c0f0)] - **event**: cancelBubble is a property (Benjamin Gruenbaum) [#33613](https://github.com/nodejs/node/pull/33613) +* [[`0c32920a82`](https://github.com/nodejs/node/commit/0c32920a82)] - **events**: fix add-remove-add case in EventTarget (Anna Henningsen) [#34056](https://github.com/nodejs/node/pull/34056) +* [[`c34f4743c4`](https://github.com/nodejs/node/commit/c34f4743c4)] - **events**: improve argument handling, start passive (James M Snell) [#34015](https://github.com/nodejs/node/pull/34015) +* [[`ea1a2d7bc9`](https://github.com/nodejs/node/commit/ea1a2d7bc9)] - **events**: support dispatching event from event (James M Snell) [#34015](https://github.com/nodejs/node/pull/34015) +* [[`5ce153365e`](https://github.com/nodejs/node/commit/5ce153365e)] - **events**: add event-target tests (James M Snell) [#34015](https://github.com/nodejs/node/pull/34015) +* [[`91b6c093b1`](https://github.com/nodejs/node/commit/91b6c093b1)] - **events**: support event handlers (Benjamin Gruenbaum) [#34015](https://github.com/nodejs/node/pull/34015) +* [[`b392fdd4aa`](https://github.com/nodejs/node/commit/b392fdd4aa)] - **events**: expose Event statics (Benjamin Gruenbaum) [#34015](https://github.com/nodejs/node/pull/34015) +* [[`cd3a1429a3`](https://github.com/nodejs/node/commit/cd3a1429a3)] - **events**: Handle a range of this values for dispatchEvent (Zirak) [#34015](https://github.com/nodejs/node/pull/34015) +* [[`aa1cb3f186`](https://github.com/nodejs/node/commit/aa1cb3f186)] - **events**: fix EventTarget support (Benjamin Gruenbaum) [#34015](https://github.com/nodejs/node/pull/34015) +* [[`0f0f4e0c40`](https://github.com/nodejs/node/commit/0f0f4e0c40)] - **events**: fix depth in customInspectSymbol and clean up (Denys Otrishko) [#34015](https://github.com/nodejs/node/pull/34015) +* [[`6ce3293cc4`](https://github.com/nodejs/node/commit/6ce3293cc4)] - **events**: use internal/validators in event\_target.js (Denys Otrishko) [#34015](https://github.com/nodejs/node/pull/34015) +* [[`eb01214ab2`](https://github.com/nodejs/node/commit/eb01214ab2)] - **events**: use property, primordials (Benjamin Gruenbaum) [#33775](https://github.com/nodejs/node/pull/33775) +* [[`667195ef8f`](https://github.com/nodejs/node/commit/667195ef8f)] - **events**: improve listeners() performance (Brian White) [#33863](https://github.com/nodejs/node/pull/33863) +* [[`f1b0291d82`](https://github.com/nodejs/node/commit/f1b0291d82)] - **events**: lazy load perf\_hooks for EventTarget (James M Snell) [#33717](https://github.com/nodejs/node/pull/33717) +* [[`c291ce599c`](https://github.com/nodejs/node/commit/c291ce599c)] - **events**: improve arrayClone performance (Brian White) [#33774](https://github.com/nodejs/node/pull/33774) +* [[`a3ef2b7335`](https://github.com/nodejs/node/commit/a3ef2b7335)] - **events**: support useCapture boolean (Benjamin Gruenbaum) [#33618](https://github.com/nodejs/node/pull/33618) +* [[`2e6eceac5c`](https://github.com/nodejs/node/commit/2e6eceac5c)] - **events**: set target property to null (Benjamin Gruenbaum) [#33615](https://github.com/nodejs/node/pull/33615) +* [[`bc2e821ccc`](https://github.com/nodejs/node/commit/bc2e821ccc)] - **events**: deal with no argument case (Benjamin Gruenbaum) [#33611](https://github.com/nodejs/node/pull/33611) +* [[`e7bce2e03a`](https://github.com/nodejs/node/commit/e7bce2e03a)] - **events**: deal with Symbol() passed to event constructor (Benjamin Gruenbaum) [#33612](https://github.com/nodejs/node/pull/33612) +* [[`27c90efce0`](https://github.com/nodejs/node/commit/27c90efce0)] - **events**: variable originalListener is useless (fuxingZhang) [#33596](https://github.com/nodejs/node/pull/33596) +* [[`2a29ced050`](https://github.com/nodejs/node/commit/2a29ced050)] - **events**: fix event-target enumerable keys (Benjamin Gruenbaum) [#33616](https://github.com/nodejs/node/pull/33616) +* [[`f3d0d3089d`](https://github.com/nodejs/node/commit/f3d0d3089d)] - **events**: add tests, better toString (Benjamin Gruenbaum) [#33622](https://github.com/nodejs/node/pull/33622) +* [[`95cbfcec99`](https://github.com/nodejs/node/commit/95cbfcec99)] - **fs**: fix readdir failure when libuv returns UV\_DIRENT\_UNKNOWN (Kirill Shatskiy) [#33395](https://github.com/nodejs/node/pull/33395) +* [[`b894df860a`](https://github.com/nodejs/node/commit/b894df860a)] - **fs**: fix realpath inode link caching (Denys Otrishko) [#33945](https://github.com/nodejs/node/pull/33945) +* [[`b280c86213`](https://github.com/nodejs/node/commit/b280c86213)] - **fs**: support util.promisify for fs.readv (Lucas Holmquist) [#33590](https://github.com/nodejs/node/pull/33590) +* [[`2c03661860`](https://github.com/nodejs/node/commit/2c03661860)] - **fs**: unify style in preprocessSymlinkDestination (Bartosz Sosnowski) [#33496](https://github.com/nodejs/node/pull/33496) +* [[`b675ea0272`](https://github.com/nodejs/node/commit/b675ea0272)] - **fs**: replace checkPosition with validateInteger (rickyes) [#33277](https://github.com/nodejs/node/pull/33277) +* [[`a90b96f338`](https://github.com/nodejs/node/commit/a90b96f338)] - **fs**: refactor the import of internalUtil (rickyes) [#33296](https://github.com/nodejs/node/pull/33296) +* [[`a0a61b81a5`](https://github.com/nodejs/node/commit/a0a61b81a5)] - **http**: used already defined validator for boolean check (Yash Ladha) [#33731](https://github.com/nodejs/node/pull/33731) +* [[`6dbd63c8ba`](https://github.com/nodejs/node/commit/6dbd63c8ba)] - ***Revert*** "**http**: set IncomingMessage.destroyed" (Robert Nagy) [#33686](https://github.com/nodejs/node/pull/33686) +* [[`feb6e1ffb8`](https://github.com/nodejs/node/commit/feb6e1ffb8)] - **http**: don't throw on `Uint8Array`s for `http.ServerResponse#write` (Pranshu Srivastava) [#33155](https://github.com/nodejs/node/pull/33155) +* [[`bcdf7e94be`](https://github.com/nodejs/node/commit/bcdf7e94be)] - **http**: simplify Agent initialization (himself65) [#33551](https://github.com/nodejs/node/pull/33551) +* [[`c2aad813c0`](https://github.com/nodejs/node/commit/c2aad813c0)] - **http**: tidy up exposure of header validation (Osher) [#33371](https://github.com/nodejs/node/pull/33371) +* [[`0752d2309f`](https://github.com/nodejs/node/commit/0752d2309f)] - **http2**: always call callback on Http2ServerResponse#end (Pranshu Srivastava) [#33911](https://github.com/nodejs/node/pull/33911) +* [[`d8aeafb4bf`](https://github.com/nodejs/node/commit/d8aeafb4bf)] - **http2**: add writable\* properties to compat api (Pranshu Srivastava) [#33506](https://github.com/nodejs/node/pull/33506) +* [[`0b34c4fb75`](https://github.com/nodejs/node/commit/0b34c4fb75)] - **http2**: add type checks for Http2ServerResponse.end (Pranshu Srivastava) [#33146](https://github.com/nodejs/node/pull/33146) +* [[`cc74f3c67c`](https://github.com/nodejs/node/commit/cc74f3c67c)] - **http2**: use `Object.create(null)` for `getHeaders` (Pranshu Srivastava) [#33188](https://github.com/nodejs/node/pull/33188) +* [[`8457033d83`](https://github.com/nodejs/node/commit/8457033d83)] - **http2**: reuse .\_onTimeout() in Http2Session and Http2Stream classes (rickyes) [#33354](https://github.com/nodejs/node/pull/33354) +* [[`c972ce200e`](https://github.com/nodejs/node/commit/c972ce200e)] - **http2**: comment on usage of `Object.create(null)` (Pranshu Srivastava) [#33183](https://github.com/nodejs/node/pull/33183) +* [[`e58f14fee7`](https://github.com/nodejs/node/commit/e58f14fee7)] - **inspector**: drop 'chrome-' from inspector url (Colin Ihrig) [#33758](https://github.com/nodejs/node/pull/33758) +* [[`42df2baa21`](https://github.com/nodejs/node/commit/42df2baa21)] - **inspector**: throw error when activating an already active inspector (Joyee Cheung) [#33015](https://github.com/nodejs/node/pull/33015) +* [[`c9489f2f23`](https://github.com/nodejs/node/commit/c9489f2f23)] - **internal**: rename error-serdes for consistency (Evan Lucas) [#33793](https://github.com/nodejs/node/pull/33793) +* [[`b7690da65e`](https://github.com/nodejs/node/commit/b7690da65e)] - **lib**: improve debuglog() performance (Brian White) [#32260](https://github.com/nodejs/node/pull/32260) +* [[`b6ef6c8476`](https://github.com/nodejs/node/commit/b6ef6c8476)] - **lib**: remove manual exception handling in queueMicrotask (Gus Caplan) [#33859](https://github.com/nodejs/node/pull/33859) +* [[`ec01867623`](https://github.com/nodejs/node/commit/ec01867623)] - **lib**: replace charCodeAt with fixed Unicode (rickyes) [#32758](https://github.com/nodejs/node/pull/32758) +* [[`76123b9ae7`](https://github.com/nodejs/node/commit/76123b9ae7)] - **lib**: add Int16Array primordials (Sebastien Ahkrin) [#31205](https://github.com/nodejs/node/pull/31205) +* [[`59d435ed4d`](https://github.com/nodejs/node/commit/59d435ed4d)] - **lib**: update TODO comments (Ruben Bridgewater) [#33361](https://github.com/nodejs/node/pull/33361) +* [[`e62a8b5007`](https://github.com/nodejs/node/commit/e62a8b5007)] - **lib**: update executionAsyncId/triggerAsyncId comment (Daniel Bevenius) [#33396](https://github.com/nodejs/node/pull/33396) +* [[`4ae4073abf`](https://github.com/nodejs/node/commit/4ae4073abf)] - **lib,src**: remove cpu profiler idle notifier (Ben Noordhuis) [#34010](https://github.com/nodejs/node/pull/34010) +* [[`fc7cad828b`](https://github.com/nodejs/node/commit/fc7cad828b)] - **meta**: introduce codeowners again (James M Snell) [#33895](https://github.com/nodejs/node/pull/33895) +* [[`b162c532d7`](https://github.com/nodejs/node/commit/b162c532d7)] - **meta**: fix a typo in the flaky test template (Colin Ihrig) [#33677](https://github.com/nodejs/node/pull/33677) +* [[`148c1f1344`](https://github.com/nodejs/node/commit/148c1f1344)] - **meta**: wrap flaky test template at 80 characters (Colin Ihrig) [#33677](https://github.com/nodejs/node/pull/33677) +* [[`2aa6469bea`](https://github.com/nodejs/node/commit/2aa6469bea)] - **meta**: add flaky test issue template (Ash Cripps) [#33500](https://github.com/nodejs/node/pull/33500) +* [[`84a5e6cec8`](https://github.com/nodejs/node/commit/84a5e6cec8)] - **module**: fix error message about importing names from cjs (Fábio Santos) [#33882](https://github.com/nodejs/node/pull/33882) +* [[`8c9e3a9dfb`](https://github.com/nodejs/node/commit/8c9e3a9dfb)] - **module**: remove dynamicInstantiate loader hook (Jan Krems) [#33501](https://github.com/nodejs/node/pull/33501) +* [[`53dbb9d232`](https://github.com/nodejs/node/commit/53dbb9d232)] - **n-api**: add version to wasm registration (Gus Caplan) [#34045](https://github.com/nodejs/node/pull/34045) +* [[`e924439d96`](https://github.com/nodejs/node/commit/e924439d96)] - **n-api**: document nextTick timing in callbacks (Mathias Buus) [#33804](https://github.com/nodejs/node/pull/33804) +* [[`524daf89a1`](https://github.com/nodejs/node/commit/524daf89a1)] - **n-api**: ensure scope present for finalization (Michael Dawson) [#33508](https://github.com/nodejs/node/pull/33508) +* [[`e83642f73d`](https://github.com/nodejs/node/commit/e83642f73d)] - **n-api**: remove `napi\_env::CallIntoModuleThrow` (Gabriel Schulhof) [#33570](https://github.com/nodejs/node/pull/33570) +* [[`4c235b07ae`](https://github.com/nodejs/node/commit/4c235b07ae)] - ***Revert*** "**n-api**: detect deadlocks in thread-safe function" (Anna Henningsen) [#33453](https://github.com/nodejs/node/pull/33453) +* [[`022dcebcd8`](https://github.com/nodejs/node/commit/022dcebcd8)] - **napi**: add \_\_wasm32\_\_ guards (Gus Caplan) [#33597](https://github.com/nodejs/node/pull/33597) +* [[`164461edfd`](https://github.com/nodejs/node/commit/164461edfd)] - **net**: refactor check for Windows (rickyes) [#33497](https://github.com/nodejs/node/pull/33497) +* [[`e0b0ddd257`](https://github.com/nodejs/node/commit/e0b0ddd257)] - **querystring**: fix stringify for empty array (sapics) [#33918](https://github.com/nodejs/node/pull/33918) +* [[`e8572e7070`](https://github.com/nodejs/node/commit/e8572e7070)] - **querystring**: improve stringify() performance (Brian White) [#33669](https://github.com/nodejs/node/pull/33669) +* [[`011fe1d443`](https://github.com/nodejs/node/commit/011fe1d443)] - **repl**: add builtinModules (Ruben Bridgewater) [#33295](https://github.com/nodejs/node/pull/33295) +* [[`71d6599191`](https://github.com/nodejs/node/commit/71d6599191)] - **repl**: simplify repl autocompletion (Ruben Bridgewater) [#33450](https://github.com/nodejs/node/pull/33450) +* [[`1330cfc2a9`](https://github.com/nodejs/node/commit/1330cfc2a9)] - **repl**: support optional chaining during autocompletion (Ruben Bridgewater) [#33450](https://github.com/nodejs/node/pull/33450) +* [[`9760c6caff`](https://github.com/nodejs/node/commit/9760c6caff)] - **src**: add errorProperties on process.report (himself65) [#28426](https://github.com/nodejs/node/pull/28426) +* [[`da81930b13`](https://github.com/nodejs/node/commit/da81930b13)] - **src**: tolerate EPERM returned from tcsetattr (patr0nus) [#33944](https://github.com/nodejs/node/pull/33944) +* [[`c1664a9008`](https://github.com/nodejs/node/commit/c1664a9008)] - **src**: clang\_format base\_object (Yash Ladha) [#33680](https://github.com/nodejs/node/pull/33680) +* [[`a789474945`](https://github.com/nodejs/node/commit/a789474945)] - **src**: fix ParseEncoding (sapics) [#33957](https://github.com/nodejs/node/pull/33957) +* [[`74f4aae22f`](https://github.com/nodejs/node/commit/74f4aae22f)] - **src**: remove unnecessary calculation in base64.h (sapics) [#33839](https://github.com/nodejs/node/pull/33839) +* [[`c492a2715e`](https://github.com/nodejs/node/commit/c492a2715e)] - **src**: use ToLocal in node\_os.cc (wenningplus) [#33939](https://github.com/nodejs/node/pull/33939) +* [[`9a52cd9cc0`](https://github.com/nodejs/node/commit/9a52cd9cc0)] - **src**: handle empty Maybe(Local) in node\_util.cc (Anna Henningsen) [#33867](https://github.com/nodejs/node/pull/33867) +* [[`e1bebf13db`](https://github.com/nodejs/node/commit/e1bebf13db)] - **src**: fix FastStringKey equal operator (sapics) [#33748](https://github.com/nodejs/node/pull/33748) +* [[`0dd67d992e`](https://github.com/nodejs/node/commit/0dd67d992e)] - **src**: reduce scope of code cache mutex (Anna Henningsen) [#33980](https://github.com/nodejs/node/pull/33980) +* [[`cd0ae4007f`](https://github.com/nodejs/node/commit/cd0ae4007f)] - **src**: improve indention for upd\_wrap.cc (gengjiawen) [#33976](https://github.com/nodejs/node/pull/33976) +* [[`6014e4e0b8`](https://github.com/nodejs/node/commit/6014e4e0b8)] - **src**: remove unnecessary ToLocalChecked call (Daniel Bevenius) [#33902](https://github.com/nodejs/node/pull/33902) +* [[`4715a41c1c`](https://github.com/nodejs/node/commit/4715a41c1c)] - **src**: simplify alignment-handling code (Anna Henningsen) [#33884](https://github.com/nodejs/node/pull/33884) +* [[`33cff40bb7`](https://github.com/nodejs/node/commit/33cff40bb7)] - **src**: remove ref to tools/generate\_code\_cache.js (Daniel Bevenius) [#33825](https://github.com/nodejs/node/pull/33825) +* [[`dfa0ee13ee`](https://github.com/nodejs/node/commit/dfa0ee13ee)] - **src**: remove unused vector include in string\_bytes (Daniel Bevenius) [#33824](https://github.com/nodejs/node/pull/33824) +* [[`fb2b0a094b`](https://github.com/nodejs/node/commit/fb2b0a094b)] - **src**: avoid unnecessary ToLocalChecked calls (Daniel Bevenius) [#33824](https://github.com/nodejs/node/pull/33824) +* [[`07c21d0d27`](https://github.com/nodejs/node/commit/07c21d0d27)] - **src**: reduce FileHandle size by reordering fields (Anna Henningsen) [#33784](https://github.com/nodejs/node/pull/33784) +* [[`83aaad7ec3`](https://github.com/nodejs/node/commit/83aaad7ec3)] - **src**: do not track BaseObjects via cleanup hooks (Anna Henningsen) [#33809](https://github.com/nodejs/node/pull/33809) +* [[`f8dddd3416`](https://github.com/nodejs/node/commit/f8dddd3416)] - **src**: handle missing TracingController everywhere (Anna Henningsen) [#33815](https://github.com/nodejs/node/pull/33815) +* [[`3b71aa8029`](https://github.com/nodejs/node/commit/3b71aa8029)] - **src**: remove unused `ERR\_TRANSFERRING\_EXTERNALIZED\_SHAREDARRAYBUFFER` (Anna Henningsen) [#33810](https://github.com/nodejs/node/pull/33810) +* [[`1f996b7372`](https://github.com/nodejs/node/commit/1f996b7372)] - **src**: simplify Reindent function in json\_utils.cc (sapics) [#33722](https://github.com/nodejs/node/pull/33722) +* [[`cdcd76810e`](https://github.com/nodejs/node/commit/cdcd76810e)] - **src**: add "missing" bash completion options (Daniel Bevenius) [#33744](https://github.com/nodejs/node/pull/33744) +* [[`cc8d70531d`](https://github.com/nodejs/node/commit/cc8d70531d)] - **src**: use Check() instead of FromJust in environment (Daniel Bevenius) [#33706](https://github.com/nodejs/node/pull/33706) +* [[`858c6b9dfd`](https://github.com/nodejs/node/commit/858c6b9dfd)] - **src**: use ToLocal in SafeGetenv (Daniel Bevenius) [#33695](https://github.com/nodejs/node/pull/33695) +* [[`c2f49319b7`](https://github.com/nodejs/node/commit/c2f49319b7)] - **src**: remove unnecessary ToLocalChecked call (Daniel Bevenius) [#33683](https://github.com/nodejs/node/pull/33683) +* [[`21f1e64737`](https://github.com/nodejs/node/commit/21f1e64737)] - **src**: simplify format in node\_file.cc (himself65) [#33660](https://github.com/nodejs/node/pull/33660) +* [[`c3728c6235`](https://github.com/nodejs/node/commit/c3728c6235)] - **src**: simplify MaybeStackBuffer::capacity() (Ben Noordhuis) [#33602](https://github.com/nodejs/node/pull/33602) +* [[`7725ff392c`](https://github.com/nodejs/node/commit/7725ff392c)] - **src**: remove superfluous inline keywords (James M Snell) [#33291](https://github.com/nodejs/node/pull/33291) +* [[`27e9cb7e85`](https://github.com/nodejs/node/commit/27e9cb7e85)] - **src**: turn AllocatedBuffer into thin wrapper around v8::BackingStore (James M Snell) [#33291](https://github.com/nodejs/node/pull/33291) +* [[`d8f040e33d`](https://github.com/nodejs/node/commit/d8f040e33d)] - **src**: extract AllocatedBuffer from env.h (James M Snell) [#33291](https://github.com/nodejs/node/pull/33291) +* [[`a8824ae0a5`](https://github.com/nodejs/node/commit/a8824ae0a5)] - **src**: avoid OOB read in URL parser (Anna Henningsen) [#33640](https://github.com/nodejs/node/pull/33640) +* [[`6ef2efe33a`](https://github.com/nodejs/node/commit/6ef2efe33a)] - **src**: use MaybeLocal.ToLocal instead of IsEmpty worker (Daniel Bevenius) [#33599](https://github.com/nodejs/node/pull/33599) +* [[`522fbbc8d9`](https://github.com/nodejs/node/commit/522fbbc8d9)] - **src**: don't use semicolon outside function (Shelley Vohr) [#33592](https://github.com/nodejs/node/pull/33592) +* [[`ad970996cf`](https://github.com/nodejs/node/commit/ad970996cf)] - **src**: remove unused using declarations (Daniel Bevenius) [#33268](https://github.com/nodejs/node/pull/33268) +* [[`20d54f6908`](https://github.com/nodejs/node/commit/20d54f6908)] - **src**: use MaybeLocal.ToLocal instead of IsEmpty (Daniel Bevenius) [#33554](https://github.com/nodejs/node/pull/33554) +* [[`5438611984`](https://github.com/nodejs/node/commit/5438611984)] - **src**: use NewFromUtf8Literal in GetLinkedBinding (Daniel Bevenius) [#33552](https://github.com/nodejs/node/pull/33552) +* [[`a5e860cd29`](https://github.com/nodejs/node/commit/a5e860cd29)] - **src**: use const in constant args.Length() (himself65) [#33555](https://github.com/nodejs/node/pull/33555) +* [[`7e351f15cb`](https://github.com/nodejs/node/commit/7e351f15cb)] - **src**: use MaybeLocal::FromMaybe to return exception (Daniel Bevenius) [#33514](https://github.com/nodejs/node/pull/33514) +* [[`3f1c756f89`](https://github.com/nodejs/node/commit/3f1c756f89)] - ***Revert*** "**src**: fix missing extra ca in tls.rootCertificates" (Eric Bickle) [#33313](https://github.com/nodejs/node/pull/33313) +* [[`d1e1dbf188`](https://github.com/nodejs/node/commit/d1e1dbf188)] - **src**: remove BeforeExit callback list (Ben Noordhuis) [#33386](https://github.com/nodejs/node/pull/33386) +* [[`ee45b78b7f`](https://github.com/nodejs/node/commit/ee45b78b7f)] - **src**: use MaybeLocal.ToLocal instead of IsEmpty (Daniel Bevenius) [#33457](https://github.com/nodejs/node/pull/33457) +* [[`9018e92b13`](https://github.com/nodejs/node/commit/9018e92b13)] - **src**: remove unused headers in src/util.h (Juan José Arboleda) [#33070](https://github.com/nodejs/node/pull/33070) +* [[`7d1d00f97a`](https://github.com/nodejs/node/commit/7d1d00f97a)] - **src**: use enum for refed flag on native immediates (Anna Henningsen) [#33444](https://github.com/nodejs/node/pull/33444) +* [[`e8cc269ee0`](https://github.com/nodejs/node/commit/e8cc269ee0)] - **src**: use symbol to store `AsyncWrap` resource (Anna Henningsen) [#31745](https://github.com/nodejs/node/pull/31745) +* [[`ab2454dec5`](https://github.com/nodejs/node/commit/ab2454dec5)] - **src**: prefer make\_unique (Michael Dawson) [#33378](https://github.com/nodejs/node/pull/33378) +* [[`a942f7280a`](https://github.com/nodejs/node/commit/a942f7280a)] - **src**: remove unnecessary else in base\_object-inl.h (Daniel Bevenius) [#33413](https://github.com/nodejs/node/pull/33413) +* [[`f6227c0577`](https://github.com/nodejs/node/commit/f6227c0577)] - **src**: reduce duplication in RegisterHandleCleanups (Daniel Bevenius) [#33421](https://github.com/nodejs/node/pull/33421) +* [[`f24292e106`](https://github.com/nodejs/node/commit/f24292e106)] - **src**: remove unused IsolateSettings variable (Daniel Bevenius) [#33417](https://github.com/nodejs/node/pull/33417) +* [[`308be6ca0c`](https://github.com/nodejs/node/commit/308be6ca0c)] - **src**: remove unused misc variable (Daniel Bevenius) [#33417](https://github.com/nodejs/node/pull/33417) +* [[`7fd0519a91`](https://github.com/nodejs/node/commit/7fd0519a91)] - **src**: add promise\_resolve to SetupHooks comment (Daniel Bevenius) [#33365](https://github.com/nodejs/node/pull/33365) +* [[`26a3cf058d`](https://github.com/nodejs/node/commit/26a3cf058d)] - **src,build**: add --openssl-default-cipher-list (Daniel Bevenius) [#33708](https://github.com/nodejs/node/pull/33708) +* [[`b0fa611e68`](https://github.com/nodejs/node/commit/b0fa611e68)] - **stream**: fix the spellings (antsmartian) [#33635](https://github.com/nodejs/node/pull/33635) +* [[`1db0d51ab2`](https://github.com/nodejs/node/commit/1db0d51ab2)] - **stream**: forward writableObjectMode (Robert Nagy) [#33390](https://github.com/nodejs/node/pull/33390) +* [[`2c568c80f3`](https://github.com/nodejs/node/commit/2c568c80f3)] - **test**: add non-ASCII character embedding test (Anna Henningsen) [#33972](https://github.com/nodejs/node/pull/33972) +* [[`d4a2ae094e`](https://github.com/nodejs/node/commit/d4a2ae094e)] - **test**: add test for Http2ServerResponse#\[writableCorked,cork,uncork\] (Pranshu Srivastava) [#33956](https://github.com/nodejs/node/pull/33956) +* [[`4a61013fb2`](https://github.com/nodejs/node/commit/4a61013fb2)] - **test**: print arguments passed to mustNotCall function (Denys Otrishko) [#33951](https://github.com/nodejs/node/pull/33951) +* [[`1b55d90975`](https://github.com/nodejs/node/commit/1b55d90975)] - **test**: AsyncLocalStorage works with thenables (Gerhard Stoebich) [#34008](https://github.com/nodejs/node/pull/34008) +* [[`195980d667`](https://github.com/nodejs/node/commit/195980d667)] - **test**: account for non-node basename (Shelley Vohr) [#33952](https://github.com/nodejs/node/pull/33952) +* [[`90223f0a88`](https://github.com/nodejs/node/commit/90223f0a88)] - **test**: fix typo in common/index.js (gengjiawen) [#33976](https://github.com/nodejs/node/pull/33976) +* [[`d427d7f905`](https://github.com/nodejs/node/commit/d427d7f905)] - **test**: add common/udppair utility (James M Snell) [#33380](https://github.com/nodejs/node/pull/33380) +* [[`b8fdde400a`](https://github.com/nodejs/node/commit/b8fdde400a)] - ***Revert*** "**test**: stop testing --interpreted-frames-native-stack for s390x" (Michaël Zasso) [#33794](https://github.com/nodejs/node/pull/33794) +* [[`e3a53329c2`](https://github.com/nodejs/node/commit/e3a53329c2)] - **test**: temporarily exclude test on arm (Michael Dawson) [#33814](https://github.com/nodejs/node/pull/33814) +* [[`b6e3616911`](https://github.com/nodejs/node/commit/b6e3616911)] - **test**: fix invalid regular expressions in case test-trace-exit (legendecas) [#33769](https://github.com/nodejs/node/pull/33769) +* [[`c3ac47c03d`](https://github.com/nodejs/node/commit/c3ac47c03d)] - **test**: changed function to arrow function (Sagar Jadhav) [#33711](https://github.com/nodejs/node/pull/33711) +* [[`15eb5a3da4`](https://github.com/nodejs/node/commit/15eb5a3da4)] - **test**: uv\_tty\_init now returns EINVAL on IBM i (Xu Meng) [#33629](https://github.com/nodejs/node/pull/33629) +* [[`da5e970a8c`](https://github.com/nodejs/node/commit/da5e970a8c)] - **test**: make flaky test stricter (Robert Nagy) [#33539](https://github.com/nodejs/node/pull/33539) +* [[`47396a42cf`](https://github.com/nodejs/node/commit/47396a42cf)] - **test**: fix flaky test-trace-atomics-wait (Anna Henningsen) [#33428](https://github.com/nodejs/node/pull/33428) +* [[`eb877a4c49`](https://github.com/nodejs/node/commit/eb877a4c49)] - **test**: mark test-dgram-multicast-ssmv6-multi-process flaky (AshCripps) [#33498](https://github.com/nodejs/node/pull/33498) +* [[`5dca04ee8e`](https://github.com/nodejs/node/commit/5dca04ee8e)] - **tools**: remove superfluous regex in tools/doc/json.js (Rich Trott) [#33998](https://github.com/nodejs/node/pull/33998) +* [[`1791d5727c`](https://github.com/nodejs/node/commit/1791d5727c)] - **tools**: update remark-preset-lint-node@1.15.1 to 1.16.0 (Rich Trott) [#33852](https://github.com/nodejs/node/pull/33852) +* [[`01d8b91942`](https://github.com/nodejs/node/commit/01d8b91942)] - **tools**: prevent js2c from running if nothing changed (Daniel Bevenius) [#33844](https://github.com/nodejs/node/pull/33844) +* [[`e837f00b4f`](https://github.com/nodejs/node/commit/e837f00b4f)] - **tools**: remove unused vector include in mkdcodecache (Daniel Bevenius) [#33828](https://github.com/nodejs/node/pull/33828) +* [[`800dbb6bdd`](https://github.com/nodejs/node/commit/800dbb6bdd)] - **tools**: update ESLint to 7.2.0 (Colin Ihrig) [#33776](https://github.com/nodejs/node/pull/33776) +* [[`a14e38a6c0`](https://github.com/nodejs/node/commit/a14e38a6c0)] - **tools**: remove unused using declarations code\_cache (Daniel Bevenius) [#33697](https://github.com/nodejs/node/pull/33697) +* [[`9fb1eb09d9`](https://github.com/nodejs/node/commit/9fb1eb09d9)] - **tools**: update remark-preset-lint-node from 1.15.0 to 1.15.1 (Rich Trott) [#33727](https://github.com/nodejs/node/pull/33727) +* [[`a331a00eac`](https://github.com/nodejs/node/commit/a331a00eac)] - **tools**: fix check-imports.py to match on word boundaries (Richard Lau) [#33268](https://github.com/nodejs/node/pull/33268) +* [[`9325ed9e1c`](https://github.com/nodejs/node/commit/9325ed9e1c)] - **tools**: update ESLint to 7.1.0 (Colin Ihrig) [#33526](https://github.com/nodejs/node/pull/33526) +* [[`6dab63f36d`](https://github.com/nodejs/node/commit/6dab63f36d)] - **tools**: add docserve target (Antoine du HAMEL) [#33221](https://github.com/nodejs/node/pull/33221) +* [[`2384044c95`](https://github.com/nodejs/node/commit/2384044c95)] - **tools,gyp**: add support for MSVC cross-compilation (Richard Townsend) [#32867](https://github.com/nodejs/node/pull/32867) +* [[`987c927225`](https://github.com/nodejs/node/commit/987c927225)] - **util**: fix width detection for DEL without ICU (Ruben Bridgewater) [#33650](https://github.com/nodejs/node/pull/33650) +* [[`91d0d53b59`](https://github.com/nodejs/node/commit/91d0d53b59)] - **util**: support Combining Diacritical Marks for Symbols (Ruben Bridgewater) [#33650](https://github.com/nodejs/node/pull/33650) +* [[`e3d53f999d`](https://github.com/nodejs/node/commit/e3d53f999d)] - **util**: gracefully handle unknown colors (Ruben Bridgewater) [#33797](https://github.com/nodejs/node/pull/33797) +* [[`a90c9aa858`](https://github.com/nodejs/node/commit/a90c9aa858)] - **util**: fix inspection of class instance prototypes (Ruben Bridgewater) [#33449](https://github.com/nodejs/node/pull/33449) +* [[`2380d90f0a`](https://github.com/nodejs/node/commit/2380d90f0a)] - **util**: mark classes while inspecting them (Ruben Bridgewater) [#32332](https://github.com/nodejs/node/pull/32332) +* [[`879c9322ce`](https://github.com/nodejs/node/commit/879c9322ce)] - **vm**: allow proxy callbacks to throw (Gus Caplan) [#33808](https://github.com/nodejs/node/pull/33808) +* [[`af14c1f776`](https://github.com/nodejs/node/commit/af14c1f776)] - **wasi**: allow WASI stdio to be configured (Colin Ihrig) [#33544](https://github.com/nodejs/node/pull/33544) +* [[`5eecea375f`](https://github.com/nodejs/node/commit/5eecea375f)] - **wasi**: simplify WASI memory management (Colin Ihrig) [#33525](https://github.com/nodejs/node/pull/33525) +* [[`f98e888fdd`](https://github.com/nodejs/node/commit/f98e888fdd)] - **wasi**: refactor and enable poll\_oneoff() test (Colin Ihrig) [#33521](https://github.com/nodejs/node/pull/33521) +* [[`6b20e8442f`](https://github.com/nodejs/node/commit/6b20e8442f)] - **wasi**: relax WebAssembly.Instance type check (Ben Noordhuis) [#33431](https://github.com/nodejs/node/pull/33431) +* [[`d15383253a`](https://github.com/nodejs/node/commit/d15383253a)] - **wasi,worker**: handle termination exception (Ben Noordhuis) [#33386](https://github.com/nodejs/node/pull/33386) +* [[`3f971d89a9`](https://github.com/nodejs/node/commit/3f971d89a9)] - **win,fs**: use namespaced path in absolute symlinks (Bartosz Sosnowski) [#33351](https://github.com/nodejs/node/pull/33351) +* [[`3520a134af`](https://github.com/nodejs/node/commit/3520a134af)] - **win,msi**: add arm64 config for windows msi (Dennis Ameling) [#33689](https://github.com/nodejs/node/pull/33689) +* [[`b79495905f`](https://github.com/nodejs/node/commit/b79495905f)] - **worker**: fix variable referencing in template string (Harshitha KP) [#33467](https://github.com/nodejs/node/pull/33467) +* [[`9c3008005d`](https://github.com/nodejs/node/commit/9c3008005d)] - **worker**: perform initial port.unref() before preload modules (Anna Henningsen) [#33455](https://github.com/nodejs/node/pull/33455) +* [[`64cae13799`](https://github.com/nodejs/node/commit/64cae13799)] - **worker**: use \_writev in internal communication (Anna Henningsen) [#33454](https://github.com/nodejs/node/pull/33454) +* [[`7817b875a7`](https://github.com/nodejs/node/commit/7817b875a7)] - **worker**: fix race condition in node\_messaging.cc (Anna Henningsen) [#33429](https://github.com/nodejs/node/pull/33429) + ## 2020-06-02, Version 14.4.0 (Current), @targos diff --git a/lib/_stream_readable.js b/lib/_stream_readable.js index 952f2d75b84839..d33d53de2b23a4 100644 --- a/lib/_stream_readable.js +++ b/lib/_stream_readable.js @@ -66,7 +66,6 @@ ObjectSetPrototypeOf(Readable.prototype, Stream.prototype); ObjectSetPrototypeOf(Readable, Stream); const { errorOrDestroy } = destroyImpl; -const kProxyEvents = ['error', 'close', 'destroy', 'pause', 'resume']; function prependListener(emitter, event, fn) { // Sadly this is not cacheable as some libraries bundle their own @@ -1051,10 +1050,29 @@ Readable.prototype.wrap = function(stream) { } } - // Proxy certain important events. - for (const kProxyEvent of kProxyEvents) { - stream.on(kProxyEvent, this.emit.bind(this, kProxyEvent)); - } + stream.on('error', (err) => { + errorOrDestroy(this, err); + }); + + stream.on('close', () => { + // TODO(ronag): Update readable state? + this.emit('close'); + }); + + stream.on('destroy', () => { + // TODO(ronag): this.destroy()? + this.emit('destroy'); + }); + + stream.on('pause', () => { + // TODO(ronag): this.pause()? + this.emit('pause'); + }); + + stream.on('resume', () => { + // TODO(ronag): this.resume()? + this.emit('resume'); + }); // When we try to consume some more bytes, simply unpause the // underlying stream. diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js index f60814d2dc9e28..a51dbf05ec4f86 100644 --- a/lib/internal/bootstrap/pre_execution.js +++ b/lib/internal/bootstrap/pre_execution.js @@ -6,7 +6,10 @@ const { SafeWeakMap, } = primordials; -const { getOptionValue } = require('internal/options'); +const { + getOptionValue, + shouldNotRegisterESMLoader +} = require('internal/options'); const { Buffer } = require('buffer'); const { ERR_MANIFEST_ASSERT_INTEGRITY } = require('internal/errors').codes; const assert = require('internal/assert'); @@ -56,7 +59,10 @@ function prepareMainThreadExecution(expandArgv1 = false) { initializeDeprecations(); initializeWASI(); initializeCJSLoader(); - initializeESMLoader(); + + if (!shouldNotRegisterESMLoader) { + initializeESMLoader(); + } const CJSLoader = require('internal/modules/cjs/loader'); assert(!CJSLoader.hasLoadedAnyUserCJSModule); diff --git a/lib/internal/options.js b/lib/internal/options.js index 03586f9dae6d76..10c6aa2d9a0978 100644 --- a/lib/internal/options.js +++ b/lib/internal/options.js @@ -1,6 +1,6 @@ 'use strict'; -const { getOptions } = internalBinding('options'); +const { getOptions, shouldNotRegisterESMLoader } = internalBinding('options'); const { options, aliases } = getOptions(); let warnOnAllowUnauthorized = true; @@ -32,4 +32,5 @@ module.exports = { aliases, getOptionValue, getAllowUnauthorized, + shouldNotRegisterESMLoader }; diff --git a/lib/internal/quic/core.js b/lib/internal/quic/core.js index 7e874ff34d58de..446bec3761374b 100644 --- a/lib/internal/quic/core.js +++ b/lib/internal/quic/core.js @@ -147,6 +147,7 @@ const { IDX_QUIC_SESSION_STATS_STREAMS_OUT_COUNT, IDX_QUIC_SESSION_STATS_KEYUPDATE_COUNT, IDX_QUIC_SESSION_STATS_LOSS_RETRANSMIT_COUNT, + IDX_QUIC_SESSION_STATS_HANDSHAKE_COMPLETED_AT, IDX_QUIC_SESSION_STATS_ACK_DELAY_RETRANSMIT_COUNT, IDX_QUIC_SESSION_STATS_MAX_BYTES_IN_FLIGHT, IDX_QUIC_SESSION_STATS_BLOCK_COUNT, @@ -208,13 +209,17 @@ const { const emit = EventEmitter.prototype.emit; +const kAfterLookup = Symbol('kAfterLookup'); +const kAfterPreferredAddressLookup = Symbol('kAfterPreferredAddressLookup'); const kAddSession = Symbol('kAddSession'); const kAddStream = Symbol('kAddStream'); const kClose = Symbol('kClose'); const kCert = Symbol('kCert'); const kClientHello = Symbol('kClientHello'); -const kContinueBind = Symbol('kContinueBind'); const kContinueConnect = Symbol('kContinueConnect'); +const kCompleteListen = Symbol('kCompleteListen'); +const kContinueListen = Symbol('kContinueListen'); +const kContinueBind = Symbol('kContinueBind'); const kDestroy = Symbol('kDestroy'); const kEndpointBound = Symbol('kEndpointBound'); const kEndpointClose = Symbol('kEndpointClose'); @@ -222,19 +227,30 @@ const kGetStreamOptions = Symbol('kGetStreamOptions'); const kHandshake = Symbol('kHandshake'); const kHandshakePost = Symbol('kHandshakePost'); const kHeaders = Symbol('kHeaders'); +const kInternalState = Symbol('kInternalState'); +const kInternalClientState = Symbol('kInternalClientState'); +const kInternalServerState = Symbol('kInternalServerState'); +const kMakeStream = Symbol('kMakeStream'); const kMaybeBind = Symbol('kMaybeBind'); +const kMaybeReady = Symbol('kMaybeReady'); +const kOnFileOpened = Symbol('kOnFileOpened'); +const kOnFileUnpipe = Symbol('kOnFileUnpipe'); +const kOnPipedFileHandleRead = Symbol('kOnPipedFileHandleRead'); const kSocketReady = Symbol('kSocketReady'); const kRemoveSession = Symbol('kRemove'); const kRemoveStream = Symbol('kRemoveStream'); const kServerBusy = Symbol('kServerBusy'); const kSetHandle = Symbol('kSetHandle'); const kSetSocket = Symbol('kSetSocket'); +const kSetSocketAfterBind = Symbol('kSetSocketAfterBind'); +const kStartFilePipe = Symbol('kStartFilePipe'); const kStreamClose = Symbol('kStreamClose'); const kStreamReset = Symbol('kStreamReset'); const kTrackWriteState = Symbol('kTrackWriteState'); const kUDPHandleForTesting = Symbol('kUDPHandleForTesting'); const kUsePreferredAddress = Symbol('kUsePreferredAddress'); const kVersionNegotiation = Symbol('kVersionNegotiation'); +const kWriteGeneric = Symbol('kWriteGeneric'); const kSocketUnbound = 0; const kSocketPending = 1; @@ -247,19 +263,11 @@ let warnedVerifyHostnameIdentity = false; assert(process.versions.ngtcp2 !== undefined); -// Called by the C++ internals when the socket is closed. -// When this is called, the only thing left to do is destroy -// the QuicSocket instance. -function onSocketClose() { - this[owner_symbol].destroy(); -} - -// Called by the C++ internals when an error occurs on the socket. -// When this is called, the only thing left to do is destroy -// the QuicSocket instance with an error. -// TODO(@jasnell): Should consolidate this with onSocketClose -function onSocketError(err) { - this[owner_symbol].destroy(errnoException(err)); +// Called by the C++ internals when the QuicSocket is closed with +// or without an error. The only thing left to do is destroy the +// QuicSocket instance. +function onSocketClose(err) { + this[owner_symbol].destroy(err != null ? errnoException(err) : undefined); } // Called by the C++ internals when the server busy state of @@ -279,23 +287,24 @@ function onSessionReady(handle) { process.nextTick(emit.bind(socket, 'session', session)); } -// During an immediate close, all currently open QuicStreams are -// abruptly closed. If they are still writable or readable, an abort -// event will be emitted, and RESET_STREAM and STOP_SENDING frames -// will be transmitted as necessary. Once streams have been -// shutdown, a CONNECTION_CLOSE frame will be sent and the -// session will enter the closing period, after which it will -// be destroyed either when the idle timeout expires, the -// QuicSession is silently closed, or destroy is called. -function onSessionClose(code, family) { +// Called when the session needs to be closed and destroyed. +// If silent is true, then the session is going to be closed +// immediately without sending any CONNECTION_CLOSE to the +// connected peer. If silent is false, a CONNECTION_CLOSE +// is going to be sent to the peer. +function onSessionClose(code, family, silent, statelessReset) { if (this[owner_symbol]) { - this[owner_symbol][kClose](family, code); - } else { - // When there's no owner_symbol, the session was closed - // before it could be fully set up. Just immediately - // close everything down on the native side. - this.destroy(code, family); + if (silent) { + this[owner_symbol][kDestroy](statelessReset, family, code); + } else { + this[owner_symbol][kClose](family, code); + } + return; } + // When there's no owner_symbol, the session was closed + // before it could be fully set up. Just immediately + // close everything down on the native side. + this.destroy(code, family); } // Called by the C++ internals when a QuicSession has been destroyed. @@ -481,14 +490,6 @@ function onSessionQlog(str) { } } -// Called when an error occurs in a QuicSession. When this happens, -// the only remedy is to destroy the session. -function onSessionError(error) { - if (this[owner_symbol]) { - this[owner_symbol].destroy(error); - } -} - // Called by the C++ internals when a client QuicSession receives // a version negotiation response from the server. function onSessionVersionNegotiation( @@ -562,14 +563,6 @@ function onStreamHeaders(id, headers, kind, push_id) { this[owner_symbol][kHeaders](id, headers, kind, push_id); } -// During a silent close, all currently open QuicStreams are abruptly -// closed. If they are still writable or readable, an abort event will be -// emitted, otherwise the stream is just destroyed. No RESET_STREAM or -// STOP_SENDING is transmitted to the peer. -function onSessionSilentClose(statelessReset, code, family) { - this[owner_symbol][kDestroy](statelessReset, family, code); -} - // When a stream is flow control blocked, causes a blocked event // to be emitted. This is a purely informational event. function onStreamBlocked() { @@ -579,18 +572,15 @@ function onStreamBlocked() { // Register the callbacks with the QUIC internal binding. setCallbacks({ onSocketClose, - onSocketError, onSocketServerBusy, onSessionReady, onSessionCert, onSessionClientHello, onSessionClose, onSessionDestroyed, - onSessionError, onSessionHandshake, onSessionKeylog, onSessionQlog, - onSessionSilentClose, onSessionStatus, onSessionTicket, onSessionVersionNegotiation, @@ -636,20 +626,29 @@ function onRemoveListener(event) { toggleListeners(this[kHandle], event, false); } +function getStats(obj, idx) { + const stats = obj[kHandle]?.stats || obj[kInternalState].stats; + // If stats is undefined at this point, it's just a bug + assert(stats); + return stats[idx]; +} + // QuicEndpoint wraps a UDP socket and is owned // by a QuicSocket. It does not exist independently // of the QuicSocket. class QuicEndpoint { - #state = kSocketUnbound; - #socket = undefined; - #udpSocket = undefined; - #address = undefined; - #ipv6Only = undefined; - #lookup = undefined; - #port = undefined; - #reuseAddr = undefined; - #type = undefined; - #fd = undefined; + [kInternalState] = { + state: kSocketUnbound, + socket: undefined, + udpSocket: undefined, + address: undefined, + ipv6Only: undefined, + lookup: undefined, + port: undefined, + reuseAddr: undefined, + type: undefined, + fd: undefined + }; constructor(socket, options) { const { @@ -661,35 +660,37 @@ class QuicEndpoint { type, preferred, } = validateQuicEndpointOptions(options); - this.#socket = socket; - this.#address = address || (type === AF_INET6 ? '::' : '0.0.0.0'); - this.#ipv6Only = !!ipv6Only; - this.#lookup = lookup || (type === AF_INET6 ? lookup6 : lookup4); - this.#port = port; - this.#reuseAddr = !!reuseAddr; - this.#type = type; - this.#udpSocket = dgram.createSocket(type === AF_INET6 ? 'udp6' : 'udp4'); + const state = this[kInternalState]; + state.socket = socket; + state.address = address || (type === AF_INET6 ? '::' : '0.0.0.0'); + state.ipv6Only = !!ipv6Only; + state.lookup = lookup || (type === AF_INET6 ? lookup6 : lookup4); + state.port = port; + state.reuseAddr = !!reuseAddr; + state.type = type; + state.udpSocket = dgram.createSocket(type === AF_INET6 ? 'udp6' : 'udp4'); // kUDPHandleForTesting is only used in the Node.js test suite to // artificially test the endpoint. This code path should never be // used in user code. if (typeof options[kUDPHandleForTesting] === 'object') { - this.#udpSocket.bind(options[kUDPHandleForTesting]); - this.#state = kSocketBound; - this.#socket[kEndpointBound](this); + state.udpSocket.bind(options[kUDPHandleForTesting]); + state.state = kSocketBound; + state.socket[kEndpointBound](this); } - const udpHandle = this.#udpSocket[internalDgram.kStateSymbol].handle; + const udpHandle = state.udpSocket[internalDgram.kStateSymbol].handle; const handle = new QuicEndpointHandle(socket[kHandle], udpHandle); handle[owner_symbol] = this; this[kHandle] = handle; - socket[kHandle].addEndpoint(handle, !!preferred); + socket[kHandle].addEndpoint(handle, preferred); } [kInspect]() { + // TODO(@jasnell): Proper custom inspect implementation const obj = { address: this.address, - fd: this.#fd, - type: this.#type === AF_INET6 ? 'udp6' : 'udp4' + fd: this[kInternalState].fd, + type: this[kInternalState].type === AF_INET6 ? 'udp6' : 'udp4' }; return `QuicEndpoint ${util.format(obj)}`; } @@ -699,29 +700,31 @@ class QuicEndpoint { // address. Once resolution is complete, the ip address needs to // be passed on to the [kContinueBind] function or the QuicEndpoint // needs to be destroyed. - static #afterLookup = function(err, ip) { + static [kAfterLookup](err, ip) { if (err) { this.destroy(err); return; } this[kContinueBind](ip); - }; + } // kMaybeBind binds the endpoint on-demand if it is not already // bound. If it is bound, we return immediately, otherwise put // the endpoint into the pending state and initiate the binding // process by calling the lookup to resolve the IP address. [kMaybeBind]() { - if (this.#state !== kSocketUnbound) + const state = this[kInternalState]; + if (state.state !== kSocketUnbound) return; - this.#state = kSocketPending; - this.#lookup(this.#address, QuicEndpoint.#afterLookup.bind(this)); + state.state = kSocketPending; + state.lookup(state.address, QuicEndpoint[kAfterLookup].bind(this)); } // IP address resolution is completed and we're ready to finish // binding to the local port. [kContinueBind](ip) { - const udpHandle = this.#udpSocket[internalDgram.kStateSymbol].handle; + const state = this[kInternalState]; + const udpHandle = state.udpSocket[internalDgram.kStateSymbol].handle; if (udpHandle == null) { // TODO(@jasnell): We may need to throw an error here. Under // what conditions does this happen? @@ -729,22 +732,22 @@ class QuicEndpoint { } const flags = - (this.#reuseAddr ? UV_UDP_REUSEADDR : 0) | - (this.#type === AF_INET6 && this.#ipv6Only ? UV_UDP_IPV6ONLY : 0); + (state.reuseAddr ? UV_UDP_REUSEADDR : 0) | + (state.type === AF_INET6 && state.ipv6Only ? UV_UDP_IPV6ONLY : 0); - const ret = udpHandle.bind(ip, this.#port, flags); + const ret = udpHandle.bind(ip, state.port, flags); if (ret) { - this.destroy(exceptionWithHostPort(ret, 'bind', ip, this.#port || 0)); + this.destroy(exceptionWithHostPort(ret, 'bind', ip, state.port || 0)); return; } // On Windows, the fd will be meaningless, but we always record it. - this.#fd = udpHandle.fd; - this.#state = kSocketBound; + state.fd = udpHandle.fd; + state.state = kSocketBound; // Notify the owning socket that the QuicEndpoint has been successfully // bound to the local UDP port. - this.#socket[kEndpointBound](this); + state.socket[kEndpointBound](this); } [kDestroy](error) { @@ -753,9 +756,10 @@ class QuicEndpoint { this[kHandle] = undefined; handle[owner_symbol] = undefined; handle.ondone = () => { - this.#udpSocket.close((err) => { + const state = this[kInternalState]; + state.udpSocket.close((err) => { if (err) error = err; - this.#socket[kEndpointClose](this, error); + state.socket[kEndpointClose](this, error); }); }; handle.waitForPendingCallbacks(); @@ -766,9 +770,10 @@ class QuicEndpoint { // the local IP address, port, and address type to which it // is bound. Otherwise, returns an empty object. get address() { - if (this.#state !== kSocketDestroyed) { + const state = this[kInternalState]; + if (state.state !== kSocketDestroyed) { try { - return this.#udpSocket.address(); + return state.udpSocket.address(); } catch (err) { if (err.code === 'EBADF') { // If there is an EBADF error, the socket is not bound. @@ -783,97 +788,102 @@ class QuicEndpoint { } get fd() { - return this.#fd; + return this[kInternalState].fd; } // True if the QuicEndpoint has been destroyed and is // no longer usable. get destroyed() { - return this.#state === kSocketDestroyed; + return this[kInternalState].state === kSocketDestroyed; } // True if binding has been initiated and is in progress. get pending() { - return this.#state === kSocketPending; + return this[kInternalState].state === kSocketPending; } - // True if the QuicEndpoint has been bound to the local - // UDP port. + // True if the QuicEndpoint has been bound to the localUDP port. get bound() { - return this.#state === kSocketBound; + return this[kInternalState].state === kSocketBound; } setTTL(ttl) { - if (this.#state === kSocketDestroyed) + const state = this[kInternalState]; + if (state.state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('setTTL'); - this.#udpSocket.setTTL(ttl); + state.udpSocket.setTTL(ttl); return this; } setMulticastTTL(ttl) { - if (this.#state === kSocketDestroyed) + const state = this[kInternalState]; + if (state.state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('setMulticastTTL'); - this.#udpSocket.setMulticastTTL(ttl); + state.udpSocket.setMulticastTTL(ttl); return this; } setBroadcast(on = true) { - if (this.#state === kSocketDestroyed) + const state = this[kInternalState]; + if (state.state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('setBroadcast'); - this.#udpSocket.setBroadcast(on); + state.udpSocket.setBroadcast(on); return this; } setMulticastLoopback(on = true) { - if (this.#state === kSocketDestroyed) + const state = this[kInternalState]; + if (state.state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('setMulticastLoopback'); - this.#udpSocket.setMulticastLoopback(on); + state.udpSocket.setMulticastLoopback(on); return this; } setMulticastInterface(iface) { - if (this.#state === kSocketDestroyed) + const state = this[kInternalState]; + if (state.state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('setMulticastInterface'); - this.#udpSocket.setMulticastInterface(iface); + state.udpSocket.setMulticastInterface(iface); return this; } addMembership(address, iface) { - if (this.#state === kSocketDestroyed) + const state = this[kInternalState]; + if (state.state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('addMembership'); - this.#udpSocket.addMembership(address, iface); + state.udpSocket.addMembership(address, iface); return this; } dropMembership(address, iface) { - if (this.#state === kSocketDestroyed) + const state = this[kInternalState]; + if (state.state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('dropMembership'); - this.#udpSocket.dropMembership(address, iface); + state.udpSocket.dropMembership(address, iface); return this; } ref() { - if (this.#state === kSocketDestroyed) + const state = this[kInternalState]; + if (state.state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('ref'); - this.#udpSocket.ref(); + state.udpSocket.ref(); return this; } unref() { - if (this.#state === kSocketDestroyed) + const state = this[kInternalState]; + if (state.state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('unref'); - this.#udpSocket.unref(); + state.udpSocket.unref(); return this; } destroy(error) { - // If the QuicEndpoint is already destroyed, do nothing - if (this.#state === kSocketDestroyed) + const state = this[kInternalState]; + if (state.state === kSocketDestroyed) return; - - // Mark the QuicEndpoint as being destroyed. - this.#state = kSocketDestroyed; - + state.state = kSocketDestroyed; this[kDestroy](error); } } @@ -882,20 +892,22 @@ class QuicEndpoint { // Protocol state. There may be *multiple* QUIC connections (QuicSession) // associated with a single QuicSocket. class QuicSocket extends EventEmitter { - #alpn = undefined; - #autoClose = undefined; - #client = undefined; - #defaultEncoding = undefined; - #endpoints = new Set(); - #highWaterMark = undefined; - #lookup = undefined; - #server = undefined; - #serverBusy = false; - #serverListening = false; - #serverSecureContext = undefined; - #sessions = new Set(); - #state = kSocketUnbound; - #stats = undefined; + [kInternalState] = { + alpn: undefined, + autoClose: undefined, + client: undefined, + defaultEncoding: undefined, + endpoints: new Set(), + highWaterMark: undefined, + lookup: undefined, + server: undefined, + serverBusy: undefined, + serverListening: false, + serverSecureContext: undefined, + sessions: new Set(), + state: kSocketUnbound, + stats: undefined, + }; constructor(options) { const { @@ -947,10 +959,12 @@ class QuicSocket extends EventEmitter { } = validateQuicSocketOptions(options); super({ captureRejections: true }); - this.#autoClose = autoClose; - this.#client = client; - this.#lookup = lookup || (type === AF_INET6 ? lookup6 : lookup4); - this.#server = server; + const state = this[kInternalState]; + + state.autoClose = autoClose; + state.client = client; + state.lookup = lookup || (type === AF_INET6 ? lookup6 : lookup4); + state.server = server; const socketOptions = (validateAddress ? QUICSOCKET_OPTIONS_VALIDATE_ADDRESS : 0) | @@ -968,7 +982,7 @@ class QuicSocket extends EventEmitter { disableStatelessReset)); this.addEndpoint({ - lookup: this.#lookup, + lookup: state.lookup, // Keep the lookup and ...endpoint in this order // to allow the passed in endpoint options to // override the lookup specifically for that endpoint @@ -981,9 +995,10 @@ class QuicSocket extends EventEmitter { // streams. These are passed on to new client and server // QuicSession instances when they are created. [kGetStreamOptions]() { + const state = this[kInternalState]; return { - highWaterMark: this.#highWaterMark, - defaultEncoding: this.#defaultEncoding, + highWaterMark: state.highWaterMark, + defaultEncoding: state.defaultEncoding, }; } @@ -996,19 +1011,21 @@ class QuicSocket extends EventEmitter { } [kInspect]() { + // TODO(@jasnell): Proper custom inspect implementation + const state = this[kInternalState]; const obj = { endpoints: this.endpoints, - sessions: this.#sessions, + sessions: state.sessions, }; return `QuicSocket ${util.format(obj)}`; } [kAddSession](session) { - this.#sessions.add(session); + this[kInternalState].sessions.add(session); } [kRemoveSession](session) { - this.#sessions.delete(session); + this[kInternalState].sessions.delete(session); this[kMaybeDestroy](); } @@ -1016,29 +1033,31 @@ class QuicSocket extends EventEmitter { // Function is a non-op if the socket is already bound or in the process of // being bound, and will call the callback once the socket is ready. [kMaybeBind](callback = () => {}) { - if (this.#state === kSocketBound) + const state = this[kInternalState]; + if (state.state === kSocketBound) return process.nextTick(callback); this.once('ready', callback); - if (this.#state === kSocketPending) + if (state.state === kSocketPending) return; - this.#state = kSocketPending; + state.state = kSocketPending; - for (const endpoint of this.#endpoints) + for (const endpoint of state.endpoints) endpoint[kMaybeBind](); } [kEndpointBound](endpoint) { - if (this.#state === kSocketBound) + const state = this[kInternalState]; + if (state.state === kSocketBound) return; - this.#state = kSocketBound; + state.state = kSocketBound; // Once the QuicSocket has been bound, we notify all currently // existing QuicSessions. QuicSessions created after this // point will automatically be notified that the QuicSocket // is ready. - for (const session of this.#sessions) + for (const session of state.sessions) session[kSocketReady](); // The ready event indicates that the QuicSocket is ready to be @@ -1050,14 +1069,15 @@ class QuicSocket extends EventEmitter { // Called when a QuicEndpoint closes [kEndpointClose](endpoint, error) { - this.#endpoints.delete(endpoint); + const state = this[kInternalState]; + state.endpoints.delete(endpoint); process.nextTick(emit.bind(this, 'endpointClose', endpoint, error)); // If there are no more QuicEndpoints, the QuicSocket is no // longer usable. - if (this.#endpoints.size === 0) { + if (state.endpoints.size === 0) { // Ensure that there are absolutely no additional sessions - for (const session of this.#sessions) + for (const session of state.sessions) session.destroy(error); if (error) process.nextTick(emit.bind(this, 'error', error)); @@ -1070,7 +1090,7 @@ class QuicSocket extends EventEmitter { [kDestroy](error) { // The QuicSocket will be destroyed once all QuicEndpoints // are destroyed. See [kEndpointClose]. - for (const endpoint of this.#endpoints) + for (const endpoint of this[kInternalState].endpoints) endpoint.destroy(error); } @@ -1078,7 +1098,7 @@ class QuicSocket extends EventEmitter { // is called. The QuicSocket will be destroyed if there are no remaining // open sessions. [kMaybeDestroy]() { - if (this.closing && this.#sessions.size === 0) { + if (this.closing && this[kInternalState].sessions.size === 0) { this.destroy(); return true; } @@ -1087,33 +1107,34 @@ class QuicSocket extends EventEmitter { // Called by the C++ internals to notify when server busy status is toggled. [kServerBusy](on) { - this.#serverBusy = on; + this[kInternalState].serverBusy = on; process.nextTick(emit.bind(this, 'busy', on)); } addEndpoint(options = {}) { - if (this.#state === kSocketDestroyed) + const state = this[kInternalState]; + if (state.state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('listen'); // TODO(@jasnell): Also forbid adding an endpoint if // the QuicSocket is closing. const endpoint = new QuicEndpoint(this, options); - this.#endpoints.add(endpoint); + state.endpoints.add(endpoint); // If the QuicSocket is already bound at this point, // also bind the newly created QuicEndpoint. - if (this.#state !== kSocketUnbound) + if (state.state !== kSocketUnbound) endpoint[kMaybeBind](); return endpoint; } - // Used only from within the #continueListen function. When a preferred + // Used only from within the [kContinueListen] function. When a preferred // address has been provided, the hostname given must be resolved into an // IP address, which must be passed on to #completeListen or the QuicSocket // needs to be destroyed. - static #afterPreferredAddressLookup = function( + static [kAfterPreferredAddressLookup]( transportParams, port, type, @@ -1123,13 +1144,13 @@ class QuicSocket extends EventEmitter { this.destroy(err); return; } - this.#completeListen(transportParams, { address, port, type }); - }; + this[kCompleteListen](transportParams, { address, port, type }); + } // The #completeListen function is called after all of the necessary // DNS lookups have been performed and we're ready to let the C++ // internals begin listening for new QuicServerSession instances. - #completeListen = function(transportParams, preferredAddress) { + [kCompleteListen](transportParams, preferredAddress) { const { address, port, @@ -1155,15 +1176,16 @@ class QuicSocket extends EventEmitter { // When the handle is told to listen, it will begin acting as a QUIC // server and will emit session events whenever a new QuicServerSession // is created. + const state = this[kInternalState]; this[kHandle].listen( - this.#serverSecureContext.context, + state.serverSecureContext.context, address, type, port, - this.#alpn, + state.alpn, options); process.nextTick(emit.bind(this, 'listening')); - }; + } // When the QuicSocket listen() function is called, the first step is to bind // the underlying QuicEndpoint's. Once at least one endpoint has been bound, @@ -1174,7 +1196,7 @@ class QuicSocket extends EventEmitter { // connecting clients to use. If specified, this will be communicate to the // client as part of the tranport parameters exchanged during the TLS // handshake. - #continueListen = function(transportParams, lookup) { + [kContinueListen](transportParams, lookup) { const { preferredAddress } = transportParams; // TODO(@jasnell): Currently, we wait to start resolving the @@ -1196,7 +1218,7 @@ class QuicSocket extends EventEmitter { // preferred address. lookup( address || (typeVal === AF_INET6 ? '::' : '0.0.0.0'), - QuicSocket.#afterPreferredAddressLookup.bind( + QuicSocket[kAfterPreferredAddressLookup].bind( this, transportParams, port, @@ -1204,19 +1226,20 @@ class QuicSocket extends EventEmitter { return; } // If preferred address is not set, we can skip directly to the listen - this.#completeListen(transportParams); - }; + this[kCompleteListen](transportParams); + } // Begin listening for server connections. The callback that may be // passed to this function is registered as a handler for the // on('session') event. Errors may be thrown synchronously by this // function. listen(options, callback) { - if (this.#serverListening) + const state = this[kInternalState]; + if (state.serverListening) throw new ERR_QUICSOCKET_LISTENING(); - if (this.#state === kSocketDestroyed || - this.#state === kSocketClosing) { + if (state.state === kSocketDestroyed || + state.state === kSocketClosing) { throw new ERR_QUICSOCKET_DESTROYED('listen'); } @@ -1229,7 +1252,7 @@ class QuicSocket extends EventEmitter { throw new ERR_INVALID_CALLBACK(callback); options = { - ...this.#server, + ...state.server, ...options, minVersion: 'TLSv1.3', maxVersion: 'TLSv1.3', @@ -1245,15 +1268,15 @@ class QuicSocket extends EventEmitter { // Store the secure context so that it is not garbage collected // while we still need to make use of it. - this.#serverSecureContext = + state.serverSecureContext = createSecureContext( options, initSecureContext); - this.#highWaterMark = highWaterMark; - this.#defaultEncoding = defaultEncoding; - this.#serverListening = true; - this.#alpn = alpn; + state.highWaterMark = highWaterMark; + state.defaultEncoding = defaultEncoding; + state.serverListening = true; + state.alpn = alpn; // If the callback function is provided, it is registered as a // handler for the on('session') event and will be called whenever @@ -1262,10 +1285,10 @@ class QuicSocket extends EventEmitter { this.on('session', callback); // Bind the QuicSocket to the local port if it hasn't been bound already. - this[kMaybeBind](this.#continueListen.bind( + this[kMaybeBind](this[kContinueListen].bind( this, transportParams, - this.#lookup)); + state.lookup)); } // When the QuicSocket connect() function is called, the first step is to bind @@ -1274,7 +1297,7 @@ class QuicSocket extends EventEmitter { // process. // // The immediate next step is to resolve the address into an ip address. - #continueConnect = function(session, lookup, address, type) { + [kContinueConnect](session, lookup, address, type) { // TODO(@jasnell): Currently, we perform the DNS resolution after // the QuicSocket has been bound. We don't have to. We could do // it in parallel while we're waitint to be bound but doing so @@ -1285,14 +1308,13 @@ class QuicSocket extends EventEmitter { lookup( address || (type === AF_INET6 ? '::' : '0.0.0.0'), connectAfterLookup.bind(session, type)); - }; + } // Creates and returns a new QuicClientSession. connect(options, callback) { - if (this.#state === kSocketDestroyed || - this.#state === kSocketClosing) { + const state = this[kInternalState]; + if (state.state === kSocketDestroyed || state.state === kSocketClosing) throw new ERR_QUICSOCKET_DESTROYED('connect'); - } if (typeof options === 'function') { callback = options; @@ -1300,7 +1322,7 @@ class QuicSocket extends EventEmitter { } options = { - ...this.#client, + ...state.client, ...options }; @@ -1316,10 +1338,10 @@ class QuicSocket extends EventEmitter { if (typeof callback === 'function') session.once('ready', callback); - this[kMaybeBind](this.#continueConnect.bind( + this[kMaybeBind](this[kContinueConnect].bind( this, session, - this.#lookup, + state.lookup, address, type)); @@ -1354,7 +1376,8 @@ class QuicSocket extends EventEmitter { // once('close') event (if specified) and the QuicSocket is destroyed // immediately. close(callback) { - if (this.#state === kSocketDestroyed) + const state = this[kInternalState]; + if (state.state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('close'); // If a callback function is specified, it is registered as a @@ -1370,17 +1393,17 @@ class QuicSocket extends EventEmitter { // If we are already closing, do nothing else and wait // for the close event to be invoked. - if (this.#state === kSocketClosing) + if (state.state === kSocketClosing) return; // If the QuicSocket is otherwise not bound to the local // port, destroy the QuicSocket immediately. - if (this.#state !== kSocketBound) { + if (state.state !== kSocketBound) { this.destroy(); } // Mark the QuicSocket as closing to prevent re-entry - this.#state = kSocketClosing; + state.state = kSocketClosing; // Otherwise, gracefully close each QuicSession, with // [kMaybeDestroy]() being called after each closes. @@ -1393,7 +1416,7 @@ class QuicSocket extends EventEmitter { if (this[kHandle]) { this[kHandle].stopListening(); } - this.#serverListening = false; + state.serverListening = false; // If there are no sessions, calling maybeDestroy // will immediately and synchronously destroy the @@ -1407,7 +1430,7 @@ class QuicSocket extends EventEmitter { // they will call the kMaybeDestroy function. When there // are no remaining session instances, the QuicSocket // will be closed and destroyed. - for (const session of this.#sessions) + for (const session of state.sessions) session.close(maybeDestroy); } @@ -1424,158 +1447,150 @@ class QuicSocket extends EventEmitter { // flushed from the QuicSocket's queue, the QuicSocket C++ instance // will be destroyed and freed from memory. destroy(error) { + const state = this[kInternalState]; // If the QuicSocket is already destroyed, do nothing - if (this.#state === kSocketDestroyed) + if (state.state === kSocketDestroyed) return; // Mark the QuicSocket as being destroyed. - this.#state = kSocketDestroyed; + state.state = kSocketDestroyed; // Immediately close any sessions that may be remaining. // If the udp socket is in a state where it is able to do so, // a final attempt to send CONNECTION_CLOSE frames for each // closed session will be made. - for (const session of this.#sessions) + for (const session of state.sessions) session.destroy(error); this[kDestroy](error); } ref() { - if (this.#state === kSocketDestroyed) + const state = this[kInternalState]; + if (state.state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('ref'); - for (const endpoint of this.#endpoints) + for (const endpoint of state.endpoints) endpoint.ref(); return this; } unref() { - if (this.#state === kSocketDestroyed) + const state = this[kInternalState]; + if (state.state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('unref'); - for (const endpoint of this.#endpoints) + for (const endpoint of state.endpoints) endpoint.unref(); return this; } get endpoints() { - return Array.from(this.#endpoints); + return Array.from(this[kInternalState].endpoints); } // The sever secure context is the SecureContext specified when calling // listen. It is the context that will be used with all new server // QuicSession instances. get serverSecureContext() { - return this.#serverSecureContext; + return this[kInternalState].serverSecureContext; } // True if at least one associated QuicEndpoint has been successfully // bound to a local UDP port. get bound() { - return this.#state === kSocketBound; + return this[kInternalState].state === kSocketBound; } // True if graceful close has been initiated by calling close() get closing() { - return this.#state === kSocketClosing; + return this[kInternalState].state === kSocketClosing; } // True if the QuicSocket has been destroyed and is no longer usable get destroyed() { - return this.#state === kSocketDestroyed; + return this[kInternalState].state === kSocketDestroyed; } // True if listen() has been called successfully get listening() { - return this.#serverListening; + return this[kInternalState].serverListening; } // True if the QuicSocket is currently waiting on at least one // QuicEndpoint to succesfully bind.g get pending() { - return this.#state === kSocketPending; + return this[kInternalState].state === kSocketPending; } // Marking a server as busy will cause all new // connection attempts to fail with a SERVER_BUSY CONNECTION_CLOSE. setServerBusy(on = true) { - if (this.#state === kSocketDestroyed) + const state = this[kInternalState]; + if (state.state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('setServerBusy'); validateBoolean(on, 'on'); - if (this.#serverBusy !== on) + if (state.serverBusy !== on) this[kHandle].setServerBusy(on); } + get serverBusy() { + return this[kInternalState].serverBusy; + } + get duration() { // TODO(@jasnell): If the object is destroyed, it should // use a fixed duration rather than calculating from now - const now = process.hrtime.bigint(); - const stats = this.#stats || this[kHandle].stats; - return now - stats[IDX_QUIC_SOCKET_STATS_CREATED_AT]; + return process.hrtime.bigint() - + getStats(this, IDX_QUIC_SOCKET_STATS_CREATED_AT); } get boundDuration() { // TODO(@jasnell): If the object is destroyed, it should // use a fixed duration rather than calculating from now - const now = process.hrtime.bigint(); - const stats = this.#stats || this[kHandle].stats; - return now - stats[IDX_QUIC_SOCKET_STATS_BOUND_AT]; + return process.hrtime.bigint() - + getStats(this, IDX_QUIC_SOCKET_STATS_BOUND_AT); } get listenDuration() { // TODO(@jasnell): If the object is destroyed, it should // use a fixed duration rather than calculating from now - const now = process.hrtime.bigint(); - const stats = this.#stats || this[kHandle].stats; - return now - stats[IDX_QUIC_SOCKET_STATS_LISTEN_AT]; + return process.hrtime.bigint() - + getStats(this, IDX_QUIC_SOCKET_STATS_LISTEN_AT); } get bytesReceived() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SOCKET_STATS_BYTES_RECEIVED]; + return getStats(this, IDX_QUIC_SOCKET_STATS_BYTES_RECEIVED); } get bytesSent() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SOCKET_STATS_BYTES_SENT]; + return getStats(this, IDX_QUIC_SOCKET_STATS_BYTES_SENT); } get packetsReceived() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SOCKET_STATS_PACKETS_RECEIVED]; + return getStats(this, IDX_QUIC_SOCKET_STATS_PACKETS_RECEIVED); } get packetsSent() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SOCKET_STATS_PACKETS_SENT]; + return getStats(this, IDX_QUIC_SOCKET_STATS_PACKETS_SENT); } get packetsIgnored() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SOCKET_STATS_PACKETS_IGNORED]; - } - - get serverBusy() { - return this.#serverBusy; + return getStats(this, IDX_QUIC_SOCKET_STATS_PACKETS_IGNORED); } get serverSessions() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SOCKET_STATS_SERVER_SESSIONS]; + return getStats(this, IDX_QUIC_SOCKET_STATS_SERVER_SESSIONS); } get clientSessions() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SOCKET_STATS_CLIENT_SESSIONS]; + return getStats(this, IDX_QUIC_SOCKET_STATS_CLIENT_SESSIONS); } get statelessResetCount() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SOCKET_STATS_STATELESS_RESET_COUNT]; + return getStats(this, IDX_QUIC_SOCKET_STATS_STATELESS_RESET_COUNT); } get serverBusyCount() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SOCKET_STATS_SERVER_BUSY_COUNT]; + return getStats(this, IDX_QUIC_SOCKET_STATS_SERVER_BUSY_COUNT); } // Diagnostic packet loss is a testing mechanism that allows simulating @@ -1583,7 +1598,7 @@ class QuicSocket extends EventEmitter { // option is a number between 0 and 1 that identifies the possibility of // packet loss in the given direction. setDiagnosticPacketLoss(options) { - if (this.#state === kSocketDestroyed) + if (this[kInternalState].state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('setDiagnosticPacketLoss'); const { rx = 0.0, @@ -1616,36 +1631,38 @@ class QuicSocket extends EventEmitter { // to be switched on/off dynamically through the lifetime of the // socket. toggleStatelessReset() { - if (this.#state === kSocketDestroyed) + if (this[kInternalState].state === kSocketDestroyed) throw new ERR_QUICSOCKET_DESTROYED('toggleStatelessReset'); return this[kHandle].toggleStatelessReset(); } } class QuicSession extends EventEmitter { - #alpn = undefined; - #cipher = undefined; - #cipherVersion = undefined; - #closeCode = NGTCP2_NO_ERROR; - #closeFamily = QUIC_ERROR_APPLICATION; - #closing = false; - #destroyed = false; - #earlyData = false; - #handshakeComplete = false; - #idleTimeout = false; - #maxPacketLength = NGTCP2_DEFAULT_MAX_PKTLEN; - #servername = undefined; - #socket = undefined; - #statelessReset = false; - #stats = undefined; - #pendingStreams = new Set(); - #streams = new Map(); - #verifyErrorReason = undefined; - #verifyErrorCode = undefined; - #handshakeAckHistogram = undefined; - #handshakeContinuationHistogram = undefined; - #highWaterMark = undefined; - #defaultEncoding = undefined; + [kInternalState] = { + alpn: undefined, + cipher: undefined, + cipherVersion: undefined, + closeCode: NGTCP2_NO_ERROR, + closeFamily: QUIC_ERROR_APPLICATION, + closing: false, + destroyed: false, + earlyData: false, + handshakeComplete: false, + idleTimeout: false, + maxPacketLength: NGTCP2_DEFAULT_MAX_PKTLEN, + servername: undefined, + socket: undefined, + statelessReset: false, + stats: undefined, + pendingStreams: new Set(), + streams: new Map(), + verifyErrorReason: undefined, + verifyErrorCode: undefined, + handshakeAckHistogram: undefined, + handshakeContinuationHistogram: undefined, + highWaterMark: undefined, + defaultEncoding: undefined, + }; constructor(socket, options) { const { @@ -1657,20 +1674,22 @@ class QuicSession extends EventEmitter { super({ captureRejections: true }); this.on('newListener', onNewListener); this.on('removeListener', onRemoveListener); - this.#socket = socket; - this.#servername = servername; - this.#alpn = alpn; - this.#highWaterMark = highWaterMark; - this.#defaultEncoding = defaultEncoding; + const state = this[kInternalState]; + state.socket = socket; + state.servername = servername; + state.alpn = alpn; + state.highWaterMark = highWaterMark; + state.defaultEncoding = defaultEncoding; socket[kAddSession](this); } // kGetStreamOptions is called to get the configured options // for peer initiated QuicStream instances. [kGetStreamOptions]() { + const state = this[kInternalState]; return { - highWaterMark: this.#highWaterMark, - defaultEncoding: this.#defaultEncoding, + highWaterMark: state.highWaterMark, + defaultEncoding: state.defaultEncoding, }; } @@ -1681,16 +1700,17 @@ class QuicSession extends EventEmitter { // must first perform DNS resolution on the provided address // before the underlying QuicSession handle can be created. [kSetHandle](handle) { + const state = this[kInternalState]; this[kHandle] = handle; if (handle !== undefined) { handle[owner_symbol] = this; - this.#handshakeAckHistogram = new Histogram(handle.ack); - this.#handshakeContinuationHistogram = new Histogram(handle.rate); + state.handshakeAckHistogram = new Histogram(handle.ack); + state.handshakeContinuationHistogram = new Histogram(handle.rate); } else { - if (this.#handshakeAckHistogram) - this.#handshakeAckHistogram[kDestroyHistogram](); - if (this.#handshakeContinuationHistogram) - this.#handshakeContinuationHistogram[kDestroyHistogram](); + if (state.handshakeAckHistogram) + state.handshakeAckHistogram[kDestroyHistogram](); + if (state.handshakeContinuationHistogram) + state.handshakeContinuationHistogram[kDestroyHistogram](); } } @@ -1715,9 +1735,10 @@ class QuicSession extends EventEmitter { // Causes the QuicSession to be immediately destroyed, but with // additional metadata set. [kDestroy](statelessReset, family, code) { - this.#statelessReset = !!statelessReset; - this.#closeCode = code; - this.#closeFamily = family; + const state = this[kInternalState]; + state.statelessReset = statelessReset; + state.closeCode = code; + state.closeFamily = family; this.destroy(); } @@ -1728,21 +1749,22 @@ class QuicSession extends EventEmitter { // CONNECTION_CLOSE will be generated and sent, switching // the session into the closing period. [kClose](family, code) { + const state = this[kInternalState]; // Do nothing if the QuicSession has already been destroyed. - if (this.#destroyed) + if (state.destroyed) return; // Set the close code and family so we can keep track. - this.#closeCode = code; - this.#closeFamily = family; + state.closeCode = code; + state.closeFamily = family; // Shutdown all pending streams. These are Streams that // have been created but do not yet have a handle assigned. - for (const stream of this.#pendingStreams) + for (const stream of state.pendingStreams) stream[kClose](family, code); // Shutdown all of the remaining streams - for (const stream of this.#streams.values()) + for (const stream of state.streams.values()) stream[kClose](family, code); // By this point, all necessary RESET_STREAM and @@ -1755,7 +1777,7 @@ class QuicSession extends EventEmitter { // Closes the specified stream with the given code. The // QuicStream object will be destroyed. [kStreamClose](id, code) { - const stream = this.#streams.get(id); + const stream = this[kInternalState].streams.get(id); if (stream === undefined) return; @@ -1766,7 +1788,7 @@ class QuicSession extends EventEmitter { // instance. This will only be called if the ALPN selected // is known to support headers. [kHeaders](id, headers, kind, push_id) { - const stream = this.#streams.get(id); + const stream = this[kInternalState].streams.get(id); if (stream === undefined) return; @@ -1774,7 +1796,7 @@ class QuicSession extends EventEmitter { } [kStreamReset](id, code) { - const stream = this.#streams.get(id); + const stream = this[kInternalState].streams.get(id); if (stream === undefined) return; @@ -1782,16 +1804,18 @@ class QuicSession extends EventEmitter { } [kInspect]() { + // TODO(@jasnell): A proper custom inspect implementation + const state = this[kInternalState]; const obj = { - alpn: this.#alpn, + alpn: state.alpn, cipher: this.cipher, closing: this.closing, closeCode: this.closeCode, destroyed: this.destroyed, - earlyData: this.#earlyData, + earlyData: state.earlyData, maxStreams: this.maxStreams, servername: this.servername, - streams: this.#streams.size, + streams: state.streams.size, stats: { handshakeAck: this.handshakeAckHistogram, handshakeContinuation: this.handshakeContinuationHistogram, @@ -1801,7 +1825,7 @@ class QuicSession extends EventEmitter { } [kSetSocket](socket) { - this.#socket = socket; + this[kInternalState].socket = socket; } // Called at the completion of the TLS handshake for the local peer @@ -1814,15 +1838,16 @@ class QuicSession extends EventEmitter { verifyErrorReason, verifyErrorCode, earlyData) { - this.#handshakeComplete = true; - this.#servername = servername; - this.#alpn = alpn; - this.#cipher = cipher; - this.#cipherVersion = cipherVersion; - this.#maxPacketLength = maxPacketLength; - this.#verifyErrorReason = verifyErrorReason; - this.#verifyErrorCode = verifyErrorCode; - this.#earlyData = earlyData; + const state = this[kInternalState]; + state.handshakeComplete = true; + state.servername = servername; + state.alpn = alpn; + state.cipher = cipher; + state.cipherVersion = cipherVersion; + state.maxPacketLength = maxPacketLength; + state.verifyErrorReason = verifyErrorReason; + state.verifyErrorCode = verifyErrorCode; + state.earlyData = earlyData; if (!this[kHandshakePost]()) return; @@ -1838,18 +1863,19 @@ class QuicSession extends EventEmitter { } [kRemoveStream](stream) { - this.#streams.delete(stream.id); + this[kInternalState].streams.delete(stream.id); } [kAddStream](id, stream) { stream.once('close', this[kMaybeDestroy].bind(this)); - this.#streams.set(id, stream); + this[kInternalState].streams.set(id, stream); } // The QuicSession will be destroyed if closing has been // called and there are no remaining streams [kMaybeDestroy]() { - if (this.#closing && this.#streams.size === 0) + const state = this[kInternalState]; + if (state.closing && state.streams.size === 0) this.destroy(); } @@ -1867,7 +1893,8 @@ class QuicSession extends EventEmitter { // opened. Calls to openStream() will fail, and new streams // from the peer will be rejected/ignored. close(callback) { - if (this.#destroyed) + const state = this[kInternalState]; + if (state.destroyed) throw new ERR_QUICSESSION_DESTROYED('close'); if (callback) { @@ -1879,10 +1906,10 @@ class QuicSession extends EventEmitter { // If we're already closing, do nothing else. // Callback will be invoked once the session // has been destroyed - if (this.#closing) + if (state.closing) return; - this.#closing = true; + state.closing = true; this[kHandle].gracefulClose(); // See if we can close immediately. @@ -1903,11 +1930,12 @@ class QuicSession extends EventEmitter { // Once destroyed, and after the 'error' event (if any), // the close event is emitted on next tick. destroy(error) { + const state = this[kInternalState]; // Destroy can only be called once. Multiple calls will be ignored - if (this.#destroyed) + if (state.destroyed) return; - this.#destroyed = true; - this.#closing = false; + state.destroyed = true; + state.closing = false; if (typeof error === 'number' || (error != null && @@ -1917,19 +1945,19 @@ class QuicSession extends EventEmitter { closeCode, closeFamily } = validateCloseCode(error); - this.#closeCode = closeCode; - this.#closeFamily = closeFamily; + state.closeCode = closeCode; + state.closeFamily = closeFamily; error = new ERR_QUIC_ERROR(closeCode, closeFamily); } // Destroy any pending streams immediately. These // are streams that have been created but have not // yet been assigned an internal handle. - for (const stream of this.#pendingStreams) + for (const stream of state.pendingStreams) stream.destroy(error); // Destroy any remaining streams immediately. - for (const stream of this.#streams.values()) + for (const stream of state.streams.values()) stream.destroy(error); this.removeListener('newListener', onNewListener); @@ -1940,20 +1968,20 @@ class QuicSession extends EventEmitter { const handle = this[kHandle]; if (handle !== undefined) { // Copy the stats for use after destruction - this.#stats = new BigInt64Array(handle.stats); - this.#idleTimeout = !!handle.state[IDX_QUIC_SESSION_STATE_IDLE_TIMEOUT]; + state.stats = new BigInt64Array(handle.stats); + state.idleTimeout = !!handle.state[IDX_QUIC_SESSION_STATE_IDLE_TIMEOUT]; // Calling destroy will cause a CONNECTION_CLOSE to be // sent to the peer and will destroy the QuicSession // handler immediately. - handle.destroy(this.#closeCode, this.#closeFamily); + handle.destroy(state.closeCode, state.closeFamily); } else { process.nextTick(emit.bind(this, 'close')); } // Remove the QuicSession JavaScript object from the // associated QuicSocket. - this.#socket[kRemoveSession](this); - this.#socket = undefined; + state.socket[kRemoveSession](this); + state.socket = undefined; } // For server QuicSession instances, true if earlyData is @@ -1963,7 +1991,7 @@ class QuicSession extends EventEmitter { // TLS handshake is completed (immeditely before the // secure event is emitted) get usingEarlyData() { - return this.#earlyData; + return this[kInternalState].earlyData; } get maxStreams() { @@ -1977,37 +2005,35 @@ class QuicSession extends EventEmitter { } get address() { - return this.#socket ? this.#socket.address : {}; + return this[kInternalState].socket?.address || {}; } get maxDataLeft() { - return this[kHandle] ? - this[kHandle].state[IDX_QUIC_SESSION_STATE_MAX_DATA_LEFT] : 0; + return this[kHandle]?.state[IDX_QUIC_SESSION_STATE_MAX_DATA_LEFT] || 0; } get bytesInFlight() { - return this[kHandle] ? - this[kHandle].state[IDX_QUIC_SESSION_STATE_BYTES_IN_FLIGHT] : 0; + return this[kHandle]?.state[IDX_QUIC_SESSION_STATE_BYTES_IN_FLIGHT] || 0; } get blockCount() { - return this[kHandle] ? - this[kHandle].state[IDX_QUIC_SESSION_STATS_BLOCK_COUNT] : 0; + return this[kHandle]?.state[IDX_QUIC_SESSION_STATS_BLOCK_COUNT] || 0; } get authenticated() { // Specifically check for null. Undefined means the check has not // been performed yet, another other value other than null means // there was an error - return this.#verifyErrorReason == null; + return this[kInternalState].verifyErrorReason == null; } get authenticationError() { if (this.authenticated) return undefined; + const state = this[kInternalState]; // eslint-disable-next-line no-restricted-syntax - const err = new Error(this.#verifyErrorReason); - const code = 'ERR_QUIC_VERIFY_' + this.#verifyErrorCode; + const err = new Error(state.verifyErrorReason); + const code = 'ERR_QUIC_VERIFY_' + state.verifyErrorCode; err.name = `Error [${code}]`; err.code = code; return err; @@ -2021,26 +2047,30 @@ class QuicSession extends EventEmitter { } get handshakeComplete() { - return this.#handshakeComplete; + return this[kInternalState].handshakeComplete; } get handshakeConfirmed() { - return Boolean(this[kHandle] ? - this[kHandle].state[IDX_QUIC_SESSION_STATE_HANDSHAKE_CONFIRMED] : 0); + return Boolean( + this[kHandle]?.state[IDX_QUIC_SESSION_STATE_HANDSHAKE_CONFIRMED]); } get idleTimeout() { - return this.#idleTimeout; + return this[kInternalState].idleTimeout; } get alpnProtocol() { - return this.#alpn; + return this[kInternalState].alpn; } get cipher() { - const name = this.#cipher; - const version = this.#cipherVersion; - return this.handshakeComplete ? { name, version } : {}; + if (!this.handshakeComplete) + return {}; + const state = this[kInternalState]; + return { + name: state.cipher, + version: state.cipherVersion, + }; } getCertificate() { @@ -2061,34 +2091,36 @@ class QuicSession extends EventEmitter { } get servername() { - return this.#servername; + return this[kInternalState].servername; } get destroyed() { - return this.#destroyed; + return this[kInternalState].destroyed; } get closing() { - return this.#closing; + return this[kInternalState].closing; } get closeCode() { + const state = this[kInternalState]; return { - code: this.#closeCode, - family: this.#closeFamily + code: state.closeCode, + family: state.closeFamily }; } get socket() { - return this.#socket; + return this[kInternalState].socket; } get statelessReset() { - return this.#statelessReset; + return this[kInternalState].statelessReset; } openStream(options) { - if (this.#destroyed || this.#closing) + const state = this[kInternalState]; + if (state.destroyed || state.closing) throw new ERR_QUICSESSION_DESTROYED('openStream'); const { halfOpen, // Unidirectional or Bidirectional @@ -2108,13 +2140,13 @@ class QuicSession extends EventEmitter { stream.read(); } - this.#pendingStreams.add(stream); + state.pendingStreams.add(stream); // If early data is being used, we can create the internal QuicStream on the // ready event, that is immediately after the internal QuicSession handle // has been created. Otherwise, we have to wait until the secure event // signaling the completion of the TLS handshake. - const makeStream = QuicSession.#makeStream.bind(this, stream, halfOpen); + const makeStream = QuicSession[kMakeStream].bind(this, stream, halfOpen); let deferred = false; if (this.allowEarlyData && !this.ready) { deferred = true; @@ -2130,8 +2162,8 @@ class QuicSession extends EventEmitter { return stream; } - static #makeStream = function(stream, halfOpen) { - this.#pendingStreams.delete(stream); + static [kMakeStream](stream, halfOpen) { + this[kInternalState].pendingStreams.delete(stream); const handle = halfOpen ? _openUnidirectionalStream(this[kHandle]) : @@ -2144,102 +2176,89 @@ class QuicSession extends EventEmitter { stream[kSetHandle](handle); this[kAddStream](stream.id, stream); - }; + } get duration() { - const now = process.hrtime.bigint(); - const stats = this.#stats || this[kHandle].stats; - return now - stats[IDX_QUIC_SESSION_STATS_CREATED_AT]; + return process.hrtime.bigint() - + getStats(this, IDX_QUIC_SESSION_STATS_CREATED_AT); } get handshakeDuration() { - const stats = this.#stats || this[kHandle].stats; const end = this.handshakeComplete ? - stats[4] : process.hrtime.bigint(); - return end - stats[IDX_QUIC_SESSION_STATS_HANDSHAKE_START_AT]; + getStats(this, IDX_QUIC_SESSION_STATS_HANDSHAKE_COMPLETED_AT) : + process.hrtime.bigint(); + return end - getStats(this, IDX_QUIC_SESSION_STATS_HANDSHAKE_START_AT); } get bytesReceived() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SESSION_STATS_BYTES_RECEIVED]; + return getStats(this, IDX_QUIC_SESSION_STATS_BYTES_RECEIVED); } get bytesSent() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SESSION_STATS_BYTES_SENT]; + return getStats(this, IDX_QUIC_SESSION_STATS_BYTES_SENT); } get bidiStreamCount() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SESSION_STATS_BIDI_STREAM_COUNT]; + return getStats(this, IDX_QUIC_SESSION_STATS_BIDI_STREAM_COUNT); } get uniStreamCount() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SESSION_STATS_UNI_STREAM_COUNT]; + return getStats(this, IDX_QUIC_SESSION_STATS_UNI_STREAM_COUNT); } get maxInFlightBytes() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SESSION_STATS_MAX_BYTES_IN_FLIGHT]; + return getStats(this, IDX_QUIC_SESSION_STATS_MAX_BYTES_IN_FLIGHT); } get lossRetransmitCount() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SESSION_STATS_LOSS_RETRANSMIT_COUNT]; + return getStats(this, IDX_QUIC_SESSION_STATS_LOSS_RETRANSMIT_COUNT); } get ackDelayRetransmitCount() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SESSION_STATS_ACK_DELAY_RETRANSMIT_COUNT]; + return getStats(this, IDX_QUIC_SESSION_STATS_ACK_DELAY_RETRANSMIT_COUNT); } get peerInitiatedStreamCount() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SESSION_STATS_STREAMS_IN_COUNT]; + return getStats(this, IDX_QUIC_SESSION_STATS_STREAMS_IN_COUNT); } get selfInitiatedStreamCount() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SESSION_STATS_STREAMS_OUT_COUNT]; + return getStats(this, IDX_QUIC_SESSION_STATS_STREAMS_OUT_COUNT); } get keyUpdateCount() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SESSION_STATS_KEYUPDATE_COUNT]; + return getStats(this, IDX_QUIC_SESSION_STATS_KEYUPDATE_COUNT); } get minRTT() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SESSION_STATS_MIN_RTT]; + return getStats(this, IDX_QUIC_SESSION_STATS_MIN_RTT); } get latestRTT() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SESSION_STATS_LATEST_RTT]; + return getStats(this, IDX_QUIC_SESSION_STATS_LATEST_RTT); } get smoothedRTT() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_SESSION_STATS_SMOOTHED_RTT]; + return getStats(this, IDX_QUIC_SESSION_STATS_SMOOTHED_RTT); } updateKey() { + const state = this[kInternalState]; // Initiates a key update for the connection. - if (this.#destroyed || this.#closing) + if (state.destroyed || state.closing) throw new ERR_QUICSESSION_DESTROYED('updateKey'); - if (!this.handshakeConfirmed) + if (!state.handshakeConfirmed) throw new ERR_QUICSESSION_UPDATEKEY(); return this[kHandle].updateKey(); } get handshakeAckHistogram() { - return this.#handshakeAckHistogram; + return this[kInternalState].handshakeAckHistogram; } get handshakeContinuationHistogram() { - return this.#handshakeContinuationHistogram; + return this[kInternalState].handshakeContinuationHistogram; } // TODO(addaleax): This is a temporary solution for testing and should be @@ -2250,7 +2269,9 @@ class QuicSession extends EventEmitter { } class QuicServerSession extends QuicSession { - #contexts = []; + [kInternalServerState] = { + contexts: [] + }; constructor(socket, handle, options) { const { @@ -2280,11 +2301,12 @@ class QuicServerSession extends QuicSession { // Called only when an OCSPRequest event handler is registered. // Allows user code the ability to answer the OCSP query. [kCert](servername, callback) { + const state = this[kInternalServerState]; const { serverSecureContext } = this.socket; let { context } = serverSecureContext; - for (var i = 0; i < this.#contexts.length; i++) { - const elem = this.#contexts[i]; + for (var i = 0; i < state.contexts.length; i++) { + const elem = state.contexts[i]; if (elem[0].test(servername)) { context = elem[1]; break; @@ -2315,27 +2337,30 @@ class QuicServerSession extends QuicSession { servername.replace(/([.^$+?\-\\[\]{}])/g, '\\$1') .replace(/\*/g, '[^.]*') + '$'); - this.#contexts.push([re, _createSecureContext(context)]); + this[kInternalServerState].contexts.push( + [re, _createSecureContext(context)]); } } class QuicClientSession extends QuicSession { - #allowEarlyData = false; - #autoStart = true; - #dcid = undefined; - #handshakeStarted = false; - #ipv6Only = undefined; - #minDHSize = undefined; - #port = undefined; - #ready = 0; - #remoteTransportParams = undefined; - #requestOCSP = undefined; - #secureContext = undefined; - #sessionTicket = undefined; - #transportParams = undefined; - #preferredAddressPolicy; - #verifyHostnameIdentity = true; - #qlogEnabled = false; + [kInternalClientState] = { + allowEarlyData: false, + autoStart: true, + dcid: undefined, + handshakeStarted: false, + ipv6Only: undefined, + minDHSize: undefined, + port: undefined, + ready: 0, + remoteTransportParams: undefined, + requestOCSP: undefined, + secureContext: undefined, + sessionTicket: undefined, + transportParams: undefined, + preferredAddressPolicy: undefined, + verifyHostnameIdentity: true, + qlogEnabled: false, + }; constructor(socket, options) { const sc_options = { @@ -2371,27 +2396,28 @@ class QuicClientSession extends QuicSession { } super(socket, { servername, alpn, highWaterMark, defaultEncoding }); - this.#autoStart = autoStart; - this.#handshakeStarted = autoStart; - this.#dcid = dcid; - this.#ipv6Only = ipv6Only; - this.#minDHSize = minDHSize; - this.#port = port || 0; - this.#preferredAddressPolicy = preferredAddressPolicy; - this.#requestOCSP = requestOCSP; - this.#secureContext = + const state = this[kInternalClientState]; + state.autoStart = autoStart; + state.handshakeStarted = autoStart; + state.dcid = dcid; + state.ipv6Only = ipv6Only; + state.minDHSize = minDHSize; + state.port = port || 0; + state.preferredAddressPolicy = preferredAddressPolicy; + state.requestOCSP = requestOCSP; + state.secureContext = createSecureContext( sc_options, initSecureContextClient); - this.#transportParams = validateTransportParams(options); - this.#verifyHostnameIdentity = verifyHostnameIdentity; - this.#qlogEnabled = qlog; + state.transportParams = validateTransportParams(options); + state.verifyHostnameIdentity = verifyHostnameIdentity; + state.qlogEnabled = qlog; // If provided, indicates that the client is attempting to // resume a prior session. Early data would be enabled. - this.#remoteTransportParams = remoteTransportParams; - this.#sessionTicket = sessionTicket; - this.#allowEarlyData = + state.remoteTransportParams = remoteTransportParams; + state.sessionTicket = sessionTicket; + state.allowEarlyData = remoteTransportParams !== undefined && sessionTicket !== undefined; @@ -2401,7 +2427,7 @@ class QuicClientSession extends QuicSession { [kHandshakePost]() { const { type, size } = this.ephemeralKeyInfo; - if (type === 'DH' && size < this.#minDHSize) { + if (type === 'DH' && size < this[kInternalClientState].minDHSize) { this.destroy(new ERR_TLS_DH_PARAM_SIZE(size)); return false; } @@ -2413,12 +2439,13 @@ class QuicClientSession extends QuicSession { } [kContinueConnect](type, ip) { - setTransportParams(this.#transportParams); + const state = this[kInternalClientState]; + setTransportParams(state.transportParams); const options = - (this.#verifyHostnameIdentity ? + (state.verifyHostnameIdentity ? QUICCLIENTSESSION_OPTION_VERIFY_HOSTNAME_IDENTITY : 0) | - (this.#requestOCSP ? + (state.requestOCSP ? QUICCLIENTSESSION_OPTION_REQUEST_OCSP : 0); const handle = @@ -2426,23 +2453,23 @@ class QuicClientSession extends QuicSession { this.socket[kHandle], type, ip, - this.#port, - this.#secureContext.context, + state.port, + state.secureContext.context, this.servername || ip, - this.#remoteTransportParams, - this.#sessionTicket, - this.#dcid, - this.#preferredAddressPolicy, + state.remoteTransportParams, + state.sessionTicket, + state.dcid, + state.preferredAddressPolicy, this.alpnProtocol, options, - this.#qlogEnabled, - this.#autoStart); + state.qlogEnabled, + state.autoStart); // We no longer need these, unset them so // memory can be garbage collected. - this.#remoteTransportParams = undefined; - this.#sessionTicket = undefined; - this.#dcid = undefined; + state.remoteTransportParams = undefined; + state.sessionTicket = undefined; + state.dcid = undefined; // If handle is a number, creating the session failed. if (typeof handle === 'number') { @@ -2478,40 +2505,41 @@ class QuicClientSession extends QuicSession { if (this.listenerCount('usePreferredAddress') > 0) toggleListeners(handle, 'usePreferredAddress', true); - this.#maybeReady(0x2); + this[kMaybeReady](0x2); } [kSocketReady]() { - this.#maybeReady(0x1); + this[kMaybeReady](0x1); } // The QuicClientSession is ready for use only after // (a) The QuicSocket has been bound and // (b) The internal handle has been created - #maybeReady = function(flag) { - this.#ready |= flag; + [kMaybeReady](flag) { + this[kInternalClientState].ready |= flag; if (this.ready) process.nextTick(emit.bind(this, 'ready')); - }; + } get allowEarlyData() { - return this.#allowEarlyData; + return this[kInternalClientState].allowEarlyData; } get ready() { - return this.#ready === 0x3; + return this[kInternalClientState].ready === 0x3; } get handshakeStarted() { - return this.#handshakeStarted; + return this[kInternalClientState].handshakeStarted; } startHandshake() { + const state = this[kInternalClientState]; if (this.destroyed) throw new ERR_QUICSESSION_DESTROYED('startHandshake'); - if (this.#handshakeStarted) + if (state.handshakeStarted) return; - this.#handshakeStarted = true; + state.handshakeStarted = true; if (!this.ready) { this.once('ready', () => this[kHandle].startHandshake()); } else { @@ -2525,7 +2553,7 @@ class QuicClientSession extends QuicSession { {}; } - #setSocketAfterBind = function(socket, callback) { + [kSetSocketAfterBind](socket, callback) { if (socket.destroyed) { callback(new ERR_QUICSOCKET_DESTROYED('setSocket')); return; @@ -2544,7 +2572,7 @@ class QuicClientSession extends QuicSession { this[kSetSocket](socket); callback(); - }; + } setSocket(socket, callback) { if (!(socket instanceof QuicSocket)) @@ -2553,7 +2581,7 @@ class QuicClientSession extends QuicSession { if (typeof callback !== 'function') throw new ERR_INVALID_CALLBACK(); - socket[kMaybeBind](() => this.#setSocketAfterBind(socket, callback)); + socket[kMaybeBind](() => this[kSetSocketAfterBind](socket, callback)); } } @@ -2568,19 +2596,21 @@ function streamOnPause() { } class QuicStream extends Duplex { - #closed = false; - #aborted = false; - #defaultEncoding = undefined; - #didRead = false; - #id = undefined; - #highWaterMark = undefined; - #push_id = undefined; - #resetCode = undefined; - #session = undefined; - #dataRateHistogram = undefined; - #dataSizeHistogram = undefined; - #dataAckHistogram = undefined; - #stats = undefined; + [kInternalState] = { + closed: false, + aborted: false, + defaultEncoding: undefined, + didRead: false, + id: undefined, + highWaterMark: undefined, + push_id: undefined, + resetCode: undefined, + session: undefined, + dataRateHistogram: undefined, + dataSizeHistogram: undefined, + dataAckHistogram: undefined, + stats: undefined, + }; constructor(options, session, push_id) { const { @@ -2597,16 +2627,18 @@ class QuicStream extends Duplex { autoDestroy: false, captureRejections: true, }); - this.#highWaterMark = highWaterMark; - this.#defaultEncoding = defaultEncoding; - this.#session = session; - this.#push_id = push_id; + const state = this[kInternalState]; + state.highWaterMark = highWaterMark; + state.defaultEncoding = defaultEncoding; + state.session = session; + state.push_id = push_id; this._readableState.readingMore = true; this.on('pause', streamOnPause); // The QuicStream writes are corked until kSetHandle // is set, ensuring that writes are buffered in JavaScript // until we have somewhere to send them. + // TODO(@jasnell): We need a better mechanism for this. this.cork(); } @@ -2618,28 +2650,29 @@ class QuicStream extends Duplex { // written will be buffered until kSetHandle is called. [kSetHandle](handle) { this[kHandle] = handle; + const state = this[kInternalState]; if (handle !== undefined) { handle.onread = onStreamRead; handle[owner_symbol] = this; this[async_id_symbol] = handle.getAsyncId(); - this.#id = handle.id(); - this.#dataRateHistogram = new Histogram(handle.rate); - this.#dataSizeHistogram = new Histogram(handle.size); - this.#dataAckHistogram = new Histogram(handle.ack); + state.id = handle.id(); + state.dataRateHistogram = new Histogram(handle.rate); + state.dataSizeHistogram = new Histogram(handle.size); + state.dataAckHistogram = new Histogram(handle.ack); this.uncork(); this.emit('ready'); } else { - if (this.#dataRateHistogram) - this.#dataRateHistogram[kDestroyHistogram](); - if (this.#dataSizeHistogram) - this.#dataSizeHistogram[kDestroyHistogram](); - if (this.#dataAckHistogram) - this.#dataAckHistogram[kDestroyHistogram](); + if (state.dataRateHistogram) + state.dataRateHistogram[kDestroyHistogram](); + if (state.dataSizeHistogram) + state.dataSizeHistogram[kDestroyHistogram](); + if (state.dataAckHistogram) + state.dataAckHistogram[kDestroyHistogram](); } } [kStreamReset](code) { - this.#resetCode = code | 0; + this[kInternalState].resetCode = code | 0; this.push(null); this.read(); } @@ -2669,6 +2702,7 @@ class QuicStream extends Duplex { } [kClose](family, code) { + const state = this[kInternalState]; // Trigger the abrupt shutdown of the stream. If the stream is // already no-longer readable or writable, this does nothing. If // the stream is readable or writable, then the abort event will @@ -2679,15 +2713,15 @@ class QuicStream extends Duplex { // having been closed to be destroyed. // Do nothing if we've already been destroyed - if (this.destroyed || this.#closed) + if (this.destroyed || state.closed) return; if (this.pending) return this.once('ready', () => this[kClose](family, code)); - this.#closed = true; + state.closed = true; - this.#aborted = this.readable || this.writable; + state.aborted = this.readable || this.writable; // Trigger scheduling of the RESET_STREAM and STOP_SENDING frames // as appropriate. Notify ngtcp2 that the stream is to be shutdown. @@ -2713,10 +2747,11 @@ class QuicStream extends Duplex { } [kInspect]() { + // TODO(@jasnell): Proper custom inspect implementation const direction = this.bidirectional ? 'bidirectional' : 'unidirectional'; const initiated = this.serverInitiated ? 'server' : 'client'; const obj = { - id: this.#id, + id: this[kInternalState].id, direction, initiated, writableState: this._writableState, @@ -2743,15 +2778,15 @@ class QuicStream extends Duplex { get pending() { // The id is set in the kSetHandle function - return this.#id === undefined; + return this[kInternalState].id === undefined; } get aborted() { - return this.#aborted; + return this[kInternalState].aborted; } get serverInitiated() { - return !!(this.#id & 0b01); + return !!(this[kInternalState].id & 0b01); } get clientInitiated() { @@ -2759,14 +2794,14 @@ class QuicStream extends Duplex { } get unidirectional() { - return !!(this.#id & 0b10); + return !!(this[kInternalState].id & 0b10); } get bidirectional() { return !this.unidirectional; } - #writeGeneric = function(writev, data, encoding, cb) { + [kWriteGeneric](writev, data, encoding, cb) { if (this.destroyed) return; // TODO(addaleax): Can this happen? @@ -2776,7 +2811,7 @@ class QuicStream extends Duplex { // ready event is emitted. if (this.pending) { return this.once('ready', () => { - this.#writeGeneric(writev, data, encoding, cb); + this[kWriteGeneric](writev, data, encoding, cb); }); } @@ -2786,14 +2821,14 @@ class QuicStream extends Duplex { writeGeneric(this, data, encoding, cb); this[kTrackWriteState](this, req.bytes); - }; + } _write(data, encoding, cb) { - this.#writeGeneric(false, data, encoding, cb); + this[kWriteGeneric](false, data, encoding, cb); } _writev(data, cb) { - this.#writeGeneric(true, data, '', cb); + this[kWriteGeneric](true, data, '', cb); } // Called when the last chunk of data has been @@ -2832,19 +2867,20 @@ class QuicStream extends Duplex { this.push(null); return; } - if (!this.#didRead) { + const state = this[kInternalState]; + if (!state.didRead) { this._readableState.readingMore = false; - this.#didRead = true; + state.didRead = true; } streamOnResume.call(this); } sendFile(path, options = {}) { - fs.open(path, 'r', QuicStream.#onFileOpened.bind(this, options)); + fs.open(path, 'r', QuicStream[kOnFileOpened].bind(this, options)); } - static #onFileOpened = function(options, err, fd) { + static [kOnFileOpened](options, err, fd) { const onError = options.onError; if (err) { if (onError) { @@ -2862,10 +2898,10 @@ class QuicStream extends Duplex { } this.sendFD(fd, options, true); - }; + } sendFD(fd, { offset = -1, length = -1 } = {}, ownsFd = false) { - if (this.destroyed || this.#closed) + if (this.destroyed || this[kInternalState].closed) return; validateInteger(offset, 'options.offset', /* min */ -1); @@ -2891,17 +2927,17 @@ class QuicStream extends Duplex { this.end(); defaultTriggerAsyncIdScope(this[async_id_symbol], - QuicStream.#startFilePipe, + QuicStream[kStartFilePipe], this, fd, offset, length); } - static #startFilePipe = (stream, fd, offset, length) => { + static [kStartFilePipe](stream, fd, offset, length) { const handle = new FileHandle(fd, offset, length); - handle.onread = QuicStream.#onPipedFileHandleRead; + handle.onread = QuicStream[kOnPipedFileHandleRead]; handle.stream = stream; const pipe = new StreamPipe(handle, stream[kHandle]); - pipe.onunpipe = QuicStream.#onFileUnpipe; + pipe.onunpipe = QuicStream[kOnFileUnpipe]; pipe.start(); // Exact length of the file doesn't matter here, since the @@ -2910,24 +2946,25 @@ class QuicStream extends Duplex { stream[kTrackWriteState](stream, 1); } - static #onFileUnpipe = function() { // Called on the StreamPipe instance. + static [kOnFileUnpipe]() { // Called on the StreamPipe instance. const stream = this.sink[owner_symbol]; if (stream.ownsFd) this.source.close().catch(stream.destroy.bind(stream)); else this.source.releaseFD(); - }; + } - static #onPipedFileHandleRead = function() { + static [kOnPipedFileHandleRead]() { const err = streamBaseState[kReadBytesOrError]; if (err < 0 && err !== UV_EOF) { this.stream.destroy(errnoException(err, 'sendFD')); } - }; + } get resetReceived() { - return (this.#resetCode !== undefined) ? - { code: this.#resetCode | 0 } : + const state = this[kInternalState]; + return (state.resetCode !== undefined) ? + { code: state.resetCode | 0 } : undefined; } @@ -2937,11 +2974,11 @@ class QuicStream extends Duplex { } get id() { - return this.#id; + return this[kInternalState].id; } get push_id() { - return this.#push_id; + return this[kInternalState].push_id; } close(code) { @@ -2949,17 +2986,18 @@ class QuicStream extends Duplex { } get session() { - return this.#session; + return this[kInternalState].session; } _destroy(error, callback) { - this.#session[kRemoveStream](this); + const state = this[kInternalState]; + state.session[kRemoveStream](this); const handle = this[kHandle]; // Do not use handle after this point as the underlying C++ // object has been destroyed. Any attempt to use the object // will segfault and crash the process. if (handle !== undefined) { - this.#stats = new BigInt64Array(handle.stats); + state.stats = new BigInt64Array(handle.stats); handle.destroy(); } // The destroy callback must be invoked in a nextTick @@ -2971,24 +3009,25 @@ class QuicStream extends Duplex { } get dataRateHistogram() { - return this.#dataRateHistogram; + return this[kInternalState].dataRateHistogram; } get dataSizeHistogram() { - return this.#dataSizeHistogram; + return this[kInternalState].dataSizeHistogram; } get dataAckHistogram() { - return this.#dataAckHistogram; + return this[kInternalState].dataAckHistogram; } pushStream(headers = {}, options = {}) { if (this.destroyed) throw new ERR_QUICSTREAM_DESTROYED('push'); + const state = this[kInternalState]; const { - highWaterMark = this.#highWaterMark, - defaultEncoding = this.#defaultEncoding, + highWaterMark = state.highWaterMark, + defaultEncoding = state.defaultEncoding, } = validateQuicStreamOptions(options); validateObject(headers, 'headers'); @@ -3119,39 +3158,32 @@ class QuicStream extends Duplex { } get duration() { - const now = process.hrtime.bigint(); - const stats = this.#stats || this[kHandle].stats; - return now - stats[IDX_QUIC_STREAM_STATS_CREATED_AT]; + return process.hrtime.bigint() - + getStats(this, IDX_QUIC_STREAM_STATS_CREATED_AT); } get bytesReceived() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_STREAM_STATS_BYTES_RECEIVED]; + return getStats(this, IDX_QUIC_STREAM_STATS_BYTES_RECEIVED); } get bytesSent() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_STREAM_STATS_BYTES_SENT]; + return getStats(this, IDX_QUIC_STREAM_STATS_BYTES_SENT); } get maxExtendedOffset() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_STREAM_STATS_MAX_OFFSET]; + return getStats(this, IDX_QUIC_STREAM_STATS_MAX_OFFSET); } get finalSize() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_STREAM_STATS_FINAL_SIZE]; + return getStats(this, IDX_QUIC_STREAM_STATS_FINAL_SIZE); } get maxAcknowledgedOffset() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_STREAM_STATS_MAX_OFFSET_ACK]; + return getStats(this, IDX_QUIC_STREAM_STATS_MAX_OFFSET_ACK); } get maxReceivedOffset() { - const stats = this.#stats || this[kHandle].stats; - return stats[IDX_QUIC_STREAM_STATS_MAX_OFFSET_RECV]; + return getStats(this, IDX_QUIC_STREAM_STATS_MAX_OFFSET_RECV); } } diff --git a/lib/net.js b/lib/net.js index 9c5393bd28504b..54958d5912e8fa 100644 --- a/lib/net.js +++ b/lib/net.js @@ -541,7 +541,7 @@ ObjectDefineProperty(Socket.prototype, 'readyState', { ObjectDefineProperty(Socket.prototype, 'bufferSize', { get: function() { if (this._handle) { - return this[kLastWriteQueueSize] + this.writableLength; + return this.writableLength; } } }); diff --git a/lib/wasi.js b/lib/wasi.js index 97268f1b9d2fbc..a764a1745ab0a9 100644 --- a/lib/wasi.js +++ b/lib/wasi.js @@ -20,13 +20,36 @@ const { validateObject, } = require('internal/validators'); const { WASI: _WASI } = internalBinding('wasi'); -const kExitCode = Symbol('exitCode'); -const kSetMemory = Symbol('setMemory'); -const kStarted = Symbol('started'); +const kExitCode = Symbol('kExitCode'); +const kSetMemory = Symbol('kSetMemory'); +const kStarted = Symbol('kStarted'); +const kInstance = Symbol('kInstance'); emitExperimentalWarning('WASI'); +function setupInstance(self, instance) { + validateObject(instance, 'instance'); + validateObject(instance.exports, 'instance.exports'); + + // WASI::_SetMemory() in src/node_wasi.cc only expects that |memory| is + // an object. It will try to look up the .buffer property when needed + // and fail with UVWASI_EINVAL when the property is missing or is not + // an ArrayBuffer. Long story short, we don't need much validation here + // but we type-check anyway because it helps catch bugs in the user's + // code early. + validateObject(instance.exports.memory, 'instance.exports.memory'); + if (!isArrayBuffer(instance.exports.memory.buffer)) { + throw new ERR_INVALID_ARG_TYPE( + 'instance.exports.memory.buffer', + ['WebAssembly.Memory'], + instance.exports.memory.buffer); + } + + self[kInstance] = instance; + self[kSetMemory](instance.exports.memory); +} + class WASI { constructor(options = {}) { validateObject(options, 'options'); @@ -75,58 +98,63 @@ class WASI { this.wasiImport = wrap; this[kStarted] = false; this[kExitCode] = 0; + this[kInstance] = undefined; } + // Must not export _initialize, must export _start start(instance) { - validateObject(instance, 'instance'); - - const exports = instance.exports; + if (this[kStarted]) { + throw new ERR_WASI_ALREADY_STARTED(); + } + this[kStarted] = true; - validateObject(exports, 'instance.exports'); + setupInstance(this, instance); - const { _initialize, _start, memory } = exports; + const { _start, _initialize } = this[kInstance].exports; if (typeof _start !== 'function') { throw new ERR_INVALID_ARG_TYPE( 'instance.exports._start', 'function', _start); } - if (_initialize !== undefined) { throw new ERR_INVALID_ARG_TYPE( 'instance.exports._initialize', 'undefined', _initialize); } - // WASI::_SetMemory() in src/node_wasi.cc only expects that |memory| is - // an object. It will try to look up the .buffer property when needed - // and fail with UVWASI_EINVAL when the property is missing or is not - // an ArrayBuffer. Long story short, we don't need much validation here - // but we type-check anyway because it helps catch bugs in the user's - // code early. - validateObject(memory, 'instance.exports.memory'); - - if (!isArrayBuffer(memory.buffer)) { - throw new ERR_INVALID_ARG_TYPE( - 'instance.exports.memory.buffer', - ['WebAssembly.Memory'], - memory.buffer); + try { + _start(); + } catch (err) { + if (err !== kExitCode) { + throw err; + } } + return this[kExitCode]; + } + + // Must not export _start, may optionally export _initialize + initialize(instance) { if (this[kStarted]) { throw new ERR_WASI_ALREADY_STARTED(); } - this[kStarted] = true; - this[kSetMemory](memory); - try { - exports._start(); - } catch (err) { - if (err !== kExitCode) { - throw err; - } + setupInstance(this, instance); + + const { _start, _initialize } = this[kInstance].exports; + + if (typeof _initialize !== 'function' && _initialize !== undefined) { + throw new ERR_INVALID_ARG_TYPE( + 'instance.exports._initialize', 'function', _initialize); + } + if (_start !== undefined) { + throw new ERR_INVALID_ARG_TYPE( + 'instance.exports._start', 'undefined', _initialize); } - return this[kExitCode]; + if (_initialize !== undefined) { + _initialize(); + } } } diff --git a/lib/zlib.js b/lib/zlib.js index 8ee2c143e5ae6f..7cc8e2e6275041 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -138,7 +138,6 @@ function zlibBufferOnError(err) { function zlibBufferOnEnd() { let buf; - let err; if (this.nread === 0) { buf = Buffer.alloc(0); } else { @@ -146,9 +145,7 @@ function zlibBufferOnEnd() { buf = (bufs.length === 1 ? bufs[0] : Buffer.concat(bufs, this.nread)); } this.close(); - if (err) - this.cb(err); - else if (this._info) + if (this._info) this.cb(null, { buffer: buf, engine: this }); else this.cb(null, buf); diff --git a/src/env-inl.h b/src/env-inl.h index 5cbb2cd60bba99..690e7970436e7b 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -767,6 +767,10 @@ inline bool Environment::is_main_thread() const { return worker_context() == nullptr; } +inline bool Environment::should_not_register_esm_loader() const { + return flags_ & EnvironmentFlags::kNoRegisterESMLoader; +} + inline bool Environment::owns_process_state() const { return flags_ & EnvironmentFlags::kOwnsProcessState; } diff --git a/src/env.h b/src/env.h index 175e1a3f8d227a..f16c79b500171c 100644 --- a/src/env.h +++ b/src/env.h @@ -450,20 +450,17 @@ constexpr size_t kFsStatsBufferLength = #if defined(NODE_EXPERIMENTAL_QUIC) && NODE_EXPERIMENTAL_QUIC # define QUIC_ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) \ V(quic_on_socket_close_function, v8::Function) \ - V(quic_on_socket_error_function, v8::Function) \ V(quic_on_socket_server_busy_function, v8::Function) \ V(quic_on_session_cert_function, v8::Function) \ V(quic_on_session_client_hello_function, v8::Function) \ V(quic_on_session_close_function, v8::Function) \ V(quic_on_session_destroyed_function, v8::Function) \ - V(quic_on_session_error_function, v8::Function) \ V(quic_on_session_handshake_function, v8::Function) \ V(quic_on_session_keylog_function, v8::Function) \ V(quic_on_session_path_validation_function, v8::Function) \ V(quic_on_session_use_preferred_address_function, v8::Function) \ V(quic_on_session_qlog_function, v8::Function) \ V(quic_on_session_ready_function, v8::Function) \ - V(quic_on_session_silent_close_function, v8::Function) \ V(quic_on_session_status_function, v8::Function) \ V(quic_on_session_ticket_function, v8::Function) \ V(quic_on_session_version_negotiation_function, v8::Function) \ @@ -1054,6 +1051,7 @@ class Environment : public MemoryRetainer { inline void set_has_serialized_options(bool has_serialized_options); inline bool is_main_thread() const; + inline bool should_not_register_esm_loader() const; inline bool owns_process_state() const; inline bool owns_inspector() const; inline uint64_t thread_id() const; diff --git a/src/node.h b/src/node.h index b3b7c0c1693178..6a6a40113b271b 100644 --- a/src/node.h +++ b/src/node.h @@ -408,7 +408,11 @@ enum Flags : uint64_t { // Set if this Environment instance is associated with the global inspector // handling code (i.e. listening on SIGUSR1). // This is set when using kDefaultFlags. - kOwnsInspector = 1 << 2 + kOwnsInspector = 1 << 2, + // Set if Node.js should not run its own esm loader. This is needed by some + // embedders, because it's possible for the Node.js esm loader to conflict + // with another one in an embedder environment, e.g. Blink's in Chromium. + kNoRegisterESMLoader = 1 << 3 }; } // namespace EnvironmentFlags diff --git a/src/node_api.h b/src/node_api.h index 7f48d8c8465520..2f1b45572d8130 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -73,11 +73,17 @@ typedef struct { } \ EXTERN_C_END +#define NAPI_MODULE_INITIALIZER_X(base, version) \ + NAPI_MODULE_INITIALIZER_X_HELPER(base, version) +#define NAPI_MODULE_INITIALIZER_X_HELPER(base, version) base##version + #ifdef __wasm32__ +#define NAPI_WASM_INITIALIZER \ + NAPI_MODULE_INITIALIZER_X(napi_register_wasm_v, NAPI_MODULE_VERSION) #define NAPI_MODULE(modname, regfunc) \ EXTERN_C_START \ - NAPI_MODULE_EXPORT napi_value _napi_register(napi_env env, \ - napi_value exports) { \ + NAPI_MODULE_EXPORT napi_value NAPI_WASM_INITIALIZER(napi_env env, \ + napi_value exports) { \ return regfunc(env, exports); \ } \ EXTERN_C_END @@ -88,10 +94,6 @@ typedef struct { #define NAPI_MODULE_INITIALIZER_BASE napi_register_module_v -#define NAPI_MODULE_INITIALIZER_X(base, version) \ - NAPI_MODULE_INITIALIZER_X_HELPER(base, version) -#define NAPI_MODULE_INITIALIZER_X_HELPER(base, version) base##version - #define NAPI_MODULE_INITIALIZER \ NAPI_MODULE_INITIALIZER_X(NAPI_MODULE_INITIALIZER_BASE, \ NAPI_MODULE_VERSION) diff --git a/src/node_options.cc b/src/node_options.cc index d7434b4b124872..913366b3500442 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -979,6 +979,12 @@ void Initialize(Local target, context, FIXED_ONE_BYTE_STRING(isolate, "envSettings"), env_settings) .Check(); + target + ->Set(context, + FIXED_ONE_BYTE_STRING(env->isolate(), "shouldNotRegisterESMLoader"), + Boolean::New(isolate, env->should_not_register_esm_loader())) + .Check(); + Local types = Object::New(isolate); NODE_DEFINE_CONSTANT(types, kNoOp); NODE_DEFINE_CONSTANT(types, kV8Option); diff --git a/src/quic/node_quic.cc b/src/quic/node_quic.cc index 202767a670a30b..ed147eb92b649f 100644 --- a/src/quic/node_quic.cc +++ b/src/quic/node_quic.cc @@ -55,19 +55,16 @@ void QuicSetCallbacks(const FunctionCallbackInfo& args) { } while (0) SETFUNCTION("onSocketClose", socket_close); - SETFUNCTION("onSocketError", socket_error); SETFUNCTION("onSessionReady", session_ready); SETFUNCTION("onSessionCert", session_cert); SETFUNCTION("onSessionClientHello", session_client_hello); SETFUNCTION("onSessionClose", session_close); SETFUNCTION("onSessionDestroyed", session_destroyed); - SETFUNCTION("onSessionError", session_error); SETFUNCTION("onSessionHandshake", session_handshake); SETFUNCTION("onSessionKeylog", session_keylog); SETFUNCTION("onSessionUsePreferredAddress", session_use_preferred_address); SETFUNCTION("onSessionPathValidation", session_path_validation); SETFUNCTION("onSessionQlog", session_qlog); - SETFUNCTION("onSessionSilentClose", session_silent_close); SETFUNCTION("onSessionStatus", session_status); SETFUNCTION("onSessionTicket", session_ticket); SETFUNCTION("onSessionVersionNegotiation", session_version_negotiation); @@ -192,7 +189,6 @@ void Initialize(Local target, V(NGTCP2_APP_NOERROR) \ V(NGTCP2_PATH_VALIDATION_RESULT_FAILURE) \ V(NGTCP2_PATH_VALIDATION_RESULT_SUCCESS) \ - V(NGTCP2_DEFAULT_MAX_PKTLEN) \ V(NGTCP2_CC_ALGO_CUBIC) \ V(NGTCP2_CC_ALGO_RENO) \ V(QUIC_ERROR_APPLICATION) \ @@ -235,11 +231,29 @@ void Initialize(Local target, QUIC_CONSTANTS(V) #undef V + NODE_DEFINE_CONSTANT(constants, NGTCP2_DEFAULT_MAX_PKTLEN); NODE_DEFINE_CONSTANT(constants, NGTCP2_PROTO_VER); NODE_DEFINE_CONSTANT(constants, NGTCP2_DEFAULT_MAX_ACK_DELAY); NODE_DEFINE_CONSTANT(constants, NGTCP2_MAX_CIDLEN); NODE_DEFINE_CONSTANT(constants, NGTCP2_MIN_CIDLEN); + NODE_DEFINE_CONSTANT(constants, NGTCP2_NO_ERROR); + NODE_DEFINE_CONSTANT(constants, NGTCP2_INTERNAL_ERROR); + NODE_DEFINE_CONSTANT(constants, NGTCP2_CONNECTION_REFUSED); + NODE_DEFINE_CONSTANT(constants, NGTCP2_FLOW_CONTROL_ERROR); + NODE_DEFINE_CONSTANT(constants, NGTCP2_STREAM_LIMIT_ERROR); + NODE_DEFINE_CONSTANT(constants, NGTCP2_STREAM_STATE_ERROR); + NODE_DEFINE_CONSTANT(constants, NGTCP2_FINAL_SIZE_ERROR); + NODE_DEFINE_CONSTANT(constants, NGTCP2_FRAME_ENCODING_ERROR); + NODE_DEFINE_CONSTANT(constants, NGTCP2_TRANSPORT_PARAMETER_ERROR); + NODE_DEFINE_CONSTANT(constants, NGTCP2_CONNECTION_ID_LIMIT_ERROR); + NODE_DEFINE_CONSTANT(constants, NGTCP2_PROTOCOL_VIOLATION); + NODE_DEFINE_CONSTANT(constants, NGTCP2_INVALID_TOKEN); + NODE_DEFINE_CONSTANT(constants, NGTCP2_APPLICATION_ERROR); + NODE_DEFINE_CONSTANT(constants, NGTCP2_CRYPTO_BUFFER_EXCEEDED); + NODE_DEFINE_CONSTANT(constants, NGTCP2_KEY_UPDATE_ERROR); + NODE_DEFINE_CONSTANT(constants, NGTCP2_CRYPTO_ERROR); + NODE_DEFINE_CONSTANT(constants, AF_INET); NODE_DEFINE_CONSTANT(constants, AF_INET6); NODE_DEFINE_STRING_CONSTANT(constants, diff --git a/src/quic/node_quic_buffer-inl.h b/src/quic/node_quic_buffer-inl.h index e03378a331154b..25f130ac9fea60 100644 --- a/src/quic/node_quic_buffer-inl.h +++ b/src/quic/node_quic_buffer-inl.h @@ -64,7 +64,6 @@ QuicBuffer::QuicBuffer(QuicBuffer&& src) noexcept src.ended_ = false; } - QuicBuffer& QuicBuffer::operator=(QuicBuffer&& src) noexcept { if (this == &src) return *this; this->~QuicBuffer(); diff --git a/src/quic/node_quic_buffer.cc b/src/quic/node_quic_buffer.cc index 2cb9211994138d..acbe4e34c3712c 100644 --- a/src/quic/node_quic_buffer.cc +++ b/src/quic/node_quic_buffer.cc @@ -17,10 +17,11 @@ void QuicBufferChunk::MemoryInfo(MemoryTracker* tracker) const { size_t QuicBuffer::Push(uv_buf_t* bufs, size_t nbufs, DoneCB done) { size_t len = 0; - if (nbufs == 0 || bufs == nullptr || is_empty(bufs[0])) { + if (UNLIKELY(nbufs == 0)) { done(0); return 0; } + DCHECK_NOT_NULL(bufs); size_t n = 0; while (nbufs > 1) { if (!is_empty(bufs[n])) { @@ -30,8 +31,14 @@ size_t QuicBuffer::Push(uv_buf_t* bufs, size_t nbufs, DoneCB done) { n++; nbufs--; } - len += bufs[n].len; - Push(bufs[n], done); + if (!is_empty(bufs[n])) { + Push(bufs[n], done); + len += bufs[n].len; + } + // Special case if all the bufs were empty. + if (UNLIKELY(len == 0)) + done(0); + return len; } diff --git a/src/quic/node_quic_buffer.h b/src/quic/node_quic_buffer.h index 17f59a7e759161..3d81a28176dd51 100644 --- a/src/quic/node_quic_buffer.h +++ b/src/quic/node_quic_buffer.h @@ -24,7 +24,7 @@ using DoneCB = std::function; // When data is sent over QUIC, we are required to retain it in memory // until we receive an acknowledgement that it has been successfully -// acknowledged. The QuicBuffer object is what we use to handle that +// received. The QuicBuffer object is what we use to handle that // and track until it is acknowledged. To understand the QuicBuffer // object itself, it is important to understand how ngtcp2 and nghttp3 // handle data that is given to it to serialize into QUIC packets. @@ -52,7 +52,7 @@ using DoneCB = std::function; // QuicBuffer is further complicated by design quirks and limitations // of the StreamBase API and how it interacts with the JavaScript side. // -// QuicBuffer is essentially a linked list of QuicBufferChunk instances. +// QuicBuffer is a linked list of QuicBufferChunk instances. // A single QuicBufferChunk wraps a single non-zero-length uv_buf_t. // When the QuicBufferChunk is created, we capture the total length // of the buffer and the total number of bytes remaining to be sent. @@ -79,7 +79,7 @@ using DoneCB = std::function; // along with a callback to be called when the data has // been consumed. // -// Any given chunk as a remaining-to-be-acknowledged length (length()) and a +// Any given chunk has a remaining-to-be-acknowledged length (length()) and a // remaining-to-be-read-length (remaining()). The former tracks the number // of bytes that have yet to be acknowledged by the QUIC peer. Once the // remaining-to-be-acknowledged length reaches zero, the done callback @@ -88,7 +88,7 @@ using DoneCB = std::function; // serialized into QUIC packets and sent. // The remaining-to-be-acknowledged length is adjusted using consume(), // while the remaining-to-be-ead length is adjusted using seek(). -class QuicBufferChunk : public MemoryRetainer { +class QuicBufferChunk final : public MemoryRetainer { public: // Default non-op done handler. static void default_done(int status) {} @@ -149,8 +149,8 @@ class QuicBufferChunk : public MemoryRetainer { friend class QuicBuffer; }; -class QuicBuffer : public bob::SourceImpl, - public MemoryRetainer { +class QuicBuffer final : public bob::SourceImpl, + public MemoryRetainer { public: QuicBuffer() = default; diff --git a/src/quic/node_quic_session.cc b/src/quic/node_quic_session.cc index eac88ff67f2e99..d058035a0b7896 100644 --- a/src/quic/node_quic_session.cc +++ b/src/quic/node_quic_session.cc @@ -293,9 +293,9 @@ void QuicSessionListener::OnSessionDestroyed() { previous_listener_->OnSessionDestroyed(); } -void QuicSessionListener::OnSessionClose(QuicError error) { +void QuicSessionListener::OnSessionClose(QuicError error, int flags) { if (previous_listener_ != nullptr) - previous_listener_->OnSessionClose(error); + previous_listener_->OnSessionClose(error, flags); } void QuicSessionListener::OnStreamReady(BaseObjectPtr stream) { @@ -328,13 +328,6 @@ void QuicSessionListener::OnStreamBlocked(int64_t stream_id) { } } -void QuicSessionListener::OnSessionSilentClose( - bool stateless_reset, - QuicError error) { - if (previous_listener_ != nullptr) - previous_listener_->OnSessionSilentClose(stateless_reset, error); -} - void QuicSessionListener::OnUsePreferredAddress( int family, const PreferredAddress& preferred_address) { @@ -525,14 +518,20 @@ void JSQuicSessionListener::OnSessionDestroyed() { env->quic_on_session_destroyed_function(), 0, nullptr); } -void JSQuicSessionListener::OnSessionClose(QuicError error) { +void JSQuicSessionListener::OnSessionClose(QuicError error, int flags) { Environment* env = session()->env(); HandleScope scope(env->isolate()); Context::Scope context_scope(env->context()); Local argv[] = { Number::New(env->isolate(), static_cast(error.code)), - Integer::New(env->isolate(), error.family) + Integer::New(env->isolate(), error.family), + flags & SESSION_CLOSE_FLAG_SILENT + ? v8::True(env->isolate()) + : v8::False(env->isolate()), + flags & SESSION_CLOSE_FLAG_STATELESS_RESET + ? v8::True(env->isolate()) + : v8::False(env->isolate()) }; // Grab a shared pointer to this to prevent the QuicSession @@ -664,26 +663,6 @@ void JSQuicSessionListener::OnSessionTicket(int size, SSL_SESSION* sess) { arraysize(argv), argv); } -void JSQuicSessionListener::OnSessionSilentClose( - bool stateless_reset, - QuicError error) { - Environment* env = session()->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - - Local argv[] = { - stateless_reset ? v8::True(env->isolate()) : v8::False(env->isolate()), - Number::New(env->isolate(), static_cast(error.code)), - Integer::New(env->isolate(), error.family) - }; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr ptr(session()); - session()->MakeCallback( - env->quic_on_session_silent_close_function(), arraysize(argv), argv); -} - void JSQuicSessionListener::OnUsePreferredAddress( int family, const PreferredAddress& preferred_address) { @@ -2323,7 +2302,7 @@ bool QuicSession::set_socket(QuicSocket* socket, bool nat_rebinding) { socket->ReceiveStart(); // Step 4: Update ngtcp2 - auto& local_address = socket->local_address(); + auto local_address = socket->local_address(); if (nat_rebinding) { ngtcp2_addr addr; ngtcp2_addr_init( @@ -2377,7 +2356,10 @@ void QuicSession::SilentClose() { err.code, is_stateless_reset() ? "yes" : "no"); - listener()->OnSessionSilentClose(is_stateless_reset(), err); + int flags = QuicSessionListener::SESSION_CLOSE_FLAG_SILENT; + if (is_stateless_reset()) + flags |= QuicSessionListener::SESSION_CLOSE_FLAG_STATELESS_RESET; + listener()->OnSessionClose(err, flags); } // Begin connection close by serializing the CONNECTION_CLOSE packet. // There are two variants: one to serialize an application close, the @@ -2896,8 +2878,8 @@ int QuicSession::OnReceiveCryptoData( if (UNLIKELY(session->is_destroyed())) return NGTCP2_ERR_CALLBACK_FAILURE; QuicSession::Ngtcp2CallbackScope callback_scope(session); - return static_cast( - session->crypto_context()->Receive(crypto_level, offset, data, datalen)); + return session->crypto_context()->Receive( + crypto_level, offset, data, datalen); } // Called by ngtcp2 for both client and server connections diff --git a/src/quic/node_quic_session.h b/src/quic/node_quic_session.h index f27aaabdd98ef1..78fb7ee0a30540 100644 --- a/src/quic/node_quic_session.h +++ b/src/quic/node_quic_session.h @@ -260,6 +260,12 @@ struct QuicSessionStatsTraits { class QuicSessionListener { public: + enum SessionCloseFlags { + SESSION_CLOSE_FLAG_NONE, + SESSION_CLOSE_FLAG_SILENT, + SESSION_CLOSE_FLAG_STATELESS_RESET + }; + virtual ~QuicSessionListener(); virtual void OnKeylog(const char* str, size_t size); @@ -280,7 +286,9 @@ class QuicSessionListener { int64_t stream_id, uint64_t app_error_code); virtual void OnSessionDestroyed(); - virtual void OnSessionClose(QuicError error); + virtual void OnSessionClose( + QuicError error, + int flags = SESSION_CLOSE_FLAG_NONE); virtual void OnStreamReady(BaseObjectPtr stream); virtual void OnHandshakeCompleted(); virtual void OnPathValidation( @@ -291,9 +299,6 @@ class QuicSessionListener { int family, const PreferredAddress& preferred_address); virtual void OnSessionTicket(int size, SSL_SESSION* session); - virtual void OnSessionSilentClose( - bool stateless_reset, - QuicError error); virtual void OnStreamBlocked(int64_t stream_id); virtual void OnVersionNegotiation( uint32_t supported_version, @@ -329,7 +334,9 @@ class JSQuicSessionListener : public QuicSessionListener { int64_t stream_id, uint64_t app_error_code) override; void OnSessionDestroyed() override; - void OnSessionClose(QuicError error) override; + void OnSessionClose( + QuicError error, + int flags = SESSION_CLOSE_FLAG_NONE) override; void OnStreamReady(BaseObjectPtr stream) override; void OnHandshakeCompleted() override; void OnPathValidation( @@ -337,7 +344,6 @@ class JSQuicSessionListener : public QuicSessionListener { const sockaddr* local, const sockaddr* remote) override; void OnSessionTicket(int size, SSL_SESSION* session) override; - void OnSessionSilentClose(bool stateless_reset, QuicError error) override; void OnUsePreferredAddress( int family, const PreferredAddress& preferred_address) override; diff --git a/src/quic/node_quic_socket-inl.h b/src/quic/node_quic_socket-inl.h index 8156fd04ad7bf5..38f3ad927180f0 100644 --- a/src/quic/node_quic_socket-inl.h +++ b/src/quic/node_quic_socket-inl.h @@ -79,8 +79,8 @@ void QuicSocket::AssociateStatelessResetToken( token_map_[token] = session; } -const SocketAddress& QuicSocket::local_address() { - CHECK(preferred_endpoint_); +SocketAddress QuicSocket::local_address() const { + DCHECK(preferred_endpoint_); return preferred_endpoint_->local_address(); } @@ -221,10 +221,6 @@ void QuicSocket::AddEndpoint( endpoint_->ReceiveStart(); } -void QuicSocket::SessionReady(BaseObjectPtr session) { - listener_->OnSessionReady(session); -} - } // namespace quic } // namespace node diff --git a/src/quic/node_quic_socket.cc b/src/quic/node_quic_socket.cc index ce7c5820b2d7b1..e3112887d8e4fb 100644 --- a/src/quic/node_quic_socket.cc +++ b/src/quic/node_quic_socket.cc @@ -83,10 +83,10 @@ bool IsShortHeader( } } // namespace -QuicPacket::QuicPacket(const char* diagnostic_label, size_t len) : - data_{0}, - len_(len), - diagnostic_label_(diagnostic_label) { +QuicPacket::QuicPacket(const char* diagnostic_label, size_t len) + : data_{0}, + len_(len), + diagnostic_label_(diagnostic_label) { CHECK_LE(len, MAX_PKTLEN); } @@ -100,8 +100,6 @@ const char* QuicPacket::diagnostic_label() const { diagnostic_label_ : "unspecified"; } -void QuicPacket::MemoryInfo(MemoryTracker* tracker) const {} - QuicSocketListener::~QuicSocketListener() { if (socket_) socket_->RemoveListener(this); @@ -137,7 +135,7 @@ void JSQuicSocketListener::OnError(ssize_t code) { HandleScope scope(env->isolate()); Context::Scope context_scope(env->context()); Local arg = Number::New(env->isolate(), static_cast(code)); - socket()->MakeCallback(env->quic_on_socket_error_function(), 1, &arg); + socket()->MakeCallback(env->quic_on_socket_close_function(), 1, &arg); } void JSQuicSocketListener::OnSessionReady(BaseObjectPtr session) { @@ -174,10 +172,10 @@ QuicEndpoint::QuicEndpoint( QuicState* quic_state, Local wrap, QuicSocket* listener, - Local udp_wrap) : - BaseObject(quic_state->env(), wrap), - listener_(listener), - quic_state_(quic_state) { + Local udp_wrap) + : BaseObject(quic_state->env(), wrap), + listener_(listener), + quic_state_(quic_state) { MakeWeak(); udp_ = static_cast( udp_wrap->GetAlignedPointerFromInternalField( @@ -187,7 +185,9 @@ QuicEndpoint::QuicEndpoint( strong_ptr_.reset(udp_->GetAsyncWrap()); } -void QuicEndpoint::MemoryInfo(MemoryTracker* tracker) const {} +QuicEndpoint::~QuicEndpoint() { + udp_->set_listener(nullptr); +} uv_buf_t QuicEndpoint::OnAlloc(size_t suggested_size) { return AllocatedBuffer::AllocateManaged(env(), suggested_size).release(); @@ -229,6 +229,14 @@ void QuicEndpoint::OnAfterBind() { listener_->OnBind(this); } +template +void QuicSocketStatsTraits::ToString(const QuicSocket& ptr, Fn&& add_field) { +#define V(_n, name, label) \ + add_field(label, ptr.GetStat(&QuicSocketStats::name)); + SOCKET_STATS(V) +#undef V +} + QuicSocket::QuicSocket( QuicState* quic_state, Local wrap, @@ -240,17 +248,17 @@ QuicSocket::QuicSocket( QlogMode qlog, const uint8_t* session_reset_secret, bool disable_stateless_reset) - : AsyncWrap(quic_state->env(), wrap, AsyncWrap::PROVIDER_QUICSOCKET), - StatsBase(quic_state->env(), wrap), - alloc_info_(MakeAllocator()), - options_(options), - max_connections_(max_connections), - max_connections_per_host_(max_connections_per_host), - max_stateless_resets_per_host_(max_stateless_resets_per_host), - retry_token_expiration_(retry_token_expiration), - qlog_(qlog), - server_alpn_(NGHTTP3_ALPN_H3), - quic_state_(quic_state) { + : AsyncWrap(quic_state->env(), wrap, AsyncWrap::PROVIDER_QUICSOCKET), + StatsBase(quic_state->env(), wrap), + alloc_info_(MakeAllocator()), + options_(options), + max_connections_(max_connections), + max_connections_per_host_(max_connections_per_host), + max_stateless_resets_per_host_(max_stateless_resets_per_host), + retry_token_expiration_(retry_token_expiration), + qlog_(qlog), + server_alpn_(NGHTTP3_ALPN_H3), + quic_state_(quic_state) { MakeWeak(); PushListener(&default_listener_); @@ -279,15 +287,13 @@ QuicSocket::~QuicSocket() { if (listener == listener_) RemoveListener(listener_); - DebugStats(); -} + // In a clean shutdown, all QuicSessions associated with the QuicSocket + // would have been destroyed explicitly. However, if the QuicSocket is + // garbage collected / freed before Destroy having been called, there + // may be sessions remaining. This is not really a good thing. + Debug(this, "Destroying with %d sessions remaining", sessions_.size()); -template -void QuicSocketStatsTraits::ToString(const QuicSocket& ptr, Fn&& add_field) { -#define V(_n, name, label) \ - add_field(label, ptr.GetStat(&QuicSocketStats::name)); - SOCKET_STATS(V) -#undef V + DebugStats(); } void QuicSocket::MemoryInfo(MemoryTracker* tracker) const { @@ -310,7 +316,6 @@ void QuicSocket::Listen( const std::string& alpn, uint32_t options) { CHECK(sc); - CHECK(!server_secure_context_); CHECK(!is_flag_set(QUICSOCKET_FLAGS_SERVER_LISTENING)); Debug(this, "Starting to listen"); server_session_config_.Set(quic_state(), preferred_address); @@ -323,6 +328,7 @@ void QuicSocket::Listen( } void QuicSocket::OnError(QuicEndpoint* endpoint, ssize_t error) { + // TODO(@jasnell): What should we do with the endpoint? Debug(this, "Reading data from UDP socket failed. Error %" PRId64, error); listener_->OnError(error); } @@ -341,7 +347,7 @@ void QuicSocket::OnEndpointDone(QuicEndpoint* endpoint) { } void QuicSocket::OnBind(QuicEndpoint* endpoint) { - const SocketAddress& local_address = endpoint->local_address(); + SocketAddress local_address = endpoint->local_address(); bound_endpoints_[local_address] = BaseObjectWeakPtr(endpoint); Debug(this, "Endpoint %s bound", local_address); @@ -545,6 +551,13 @@ void QuicSocket::OnReceive( IncrementStat(&QuicSocketStats::packets_ignored); return; } + + // The QuicSession was destroyed while it was being set up. There's + // no further processing we can do here. + if (session->is_destroyed()) { + IncrementStat(&QuicSocketStats::packets_ignored); + return; + } } CHECK(session); @@ -683,6 +696,8 @@ bool QuicSocket::SendRetry( } // Shutdown a connection prematurely, before a QuicSession is created. +// This should only be called t the start of a session before the crypto +// keys have been established. void QuicSocket::ImmediateConnectionClose( const QuicCID& scid, const QuicCID& dcid, @@ -819,6 +834,18 @@ BaseObjectPtr QuicSocket::AcceptInitialPacket( listener_->OnSessionReady(session); + // It's possible that the session was destroyed while processing + // the ready callback. If it was, then we need to send an early + // CONNECTION_CLOSE. + if (session->is_destroyed()) { + ImmediateConnectionClose( + QuicCID(hd.scid), + QuicCID(hd.dcid), + local_addr, + remote_addr, + NGTCP2_CONNECTION_REFUSED); + } + return session; } @@ -826,9 +853,9 @@ QuicSocket::SendWrap::SendWrap( QuicState* quic_state, Local req_wrap_obj, size_t total_length) - : ReqWrap(quic_state->env(), req_wrap_obj, PROVIDER_QUICSOCKET), - total_length_(total_length), - quic_state_(quic_state) { + : ReqWrap(quic_state->env(), req_wrap_obj, PROVIDER_QUICSOCKET), + total_length_(total_length), + quic_state_(quic_state) { } std::string QuicSocket::SendWrap::MemoryInfoName() const { @@ -1093,7 +1120,7 @@ void QuicSocketStopListening(const FunctionCallbackInfo& args) { socket->StopListening(); } -void QuicSocketset_server_busy(const FunctionCallbackInfo& args) { +void QuicSocketSetServerBusy(const FunctionCallbackInfo& args) { QuicSocket* socket; ASSIGN_OR_RETURN_UNWRAP(&socket, args.Holder()); CHECK_EQ(args.Length(), 1); @@ -1164,7 +1191,7 @@ void QuicSocket::Initialize( QuicSocketSetDiagnosticPacketLoss); env->SetProtoMethod(socket, "setServerBusy", - QuicSocketset_server_busy); + QuicSocketSetServerBusy); env->SetProtoMethod(socket, "stopListening", QuicSocketStopListening); diff --git a/src/quic/node_quic_socket.h b/src/quic/node_quic_socket.h index 08ccd74dcb52f0..2fcf0fb7f19d31 100644 --- a/src/quic/node_quic_socket.h +++ b/src/quic/node_quic_socket.h @@ -33,6 +33,9 @@ using v8::Value; namespace quic { +class QuicSocket; +class QuicEndpoint; + enum QuicSocketOptions : uint32_t { // When enabled the QuicSocket will validate the address // using a RETRY packet to the peer. @@ -80,9 +83,6 @@ struct QuicSocketStatsTraits { static void ToString(const Base& ptr, Fn&& add_field); }; -class QuicSocket; -class QuicEndpoint; - // This is the generic interface for objects that control QuicSocket // instances. The default `JSQuicSocketListener` emits events to // JavaScript @@ -96,7 +96,7 @@ class QuicSocketListener { virtual void OnEndpointDone(QuicEndpoint* endpoint); virtual void OnDestroy(); - QuicSocket* socket() { return socket_.get(); } + QuicSocket* socket() const { return socket_.get(); } private: BaseObjectWeakPtr socket_; @@ -104,7 +104,7 @@ class QuicSocketListener { friend class QuicSocket; }; -class JSQuicSocketListener : public QuicSocketListener { +class JSQuicSocketListener final : public QuicSocketListener { public: void OnError(ssize_t code) override; void OnSessionReady(BaseObjectPtr session) override; @@ -121,17 +121,21 @@ constexpr size_t MAX_PKTLEN = std::max(NGTCP2_MAX_PKTLEN_IPV4, NGTCP2_MAX_PKTLEN_IPV6); // A serialized QuicPacket to be sent by a QuicSocket instance. +// QuicPackets are intended to be transient. They are created, +// filled with the contents of a serialized packet, and passed +// off immediately to the QuicSocket to be sent. As soon as +// the packet is sent, it is freed. class QuicPacket : public MemoryRetainer { public: // Creates a new QuicPacket. By default the packet will be // stack allocated with a max size of NGTCP2_MAX_PKTLEN_IPV4. // If a larger packet size is specified, it will be heap - // allocated. Generally speaking, a QUIC packet should never - // be larger than the current MTU to avoid IP fragmentation. + // allocated. A QUIC packet should never be larger than the + // current MTU to avoid IP fragmentation. // - // The content of a QuicPacket is provided by ngtcp2. The - // typical use pattern is to create a QuicPacket instance - // and then pass a pointer to it's internal buffer and max + // The content of a QuicPacket is provided by ngtcp2 and is + // opaque for us. The typical use pattern is to create a QuicPacket + // instance and then pass a pointer to it's internal buffer and max // size in to an ngtcp2 function that serializes the data. // ngtcp2 will fill the buffer as much as possible then return // the number of bytes serialized. User code is then responsible @@ -159,17 +163,22 @@ class QuicPacket : public MemoryRetainer { QuicPacket(const char* diagnostic_label, size_t len); QuicPacket(const QuicPacket& other); + uint8_t* data() { return data_; } + size_t length() const { return len_; } + uv_buf_t buf() const { return uv_buf_init( const_cast(reinterpret_cast(&data_)), length()); } + inline void set_length(size_t len); + const char* diagnostic_label() const; - void MemoryInfo(MemoryTracker* tracker) const override; + SET_NO_MEMORY_INFO(); SET_MEMORY_INFO_NAME(QuicPacket); SET_SELF_SIZE(QuicPacket); @@ -198,8 +207,8 @@ class QuicEndpointListener { // A QuicEndpoint wraps a UDPBaseWrap. A single QuicSocket may // have multiple QuicEndpoints, the lifecycles of which are // attached to the QuicSocket. -class QuicEndpoint : public BaseObject, - public UDPListener { +class QuicEndpoint final : public BaseObject, + public UDPListener { public: static void Initialize( Environment* env, @@ -212,9 +221,10 @@ class QuicEndpoint : public BaseObject, QuicSocket* listener, Local udp_wrap); - const SocketAddress& local_address() const { - local_address_ = udp_->GetSockName(); - return local_address_; + ~QuicEndpoint() override; + + SocketAddress local_address() const { + return udp_->GetSockName(); } // Implementation for UDPListener @@ -242,17 +252,16 @@ class QuicEndpoint : public BaseObject, void IncrementPendingCallbacks() { pending_callbacks_++; } void DecrementPendingCallbacks() { pending_callbacks_--; } - bool has_pending_callbacks() { return pending_callbacks_ > 0; } + bool has_pending_callbacks() const { return pending_callbacks_ > 0; } inline void WaitForPendingCallbacks(); QuicState* quic_state() const { return quic_state_.get(); } - void MemoryInfo(MemoryTracker* tracker) const override; + SET_NO_MEMORY_INFO(); SET_MEMORY_INFO_NAME(QuicEndpoint) SET_SELF_SIZE(QuicEndpoint) private: - mutable SocketAddress local_address_; BaseObjectWeakPtr listener_; UDPWrapBase* udp_; BaseObjectPtr strong_ptr_; @@ -298,7 +307,7 @@ class QuicSocket : public AsyncWrap, // Returns the default/preferred local address. Additional // QuicEndpoint instances may be associated with the // QuicSocket bound to other local addresses. - inline const SocketAddress& local_address(); + inline SocketAddress local_address() const; void MaybeClose(); @@ -342,8 +351,6 @@ class QuicSocket : public AsyncWrap, std::unique_ptr packet, BaseObjectPtr session = BaseObjectPtr()); - inline void SessionReady(BaseObjectPtr session); - inline void set_server_busy(bool on); inline void set_diagnostic_packet_loss(double rx = 0.0, double tx = 0.0); diff --git a/src/quic/node_quic_stream-inl.h b/src/quic/node_quic_stream-inl.h index 3da0f5fb3b57cf..3dd1fc216d9783 100644 --- a/src/quic/node_quic_stream-inl.h +++ b/src/quic/node_quic_stream-inl.h @@ -35,7 +35,12 @@ void QuicStream::set_flag(int32_t flag, bool on) { } void QuicStream::set_final_size(uint64_t final_size) { - CHECK_EQ(GetStat(&QuicStreamStats::final_size), 0); + // Only set the final size once. + if (is_flag_set(QUICSTREAM_FLAG_FIN)) { + CHECK_LE(final_size, GetStat(&QuicStreamStats::final_size)); + return; + } + set_flag(QUICSTREAM_FLAG_FIN); SetStat(&QuicStreamStats::final_size, final_size); } diff --git a/src/quic/node_quic_stream.cc b/src/quic/node_quic_stream.cc index 7a1054db407f59..ce8cc78a1ec8c5 100644 --- a/src/quic/node_quic_stream.cc +++ b/src/quic/node_quic_stream.cc @@ -355,7 +355,6 @@ void QuicStream::ReceiveData( // When fin != 0, we've received that last chunk of data for this // stream, indicating that the stream will no longer be readable. if (flags & NGTCP2_STREAM_DATA_FLAG_FIN) { - set_flag(QUICSTREAM_FLAG_FIN); set_final_size(offset + datalen); EmitRead(UV_EOF); } diff --git a/src/quic/node_quic_stream.h b/src/quic/node_quic_stream.h index 97174dcb7b925d..d8297f300ba85c 100644 --- a/src/quic/node_quic_stream.h +++ b/src/quic/node_quic_stream.h @@ -256,7 +256,11 @@ class QuicStream : public AsyncWrap, // Specifies the kind of headers currently being processed. inline void set_headers_kind(QuicStreamHeadersKind kind); - // Set the final size for the QuicStream + // Set the final size for the QuicStream. This only works + // the first time it is called. Subsequent calls will be + // ignored unless the subsequent size is greater than the + // prior set size, in which case we have a bug and we'll + // assert. inline void set_final_size(uint64_t final_size); // The final size is the maximum amount of data that has been diff --git a/test/cctest/test_environment.cc b/test/cctest/test_environment.cc index d0b241528ba31e..0314c7e5b8ad04 100644 --- a/test/cctest/test_environment.cc +++ b/test/cctest/test_environment.cc @@ -32,6 +32,56 @@ class EnvironmentTest : public EnvironmentTestFixture { } }; +TEST_F(EnvironmentTest, EnvironmentWithESMLoader) { + const v8::HandleScope handle_scope(isolate_); + Argv argv; + Env env {handle_scope, argv}; + + node::Environment* envi = *env; + envi->options()->experimental_vm_modules = true; + + SetProcessExitHandler(*env, [&](node::Environment* env_, int exit_code) { + EXPECT_EQ(*env, env_); + EXPECT_EQ(exit_code, 0); + node::Stop(*env); + }); + + node::LoadEnvironment( + *env, + "const { SourceTextModule } = require('vm');" + "try {" + "new SourceTextModule('export const a = 1;');" + "process.exit(0);" + "} catch {" + "process.exit(42);" + "}"); +} + +TEST_F(EnvironmentTest, EnvironmentWithNoESMLoader) { + const v8::HandleScope handle_scope(isolate_); + Argv argv; + Env env {handle_scope, argv, node::EnvironmentFlags::kNoRegisterESMLoader}; + + node::Environment* envi = *env; + envi->options()->experimental_vm_modules = true; + + SetProcessExitHandler(*env, [&](node::Environment* env_, int exit_code) { + EXPECT_EQ(*env, env_); + EXPECT_EQ(exit_code, 42); + node::Stop(*env); + }); + + node::LoadEnvironment( + *env, + "const { SourceTextModule } = require('vm');" + "try {" + "new SourceTextModule('export const a = 1;');" + "process.exit(0);" + "} catch {" + "process.exit(42);" + "}"); +} + TEST_F(EnvironmentTest, PreExecutionPreparation) { const v8::HandleScope handle_scope(isolate_); const Argv argv; diff --git a/test/parallel/test-quic-binding.js b/test/parallel/test-quic-binding.js index 6f28944664ffc9..2044ed43b48e53 100644 --- a/test/parallel/test-quic-binding.js +++ b/test/parallel/test-quic-binding.js @@ -20,6 +20,23 @@ assert(quic.constants); assert.strictEqual(quic.constants.NGTCP2_PROTO_VER.toString(16), 'ff00001d'); assert.strictEqual(quic.constants.NGHTTP3_ALPN_H3, '\u0005h3-29'); +assert.strictEqual(quic.constants.NGTCP2_NO_ERROR, 0); +assert.strictEqual(quic.constants.NGTCP2_INTERNAL_ERROR, 1); +assert.strictEqual(quic.constants.NGTCP2_CONNECTION_REFUSED, 2); +assert.strictEqual(quic.constants.NGTCP2_FLOW_CONTROL_ERROR, 3); +assert.strictEqual(quic.constants.NGTCP2_STREAM_LIMIT_ERROR, 4); +assert.strictEqual(quic.constants.NGTCP2_STREAM_STATE_ERROR, 5); +assert.strictEqual(quic.constants.NGTCP2_FINAL_SIZE_ERROR, 6); +assert.strictEqual(quic.constants.NGTCP2_FRAME_ENCODING_ERROR, 7); +assert.strictEqual(quic.constants.NGTCP2_TRANSPORT_PARAMETER_ERROR, 8); +assert.strictEqual(quic.constants.NGTCP2_CONNECTION_ID_LIMIT_ERROR, 9); +assert.strictEqual(quic.constants.NGTCP2_PROTOCOL_VIOLATION, 0xa); +assert.strictEqual(quic.constants.NGTCP2_INVALID_TOKEN, 0xb); +assert.strictEqual(quic.constants.NGTCP2_APPLICATION_ERROR, 0xc); +assert.strictEqual(quic.constants.NGTCP2_CRYPTO_BUFFER_EXCEEDED, 0xd); +assert.strictEqual(quic.constants.NGTCP2_KEY_UPDATE_ERROR, 0xe); +assert.strictEqual(quic.constants.NGTCP2_CRYPTO_ERROR, 0x100); + // The following just tests for the presence of things we absolutely need. // They don't test the functionality of those things. diff --git a/test/parallel/test-stream2-readable-wrap-error.js b/test/parallel/test-stream2-readable-wrap-error.js new file mode 100644 index 00000000000000..b56b9bc41c7527 --- /dev/null +++ b/test/parallel/test-stream2-readable-wrap-error.js @@ -0,0 +1,32 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const Readable = require('_stream_readable'); +const EE = require('events').EventEmitter; + +const oldStream = new EE(); +oldStream.pause = () => {}; +oldStream.resume = () => {}; + +{ + const r = new Readable({ autoDestroy: true }) + .wrap(oldStream) + .on('error', common.mustCall(() => { + assert.strictEqual(r._readableState.errorEmitted, true); + assert.strictEqual(r._readableState.errored, true); + assert.strictEqual(r.destroyed, true); + })); + oldStream.emit('error', new Error()); +} + +{ + const r = new Readable({ autoDestroy: false }) + .wrap(oldStream) + .on('error', common.mustCall(() => { + assert.strictEqual(r._readableState.errorEmitted, true); + assert.strictEqual(r._readableState.errored, true); + assert.strictEqual(r.destroyed, false); + })); + oldStream.emit('error', new Error()); +} diff --git a/test/parallel/test-tls-buffersize.js b/test/parallel/test-tls-buffersize.js index c94b95d7b32d31..eadd4cb1e40c9e 100644 --- a/test/parallel/test-tls-buffersize.js +++ b/test/parallel/test-tls-buffersize.js @@ -31,7 +31,7 @@ server.listen(0, common.mustCall(() => { for (let i = 1; i < iter; i++) { client.write('a'); - assert.strictEqual(client.bufferSize, i + 1); + assert.strictEqual(client.bufferSize, i); } client.on('finish', common.mustCall(() => { diff --git a/test/parallel/test-tls-streamwrap-buffersize.js b/test/parallel/test-tls-streamwrap-buffersize.js index 984cc51e505183..0e0a49883a5a08 100644 --- a/test/parallel/test-tls-streamwrap-buffersize.js +++ b/test/parallel/test-tls-streamwrap-buffersize.js @@ -56,7 +56,7 @@ const net = require('net'); for (let i = 1; i < iter; i++) { client.write('a'); - assert.strictEqual(client.bufferSize, i + 1); + assert.strictEqual(client.bufferSize, i); } client.on('end', common.mustCall()); diff --git a/test/pummel/test-child-process-spawn-loop.js b/test/pummel/test-child-process-spawn-loop.js index f6d8207df85b9b..4dc218161125bf 100644 --- a/test/pummel/test-child-process-spawn-loop.js +++ b/test/pummel/test-child-process-spawn-loop.js @@ -30,7 +30,7 @@ const N = 40; let finished = false; function doSpawn(i) { - const child = spawn('python', ['-c', `print ${SIZE} * "C"`]); + const child = spawn('python', ['-c', `print(${SIZE} * "C")`]); let count = 0; child.stdout.setEncoding('ascii'); diff --git a/test/wasi/test-wasi-initialize-validation.js b/test/wasi/test-wasi-initialize-validation.js new file mode 100644 index 00000000000000..79b0bd8485a483 --- /dev/null +++ b/test/wasi/test-wasi-initialize-validation.js @@ -0,0 +1,195 @@ +// Flags: --experimental-wasi-unstable-preview1 +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const vm = require('vm'); +const { WASI } = require('wasi'); + +const fixtures = require('../common/fixtures'); +const bufferSource = fixtures.readSync('simple.wasm'); + +(async () => { + { + // Verify that a WebAssembly.Instance is passed in. + const wasi = new WASI(); + + assert.throws( + () => { wasi.initialize(); }, + { + code: 'ERR_INVALID_ARG_TYPE', + message: /"instance" argument must be of type object/ + } + ); + } + + { + // Verify that the passed instance has an exports objects. + const wasi = new WASI({}); + const wasm = await WebAssembly.compile(bufferSource); + const instance = await WebAssembly.instantiate(wasm); + + Object.defineProperty(instance, 'exports', { get() { return null; } }); + assert.throws( + () => { wasi.initialize(instance); }, + { + code: 'ERR_INVALID_ARG_TYPE', + message: /"instance\.exports" property must be of type object/ + } + ); + } + + { + // Verify that a _initialize() export was passed. + const wasi = new WASI({}); + const wasm = await WebAssembly.compile(bufferSource); + const instance = await WebAssembly.instantiate(wasm); + + Object.defineProperty(instance, 'exports', { + get() { + return { _initialize: 5, memory: new Uint8Array() }; + }, + }); + assert.throws( + () => { wasi.initialize(instance); }, + { + code: 'ERR_INVALID_ARG_TYPE', + message: /"instance\.exports\._initialize" property must be of type function/ + } + ); + } + + { + // Verify that a _start export was not passed. + const wasi = new WASI({}); + const wasm = await WebAssembly.compile(bufferSource); + const instance = await WebAssembly.instantiate(wasm); + + Object.defineProperty(instance, 'exports', { + get() { + return { + _start() {}, + _initialize() {}, + memory: new Uint8Array(), + }; + } + }); + assert.throws( + () => { wasi.initialize(instance); }, + { + code: 'ERR_INVALID_ARG_TYPE', + message: /"instance\.exports\._start" property must be undefined/ + } + ); + } + + { + // Verify that a memory export was passed. + const wasi = new WASI({}); + const wasm = await WebAssembly.compile(bufferSource); + const instance = await WebAssembly.instantiate(wasm); + + Object.defineProperty(instance, 'exports', { + get() { return { _initialize() {} }; } + }); + assert.throws( + () => { wasi.initialize(instance); }, + { + code: 'ERR_INVALID_ARG_TYPE', + message: /"instance\.exports\.memory" property must be of type object/ + } + ); + } + + { + // Verify that a non-ArrayBuffer memory.buffer is rejected. + const wasi = new WASI({}); + const wasm = await WebAssembly.compile(bufferSource); + const instance = await WebAssembly.instantiate(wasm); + + Object.defineProperty(instance, 'exports', { + get() { + return { + _initialize() {}, + memory: {}, + }; + } + }); + // The error message is a little white lie because any object + // with a .buffer property of type ArrayBuffer is accepted, + // but 99% of the time a WebAssembly.Memory object is used. + assert.throws( + () => { wasi.initialize(instance); }, + { + code: 'ERR_INVALID_ARG_TYPE', + message: /"instance\.exports\.memory\.buffer" property must be an WebAssembly\.Memory/ + } + ); + } + + { + // Verify that an argument that duck-types as a WebAssembly.Instance + // is accepted. + const wasi = new WASI({}); + const wasm = await WebAssembly.compile(bufferSource); + const instance = await WebAssembly.instantiate(wasm); + + Object.defineProperty(instance, 'exports', { + get() { + return { + _initialize() {}, + memory: { buffer: new ArrayBuffer(0) }, + }; + } + }); + wasi.initialize(instance); + } + + { + // Verify that a WebAssembly.Instance from another VM context is accepted. + const wasi = new WASI({}); + const instance = await vm.runInNewContext(` + (async () => { + const wasm = await WebAssembly.compile(bufferSource); + const instance = await WebAssembly.instantiate(wasm); + + Object.defineProperty(instance, 'exports', { + get() { + return { + _initialize() {}, + memory: new WebAssembly.Memory({ initial: 1 }) + }; + } + }); + + return instance; + })() + `, { bufferSource }); + + wasi.initialize(instance); + } + + { + // Verify that initialize() can only be called once. + const wasi = new WASI({}); + const wasm = await WebAssembly.compile(bufferSource); + const instance = await WebAssembly.instantiate(wasm); + + Object.defineProperty(instance, 'exports', { + get() { + return { + _initialize() {}, + memory: new WebAssembly.Memory({ initial: 1 }) + }; + } + }); + wasi.initialize(instance); + assert.throws( + () => { wasi.initialize(instance); }, + { + code: 'ERR_WASI_ALREADY_STARTED', + message: /^WASI instance has already started$/ + } + ); + } +})().then(common.mustCall()); diff --git a/test/wasi/test-wasi-start-validation.js b/test/wasi/test-wasi-start-validation.js index 3134514d704595..5c6a1ede5d4fd7 100644 --- a/test/wasi/test-wasi-start-validation.js +++ b/test/wasi/test-wasi-start-validation.js @@ -45,7 +45,11 @@ const bufferSource = fixtures.readSync('simple.wasm'); const wasm = await WebAssembly.compile(bufferSource); const instance = await WebAssembly.instantiate(wasm); - Object.defineProperty(instance, 'exports', { get() { return {}; } }); + Object.defineProperty(instance, 'exports', { + get() { + return { memory: new Uint8Array() }; + }, + }); assert.throws( () => { wasi.start(instance); }, { @@ -65,7 +69,8 @@ const bufferSource = fixtures.readSync('simple.wasm'); get() { return { _start() {}, - _initialize() {} + _initialize() {}, + memory: new Uint8Array(), }; } });