From d82019e99a02520d815186fbaaefb5108fa8206f Mon Sep 17 00:00:00 2001 From: Kamal Rawal Date: Tue, 4 Aug 2026 14:42:54 +0530 Subject: [PATCH 01/24] doc: fix grammar and editorial issues in addons documentation Signed-off-by: Rawal27 PR-URL: https://github.com/nodejs/node/pull/64952 Reviewed-By: Mike McCready <66998419+MikeMcC399@users.noreply.github.com> Reviewed-By: Richard Lau Reviewed-By: Rich Trott --- doc/api/addons.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/api/addons.md b/doc/api/addons.md index 8272e1e77c0a..80d23a4cf48a 100644 --- a/doc/api/addons.md +++ b/doc/api/addons.md @@ -14,7 +14,7 @@ There are three options for implementing addons: * `nan` ([Native Abstractions for Node.js][]) * direct use of public V8, libuv, and Node.js interfaces -This rest of this document focuses on the latter, requiring +The rest of this document focuses on the latter, requiring knowledge of multiple components and APIs: * [V8][]: the C++ library Node.js uses to provide the @@ -113,7 +113,7 @@ To integrate with the npm ecosystem, see the [Building][] section. ### Context-aware addons -Addons defined with `NODE_MODULE()` can not be loaded in multiple contexts or +Addons defined with `NODE_MODULE()` cannot be loaded in multiple contexts or multiple threads at the same time. There are environments in which Node.js addons may need to be loaded multiple @@ -255,7 +255,7 @@ changes: In order to be loaded from multiple Node.js environments, such as a main thread and a Worker thread, an add-on needs to either: -* Be an [Node-API][] addon. +* Be a [Node-API][] addon. * Be declared as context-aware using `NODE_MODULE_INIT()` as described above. In order to support [`Worker`][] threads, addons need to clean up any resources @@ -487,13 +487,13 @@ See [C/C++ addons with Node-API][Node-API]. ## Addon examples -Following are some example addons intended to help developers get started. The +The following are some example addons intended to help developers get started. The examples use the V8 APIs. Refer to the online [V8 reference][v8-docs] for help with the various V8 calls, and V8's [Embedder's Guide][] for an explanation of several concepts used such as handles, scopes, function templates, etc. -Each of these examples using the following `binding.gyp` file: +Each of these examples uses the following `binding.gyp` file: ```json { @@ -1043,7 +1043,7 @@ provided by the underlying V8 JavaScript engine. They are subject to change or removal at any time. They are not documented by Node.js or V8, and they should never be used outside of testing. -During shutdown of the process or worker threads destructors are not called +During shutdown of the process or worker threads, destructors are not called by the JS engine. Therefore it's the responsibility of the user to track these objects and ensure proper destruction to avoid resource leaks. @@ -1274,7 +1274,7 @@ console.log(obj2.plusOne()); In addition to wrapping and returning C++ objects, it is possible to pass wrapped objects around by unwrapping them with the Node.js helper function -`ObjectWrap::Unwrap`. The following examples shows a function `add()` +`ObjectWrap::Unwrap`. The following example shows a function `add()` that can take two `MyObject` objects as input arguments: From 11bdef42b7239f84287400795897c32cc30c0d03 Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Tue, 4 Aug 2026 05:55:38 -0400 Subject: [PATCH 02/24] deps: update nghttp3 to 1.18.0 PR-URL: https://github.com/nodejs/node/pull/64943 Reviewed-By: Tim Perry Reviewed-By: Antoine du Hamel --- .../nghttp3/lib/includes/nghttp3/nghttp3.h | 127 +++++++++++++++++- .../nghttp3/lib/includes/nghttp3/version.h | 4 +- deps/ngtcp2/nghttp3/lib/nghttp3_callbacks.c | 3 + deps/ngtcp2/nghttp3/lib/nghttp3_conn.c | 51 +++++-- deps/ngtcp2/nghttp3/lib/nghttp3_ksl.c | 22 +-- deps/ngtcp2/nghttp3/lib/nghttp3_qpack.c | 54 +++++--- deps/ngtcp2/nghttp3/lib/nghttp3_qpack.h | 8 +- deps/ngtcp2/nghttp3/lib/nghttp3_stream.c | 1 - deps/ngtcp2/nghttp3/lib/nghttp3_stream.h | 2 - src/quic/http3.cc | 6 +- 10 files changed, 227 insertions(+), 51 deletions(-) diff --git a/deps/ngtcp2/nghttp3/lib/includes/nghttp3/nghttp3.h b/deps/ngtcp2/nghttp3/lib/includes/nghttp3/nghttp3.h index 83999a34d17b..d3dd9217d4b0 100644 --- a/deps/ngtcp2/nghttp3/lib/includes/nghttp3/nghttp3.h +++ b/deps/ngtcp2/nghttp3/lib/includes/nghttp3/nghttp3.h @@ -1961,7 +1961,7 @@ typedef int (*nghttp3_acked_stream_data)(nghttp3_conn *conn, int64_t stream_id, /** * @functypedef * - * :type:`nghttp3_conn_stream_close` is a callback function which is + * :type:`nghttp3_stream_close` is a callback function which is * invoked when a stream identified by |stream_id| is closed. QUIC * application error code |app_error_code| indicates the reason of * this closure. @@ -1970,6 +1970,9 @@ typedef int (*nghttp3_acked_stream_data)(nghttp3_conn *conn, int64_t stream_id, * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the * caller immediately. Any values other than 0 is treated as * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + * + * .. seealso:: + * :type:`nghttp3_stream_close2` */ typedef int (*nghttp3_stream_close)(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code, @@ -2240,10 +2243,80 @@ typedef int (*nghttp3_recv_settings2)(nghttp3_conn *conn, const nghttp3_proto_settings *settings, void *conn_user_data); +/** + * @macrosection + * + * Stream close flags + */ + +/** + * @macro + * + * :macro:`NGHTTP3_STREAM_CLOSE_FLAG_NONE` indicates no flag set. + * + * .. version-added:: 1.18.0 + */ +#define NGHTTP3_STREAM_CLOSE_FLAG_NONE 0x00U + +/** + * @macro + * + * :macro:`NGHTTP3_STREAM_CLOSE_FLAG_RX_APP_ERROR_CODE_SET` indicates + * that rx_app_error_code parameter is set. + * + * .. version-added:: 1.18.0 + */ +#define NGHTTP3_STREAM_CLOSE_FLAG_RX_APP_ERROR_CODE_SET 0x01U + +/** + * @macro + * + * :macro:`NGHTTP3_STREAM_CLOSE_FLAG_TX_APP_ERROR_CODE_SET` indicates + * that tx_app_error_code parameter is set. + * + * .. version-added:: 1.18.0 + */ +#define NGHTTP3_STREAM_CLOSE_FLAG_TX_APP_ERROR_CODE_SET 0x02U + +/** + * @functypedef + * + * :type:`nghttp3_stream_close2` is a callback function which is + * invoked when a stream identified by |stream_id| is closed. If + * :macro:`NGHTTP3_STREAM_CLOSE_FLAG_RX_APP_ERROR_CODE_SET` is set in + * |flags|, |rx_app_error_code| is the QUIC application error code + * that shut down the receiving side of the stream. If + * :macro:`NGHTTP3_STREAM_CLOSE_FLAG_TX_APP_ERROR_CODE_SET` is set in + * |flags|, |tx_app_error_code| is the QUIC application error code + * that shut down the sending side of the stream. No application code + * means that direction of stream is closed without any error. + * + * This callback should be used with `nghttp3_conn_close_stream2`. If + * `nghttp3_conn_close_stream` is used, the app_error_code is set to + * both |rx_app_error_code| and |tx_app_error_code|, and + * :macro:`NGHTTP3_STREAM_CLOSE_FLAG_RX_APP_ERROR_CODE_SET` and + * :macro:`NGHTTP3_STREAM_CLOSE_FLAG_TX_APP_ERROR_CODE_SET` are set in + * |flags|. + * + * The implementation of this callback must return 0 if it succeeds. + * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the + * caller immediately. Any values other than 0 is treated as + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + * + * .. version-added:: 1.18.0 + */ +typedef int (*nghttp3_stream_close2)(nghttp3_conn *conn, uint32_t flags, + int64_t stream_id, + uint64_t rx_app_error_code, + uint64_t tx_app_error_code, + void *conn_user_data, + void *stream_user_data); + #define NGHTTP3_CALLBACKS_V1 1 #define NGHTTP3_CALLBACKS_V2 2 #define NGHTTP3_CALLBACKS_V3 3 -#define NGHTTP3_CALLBACKS_VERSION NGHTTP3_CALLBACKS_V3 +#define NGHTTP3_CALLBACKS_V4 4 +#define NGHTTP3_CALLBACKS_VERSION NGHTTP3_CALLBACKS_V4 /** * @struct @@ -2260,6 +2333,9 @@ typedef struct nghttp3_callbacks { /** * :member:`stream_close` is a callback function which is invoked * when a particular stream has closed. + * + * .. seealso:: + * :member:`stream_close2` */ nghttp3_stream_close stream_close; /** @@ -2368,6 +2444,8 @@ typedef struct nghttp3_callbacks { * .. version-added:: 1.11.0 */ nghttp3_rand rand; + /* The following fields have been added since + NGHTTP3_CALLBACKS_V3. */ /** * :member:`recv_settings2` is a callback function which is invoked * when SETTINGS frame is received. @@ -2375,6 +2453,15 @@ typedef struct nghttp3_callbacks { * .. version-added:: 1.14.0 */ nghttp3_recv_settings2 recv_settings2; + /* The following fields have been added since + NGHTTP3_CALLBACKS_V4. */ + /** + * :member:`stream_close2` is a callback function which is invoked + * when a particular stream has closed. + * + * .. version-added:: 1.18.0 + */ + nghttp3_stream_close2 stream_close2; } nghttp3_callbacks; /** @@ -2837,11 +2924,47 @@ NGHTTP3_EXTERN int nghttp3_conn_resume_stream(nghttp3_conn *conn, * A critical stream is closed. * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` * User callback failed + * + * .. seealso:: + * `nghttp3_conn_close_stream2` */ NGHTTP3_EXTERN int nghttp3_conn_close_stream(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code); +/** + * @function + * + * `nghttp3_conn_close_stream2` tells the library that a stream + * identified by |stream_id| has been closed. If + * :macro:`NGHTTP3_STREAM_CLOSE_FLAG_RX_APP_ERROR_CODE_SET` is set in + * |flags|, |rx_app_error_code| is the QUIC application error code + * that shut down the receiving side of the stream. Similarly, + * :macro:`NGHTTP3_STREAM_CLOSE_FLAG_TX_APP_ERROR_CODE_SET` is set in + * |flags|, |tx_app_error_code| is the QUIC application error code + * that shut down the sending side of the stream. + * + * For stream close callback, prefer + * :member:`nghttp3_callbacks.stream_close2` to + * :member:`nghttp3_callbacks.stream_close`. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGHTTP3_ERR_STREAM_NOT_FOUND` + * Stream not found. + * :macro:`NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM` + * A critical stream is closed. + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` + * User callback failed + * + * .. version-added:: 1.18.0 + */ +NGHTTP3_EXTERN int nghttp3_conn_close_stream2(nghttp3_conn *conn, + uint32_t flags, int64_t stream_id, + uint64_t rx_app_error_code, + uint64_t tx_app_error_code); + /** * @macrosection * diff --git a/deps/ngtcp2/nghttp3/lib/includes/nghttp3/version.h b/deps/ngtcp2/nghttp3/lib/includes/nghttp3/version.h index 6f9f04e7426b..adb41c1c7b2d 100644 --- a/deps/ngtcp2/nghttp3/lib/includes/nghttp3/version.h +++ b/deps/ngtcp2/nghttp3/lib/includes/nghttp3/version.h @@ -31,7 +31,7 @@ * * Version number of the nghttp3 library release. */ -#define NGHTTP3_VERSION "1.17.0" +#define NGHTTP3_VERSION "1.18.0" /** * @macro @@ -41,6 +41,6 @@ * number, 8 bits for minor and 8 bits for patch. Version 1.2.3 * becomes 0x010203. */ -#define NGHTTP3_VERSION_NUM 0x011100 +#define NGHTTP3_VERSION_NUM 0x011200 #endif /* !defined(NGHTTP3_VERSION_H) */ diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_callbacks.c b/deps/ngtcp2/nghttp3/lib/nghttp3_callbacks.c index 283f479b0d34..441c707af90f 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_callbacks.c +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_callbacks.c @@ -66,6 +66,9 @@ size_t nghttp3_callbackslen_version(int callbacks_version) { switch (callbacks_version) { case NGHTTP3_CALLBACKS_VERSION: return sizeof(callbacks); + case NGHTTP3_CALLBACKS_V3: + return offsetof(nghttp3_callbacks, recv_settings2) + + sizeof(callbacks.recv_settings2); case NGHTTP3_CALLBACKS_V2: return offsetof(nghttp3_callbacks, rand) + sizeof(callbacks.rand); case NGHTTP3_CALLBACKS_V1: diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_conn.c b/deps/ngtcp2/nghttp3/lib/nghttp3_conn.c index d1b6355bb7e0..04b50ac3b632 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_conn.c +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_conn.c @@ -623,7 +623,9 @@ static nghttp3_ssize conn_read_type(nghttp3_conn *conn, nghttp3_stream *stream, return nread; } -static int conn_delete_stream(nghttp3_conn *conn, nghttp3_stream *stream); +static int conn_delete_stream(nghttp3_conn *conn, nghttp3_stream *stream, + uint32_t flags, uint64_t rx_app_error_code, + uint64_t tx_app_error_code); nghttp3_ssize nghttp3_conn_read_uni(nghttp3_conn *conn, nghttp3_stream *stream, const uint8_t *src, size_t srclen, int fin, @@ -649,7 +651,8 @@ nghttp3_ssize nghttp3_conn_read_uni(nghttp3_conn *conn, nghttp3_stream *stream, return NGHTTP3_ERR_H3_EXCESSIVE_LOAD; } - return conn_delete_stream(conn, stream); + return conn_delete_stream(conn, stream, NGHTTP3_STREAM_CLOSE_FLAG_NONE, 0, + 0); } nread = conn_read_type(conn, stream, src, srclen, fin); if (nread < 0) { @@ -1200,7 +1203,7 @@ nghttp3_ssize nghttp3_conn_read_control(nghttp3_conn *conn, for (; conn->rx.originlen_offset < sizeof(conn->rx.originlen) && (size_t)nread < len; ++conn->rx.originlen_offset, ++nread) { - conn->rx.originlen <<= 8; + conn->rx.originlen *= 256; conn->rx.originlen += *p++; } @@ -1335,8 +1338,11 @@ nghttp3_ssize nghttp3_conn_read_control(nghttp3_conn *conn, return (nghttp3_ssize)nconsumed; } -static int conn_delete_stream(nghttp3_conn *conn, nghttp3_stream *stream) { +static int conn_delete_stream(nghttp3_conn *conn, nghttp3_stream *stream, + uint32_t flags, uint64_t rx_app_error_code, + uint64_t tx_app_error_code) { int rv; + uint64_t app_error_code; rv = conn_call_deferred_consume(conn, stream, nghttp3_stream_get_buffered_datalen(stream)); @@ -1353,8 +1359,26 @@ static int conn_delete_stream(nghttp3_conn *conn, nghttp3_stream *stream) { } } - if (conn->callbacks.stream_close) { - rv = conn->callbacks.stream_close(conn, stream->node.id, stream->error_code, + if (conn->callbacks.stream_close2) { + rv = conn->callbacks.stream_close2(conn, flags, stream->node.id, + rx_app_error_code, tx_app_error_code, + conn->user_data, stream->user_data); + if (rv != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } else if (conn->callbacks.stream_close) { + app_error_code = NGHTTP3_H3_NO_ERROR; + + if (flags & NGHTTP3_STREAM_CLOSE_FLAG_RX_APP_ERROR_CODE_SET) { + app_error_code = rx_app_error_code; + } + + if (app_error_code == NGHTTP3_H3_NO_ERROR && + (flags & NGHTTP3_STREAM_CLOSE_FLAG_TX_APP_ERROR_CODE_SET)) { + app_error_code = tx_app_error_code; + } + + rv = conn->callbacks.stream_close(conn, stream->node.id, app_error_code, conn->user_data, stream->user_data); if (rv != 0) { return NGHTTP3_ERR_CALLBACK_FAILURE; @@ -2725,6 +2749,16 @@ int nghttp3_conn_resume_stream(nghttp3_conn *conn, int64_t stream_id) { int nghttp3_conn_close_stream(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code) { + return nghttp3_conn_close_stream2( + conn, + NGHTTP3_STREAM_CLOSE_FLAG_RX_APP_ERROR_CODE_SET | + NGHTTP3_STREAM_CLOSE_FLAG_TX_APP_ERROR_CODE_SET, + stream_id, app_error_code, app_error_code); +} + +int nghttp3_conn_close_stream2(nghttp3_conn *conn, uint32_t flags, + int64_t stream_id, uint64_t rx_app_error_code, + uint64_t tx_app_error_code) { nghttp3_stream *stream = nghttp3_conn_find_stream(conn, stream_id); if (stream == NULL) { @@ -2736,11 +2770,10 @@ int nghttp3_conn_close_stream(nghttp3_conn *conn, int64_t stream_id, return NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM; } - stream->error_code = app_error_code; - nghttp3_conn_unschedule_stream(conn, stream); - return conn_delete_stream(conn, stream); + return conn_delete_stream(conn, stream, flags, rx_app_error_code, + tx_app_error_code); } int nghttp3_conn_shutdown_stream_read(nghttp3_conn *conn, int64_t stream_id) { diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_ksl.c b/deps/ngtcp2/nghttp3/lib/nghttp3_ksl.c index efb4f958a85e..4ab9b81fde0c 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_ksl.c +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_ksl.c @@ -37,8 +37,11 @@ static nghttp3_ksl_blk null_blk; nghttp3_objalloc_def(ksl_blk, nghttp3_ksl_blk, oplent) +#define NGHTTP3_KSL_ALIGNED_BLKLEN \ + ((sizeof(nghttp3_ksl_blk) + 0x7U) & ~(size_t)0x7U) + static size_t ksl_blklen(size_t aligned_keylen) { - return sizeof(nghttp3_ksl_blk) + NGHTTP3_KSL_MAX_NBLK * aligned_keylen; + return NGHTTP3_KSL_ALIGNED_BLKLEN + NGHTTP3_KSL_MAX_NBLK * aligned_keylen; } /* @@ -80,7 +83,7 @@ static nghttp3_ksl_blk *ksl_blk_objalloc_new(nghttp3_ksl *ksl) { return NULL; } - blk->keys = (uint8_t *)blk + sizeof(*blk); + blk->keys = (uint8_t *)blk + NGHTTP3_KSL_ALIGNED_BLKLEN; blk->aligned_keylen = (uint16_t)ksl->aligned_keylen; return blk; @@ -223,22 +226,21 @@ static int ksl_split_node(nghttp3_ksl *ksl, nghttp3_ksl_blk *blk, size_t i) { * Out of memory. */ static int ksl_split_root(nghttp3_ksl *ksl) { - nghttp3_ksl_blk *rblk = NULL, *lblk, *nroot = NULL; + nghttp3_ksl_blk *rblk, *lblk, *nroot; + + nroot = ksl_blk_objalloc_new(ksl); + if (nroot == NULL) { + return NGHTTP3_ERR_NOMEM; + } rblk = ksl_split_blk(ksl, ksl->root); if (rblk == NULL) { + ksl_blk_objalloc_del(ksl, nroot); return NGHTTP3_ERR_NOMEM; } lblk = ksl->root; - nroot = ksl_blk_objalloc_new(ksl); - - if (nroot == NULL) { - ksl_blk_objalloc_del(ksl, rblk); - return NGHTTP3_ERR_NOMEM; - } - nroot->next = nroot->prev = NULL; nroot->n = 2; nroot->leaf = 0; diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_qpack.c b/deps/ngtcp2/nghttp3/lib/nghttp3_qpack.c index 59b633ea0572..8e950a201d89 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_qpack.c +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_qpack.c @@ -2820,6 +2820,7 @@ nghttp3_ssize nghttp3_qpack_decoder_read_encoder(nghttp3_qpack_decoder *decoder, int busy = 0; const nghttp3_mem *mem = decoder->ctx.mem; nghttp3_ssize nread; + size_t huff_declen; int rfin; if (decoder->ctx.bad) { @@ -2958,13 +2959,16 @@ nghttp3_ssize nghttp3_qpack_decoder_read_encoder(nghttp3_qpack_decoder *decoder, } if (decoder->rstate.huffman_encoded) { + huff_declen = nghttp3_qpack_huffman_estimate_decode_length( + (size_t)decoder->rstate.left); + if (huff_declen > NGHTTP3_QPACK_MAX_NAMELEN) { + rv = NGHTTP3_ERR_QPACK_HEADER_TOO_LARGE; + goto fail; + } + decoder->state = NGHTTP3_QPACK_ES_STATE_READ_NAME_HUFFMAN; nghttp3_qpack_huffman_decode_context_init(&decoder->rstate.huffman_ctx); - rv = nghttp3_rcbuf_new(&decoder->rstate.name, - nghttp3_qpack_huffman_estimate_decode_length( - (size_t)decoder->rstate.left) + - 1, - mem); + rv = nghttp3_rcbuf_new(&decoder->rstate.name, huff_declen + 1, mem); } else { decoder->state = NGHTTP3_QPACK_ES_STATE_READ_NAME; rv = nghttp3_rcbuf_new(&decoder->rstate.name, @@ -3043,13 +3047,16 @@ nghttp3_ssize nghttp3_qpack_decoder_read_encoder(nghttp3_qpack_decoder *decoder, } if (decoder->rstate.huffman_encoded) { + huff_declen = nghttp3_qpack_huffman_estimate_decode_length( + (size_t)decoder->rstate.left); + if (huff_declen > NGHTTP3_QPACK_MAX_VALUELEN) { + rv = NGHTTP3_ERR_QPACK_HEADER_TOO_LARGE; + goto fail; + } + decoder->state = NGHTTP3_QPACK_ES_STATE_READ_VALUE_HUFFMAN; nghttp3_qpack_huffman_decode_context_init(&decoder->rstate.huffman_ctx); - rv = nghttp3_rcbuf_new(&decoder->rstate.value, - nghttp3_qpack_huffman_estimate_decode_length( - (size_t)decoder->rstate.left) + - 1, - mem); + rv = nghttp3_rcbuf_new(&decoder->rstate.value, huff_declen + 1, mem); } else { decoder->state = NGHTTP3_QPACK_ES_STATE_READ_VALUE; rv = nghttp3_rcbuf_new(&decoder->rstate.value, @@ -3342,6 +3349,7 @@ nghttp3_qpack_decoder_read_request(nghttp3_qpack_decoder *decoder, nghttp3_ssize nread; int rfin; const nghttp3_mem *mem = decoder->ctx.mem; + size_t huff_declen; if (decoder->ctx.bad) { return NGHTTP3_ERR_QPACK_FATAL; @@ -3565,13 +3573,16 @@ nghttp3_qpack_decoder_read_request(nghttp3_qpack_decoder *decoder, } if (sctx->rstate.huffman_encoded) { + huff_declen = nghttp3_qpack_huffman_estimate_decode_length( + (size_t)sctx->rstate.left); + if (huff_declen > NGHTTP3_QPACK_MAX_NAMELEN) { + rv = NGHTTP3_ERR_QPACK_HEADER_TOO_LARGE; + goto fail; + } + sctx->state = NGHTTP3_QPACK_RS_STATE_READ_NAME_HUFFMAN; nghttp3_qpack_huffman_decode_context_init(&sctx->rstate.huffman_ctx); - rv = nghttp3_rcbuf_new(&sctx->rstate.name, - nghttp3_qpack_huffman_estimate_decode_length( - (size_t)sctx->rstate.left) + - 1, - mem); + rv = nghttp3_rcbuf_new(&sctx->rstate.name, huff_declen + 1, mem); } else { sctx->state = NGHTTP3_QPACK_RS_STATE_READ_NAME; rv = nghttp3_rcbuf_new(&sctx->rstate.name, @@ -3648,13 +3659,16 @@ nghttp3_qpack_decoder_read_request(nghttp3_qpack_decoder *decoder, } if (sctx->rstate.huffman_encoded) { + huff_declen = nghttp3_qpack_huffman_estimate_decode_length( + (size_t)sctx->rstate.left); + if (huff_declen > NGHTTP3_QPACK_MAX_VALUELEN) { + rv = NGHTTP3_ERR_QPACK_HEADER_TOO_LARGE; + goto fail; + } + sctx->state = NGHTTP3_QPACK_RS_STATE_READ_VALUE_HUFFMAN; nghttp3_qpack_huffman_decode_context_init(&sctx->rstate.huffman_ctx); - rv = nghttp3_rcbuf_new(&sctx->rstate.value, - nghttp3_qpack_huffman_estimate_decode_length( - (size_t)sctx->rstate.left) + - 1, - mem); + rv = nghttp3_rcbuf_new(&sctx->rstate.value, huff_declen + 1, mem); } else { sctx->state = NGHTTP3_QPACK_RS_STATE_READ_VALUE; rv = nghttp3_rcbuf_new(&sctx->rstate.value, diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_qpack.h b/deps/ngtcp2/nghttp3/lib/nghttp3_qpack.h index 2b8dca9d5cf7..934cae77c1bf 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_qpack.h +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_qpack.h @@ -42,11 +42,11 @@ #define NGHTTP3_QPACK_INT_MAX ((1ULL << 62) - 1) -/* NGHTTP3_QPACK_MAX_NAMELEN is the maximum (compressed) length of - header name this library can decode. */ +/* NGHTTP3_QPACK_MAX_NAMELEN is the maximum (estimated uncompressed) + length of header name this library can decode. */ #define NGHTTP3_QPACK_MAX_NAMELEN 256 -/* NGHTTP3_QPACK_MAX_VALUELEN is the maximum (compressed) length of - header value this library can decode. */ +/* NGHTTP3_QPACK_MAX_VALUELEN is the maximum (estimated uncompressed) + length of header value this library can decode. */ #define NGHTTP3_QPACK_MAX_VALUELEN 65536 /* NGHTTP3_QPACK_MAX_ENCODERLEN is the maximum encoder stream length that a decoder accepts without completely processing a single field diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_stream.c b/deps/ngtcp2/nghttp3/lib/nghttp3_stream.c index 76db6fe303da..09298006f4f6 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_stream.c +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_stream.c @@ -68,7 +68,6 @@ int nghttp3_stream_new(nghttp3_stream **pstream, int64_t stream_id, .http.content_length = -1, .http.pri.urgency = NGHTTP3_DEFAULT_URGENCY, }, - .error_code = NGHTTP3_H3_NO_ERROR, }; nghttp3_tnode_init(&stream->node, stream_id); diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_stream.h b/deps/ngtcp2/nghttp3/lib/nghttp3_stream.h index 61a1f085ac87..d313b1c192b9 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_stream.h +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_stream.h @@ -225,8 +225,6 @@ struct nghttp3_stream { uint64_t unscheduled_nwrite; nghttp3_stream_type type; nghttp3_stream_read_state rstate; - /* error_code indicates the reason of closure of this stream. */ - uint64_t error_code; struct { uint64_t offset; diff --git a/src/quic/http3.cc b/src/quic/http3.cc index efaf19594270..b6d876af60f6 100644 --- a/src/quic/http3.cc +++ b/src/quic/http3.cc @@ -1399,7 +1399,11 @@ class Http3ApplicationImpl final : public Session::Application { on_receive_origin, on_end_origin, on_rand, - on_receive_settings}; + on_receive_settings, +#ifdef NGHTTP3_CALLBACKS_V4 + nullptr, +#endif // NGHTTP3_CALLBACKS_V4 + }; }; std::optional ParseHttp3TicketData(const uv_buf_t& data) { From ece8bbef9770f7169c3649374e6e0d6bb6483fab Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Sun, 2 Aug 2026 00:53:39 +0000 Subject: [PATCH 03/24] deps: update ngtcp2 to 1.25.0 PR-URL: https://github.com/nodejs/node/pull/64944 Reviewed-By: Tim Perry Reviewed-By: Antoine du Hamel --- .../ngtcp2/crypto/boringssl/boringssl.c | 35 ++- deps/ngtcp2/ngtcp2/crypto/ossl/ossl.c | 51 +++- deps/ngtcp2/ngtcp2/crypto/quictls/quictls.c | 102 ++++---- deps/ngtcp2/ngtcp2/examples/client.cc | 45 ++-- deps/ngtcp2/ngtcp2/examples/client.h | 6 +- deps/ngtcp2/ngtcp2/examples/debug.cc | 6 +- .../ngtcp2/examples/hq_client_proto_codec.cc | 10 +- .../ngtcp2/examples/hq_client_proto_codec.h | 6 +- .../ngtcp2/examples/hq_server_proto_codec.cc | 18 +- .../ngtcp2/examples/hq_server_proto_codec.h | 9 +- .../examples/http3_client_proto_codec.cc | 32 ++- .../examples/http3_client_proto_codec.h | 10 +- .../examples/http3_server_proto_codec.cc | 32 ++- .../examples/http3_server_proto_codec.h | 10 +- deps/ngtcp2/ngtcp2/examples/server.cc | 60 +++-- deps/ngtcp2/ngtcp2/examples/server.h | 6 +- deps/ngtcp2/ngtcp2/examples/util.cc | 6 + deps/ngtcp2/ngtcp2/examples/util.h | 4 + deps/ngtcp2/ngtcp2/examples/util_test.cc | 6 + deps/ngtcp2/ngtcp2/examples/util_test.h | 1 + .../ngtcp2/lib/includes/ngtcp2/ngtcp2.h | 97 +++++++- .../ngtcp2/lib/includes/ngtcp2/version.h | 4 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.c | 152 +++++------- deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.h | 72 ++---- deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c | 155 +++++++----- deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h | 14 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_callbacks.c | 3 + deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c | 171 +++++++------ deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.c | 22 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.c | 2 + deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.c | 224 +++++++++--------- deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.h | 88 ++----- deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.c | 54 ++++- deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.h | 55 ++++- src/quic/session.cc | 10 +- 35 files changed, 899 insertions(+), 679 deletions(-) diff --git a/deps/ngtcp2/ngtcp2/crypto/boringssl/boringssl.c b/deps/ngtcp2/ngtcp2/crypto/boringssl/boringssl.c index 6daedf107555..bcb7cbd3647c 100644 --- a/deps/ngtcp2/ngtcp2/crypto/boringssl/boringssl.c +++ b/deps/ngtcp2/ngtcp2/crypto/boringssl/boringssl.c @@ -278,33 +278,44 @@ int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx, const uint8_t *key) { ngtcp2_crypto_boringssl_cipher *hp_cipher = cipher->native_handle; ngtcp2_crypto_boringssl_cipher_ctx *ctx; - int rv; - (void)rv; + int rv = 0; ctx = malloc(sizeof(*ctx)); if (ctx == NULL) { return -1; } - ctx->type = hp_cipher->type; - cipher_ctx->native_handle = ctx; - switch (hp_cipher->type) { case NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_128: - rv = AES_set_encrypt_key(key, 128, &ctx->aes_key); - assert(0 == rv); - return 0; + if (AES_set_encrypt_key(key, 128, &ctx->aes_key) != 0) { + rv = -1; + } + + break; case NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_256: - rv = AES_set_encrypt_key(key, 256, &ctx->aes_key); - assert(0 == rv); - return 0; + if (AES_set_encrypt_key(key, 256, &ctx->aes_key) != 0) { + rv = -1; + } + + break; case NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_CHACHA20: memcpy(ctx->key, key, sizeof(ctx->key)); - return 0; + break; default: assert(0); abort(); }; + + if (rv != 0) { + free(ctx); + + return rv; + } + + ctx->type = hp_cipher->type; + cipher_ctx->native_handle = ctx; + + return 0; } void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx) { diff --git a/deps/ngtcp2/ngtcp2/crypto/ossl/ossl.c b/deps/ngtcp2/ngtcp2/crypto/ossl/ossl.c index 3fedb8df8e8d..067129077545 100644 --- a/deps/ngtcp2/ngtcp2/crypto/ossl/ossl.c +++ b/deps/ngtcp2/ngtcp2/crypto/ossl/ossl.c @@ -730,8 +730,8 @@ int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md, const uint8_t *secret, size_t secretlen, const uint8_t *salt, size_t saltlen) { const EVP_MD *prf = md->native_handle; - EVP_KDF *kdf = crypto_kdf_hkdf(); - EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + EVP_KDF *kdf; + EVP_KDF_CTX *kctx; int mode = EVP_KDF_HKDF_MODE_EXTRACT_ONLY; OSSL_PARAM params[] = { OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode), @@ -745,13 +745,24 @@ int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md, }; int rv = 0; - crypto_kdf_hkdf_free(kdf); + kdf = crypto_kdf_hkdf(); + if (!kdf) { + return -1; + } + + kctx = EVP_KDF_CTX_new(kdf); + if (!kctx) { + rv = -1; + goto fail_kdf_ctx_new; + } if (EVP_KDF_derive(kctx, dest, (size_t)EVP_MD_size(prf), params) <= 0) { rv = -1; } EVP_KDF_CTX_free(kctx); +fail_kdf_ctx_new: + crypto_kdf_hkdf_free(kdf); return rv; } @@ -761,8 +772,8 @@ int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen, size_t secretlen, const uint8_t *info, size_t infolen) { const EVP_MD *prf = md->native_handle; - EVP_KDF *kdf = crypto_kdf_hkdf(); - EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + EVP_KDF *kdf; + EVP_KDF_CTX *kctx; int mode = EVP_KDF_HKDF_MODE_EXPAND_ONLY; OSSL_PARAM params[] = { OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode), @@ -776,13 +787,24 @@ int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen, }; int rv = 0; - crypto_kdf_hkdf_free(kdf); + kdf = crypto_kdf_hkdf(); + if (!kdf) { + return -1; + } + + kctx = EVP_KDF_CTX_new(kdf); + if (!kctx) { + rv = -1; + goto fail_kdf_ctx_new; + } if (EVP_KDF_derive(kctx, dest, destlen, params) <= 0) { rv = -1; } EVP_KDF_CTX_free(kctx); +fail_kdf_ctx_new: + crypto_kdf_hkdf_free(kdf); return rv; } @@ -792,8 +814,8 @@ int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen, size_t secretlen, const uint8_t *salt, size_t saltlen, const uint8_t *info, size_t infolen) { const EVP_MD *prf = md->native_handle; - EVP_KDF *kdf = crypto_kdf_hkdf(); - EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + EVP_KDF *kdf; + EVP_KDF_CTX *kctx; OSSL_PARAM params[] = { OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, (char *)EVP_MD_get0_name(prf), 0), @@ -807,13 +829,24 @@ int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen, }; int rv = 0; - crypto_kdf_hkdf_free(kdf); + kdf = crypto_kdf_hkdf(); + if (!kdf) { + return -1; + } + + kctx = EVP_KDF_CTX_new(kdf); + if (!kctx) { + rv = -1; + goto fail_kdf_ctx_new; + } if (EVP_KDF_derive(kctx, dest, destlen, params) <= 0) { rv = -1; } EVP_KDF_CTX_free(kctx); +fail_kdf_ctx_new: + crypto_kdf_hkdf_free(kdf); return rv; } diff --git a/deps/ngtcp2/ngtcp2/crypto/quictls/quictls.c b/deps/ngtcp2/ngtcp2/crypto/quictls/quictls.c index 64669168d3cf..5768c7c2e606 100644 --- a/deps/ngtcp2/ngtcp2/crypto/quictls/quictls.c +++ b/deps/ngtcp2/ngtcp2/crypto/quictls/quictls.c @@ -44,7 +44,6 @@ #include "shared.h" #if OPENSSL_VERSION_NUMBER >= 0x30000000L -static int crypto_initialized; static EVP_CIPHER *crypto_aes_128_gcm; static EVP_CIPHER *crypto_aes_256_gcm; static EVP_CIPHER *crypto_chacha20_poly1305; @@ -57,57 +56,19 @@ static EVP_MD *crypto_sha384; static EVP_KDF *crypto_hkdf; int ngtcp2_crypto_quictls_init(void) { + /* We do not care whether the pre-fetch succeeds or not. If it + fails, it returns NULL, which is still the default value, and our + code should still work with it. */ crypto_aes_128_gcm = EVP_CIPHER_fetch(NULL, "AES-128-GCM", NULL); - if (crypto_aes_128_gcm == NULL) { - return -1; - } - crypto_aes_256_gcm = EVP_CIPHER_fetch(NULL, "AES-256-GCM", NULL); - if (crypto_aes_256_gcm == NULL) { - return -1; - } - crypto_chacha20_poly1305 = EVP_CIPHER_fetch(NULL, "ChaCha20-Poly1305", NULL); - if (crypto_chacha20_poly1305 == NULL) { - return -1; - } - crypto_aes_128_ccm = EVP_CIPHER_fetch(NULL, "AES-128-CCM", NULL); - if (crypto_aes_128_ccm == NULL) { - return -1; - } - crypto_aes_128_ecb = EVP_CIPHER_fetch(NULL, "AES-128-ECB", NULL); - if (crypto_aes_128_ecb == NULL) { - return -1; - } - crypto_aes_256_ecb = EVP_CIPHER_fetch(NULL, "AES-256-ECB", NULL); - if (crypto_aes_256_ecb == NULL) { - return -1; - } - crypto_chacha20 = EVP_CIPHER_fetch(NULL, "ChaCha20", NULL); - if (crypto_chacha20 == NULL) { - return -1; - } - crypto_sha256 = EVP_MD_fetch(NULL, "sha256", NULL); - if (crypto_sha256 == NULL) { - return -1; - } - crypto_sha384 = EVP_MD_fetch(NULL, "sha384", NULL); - if (crypto_sha384 == NULL) { - return -1; - } - crypto_hkdf = EVP_KDF_fetch(NULL, "hkdf", NULL); - if (crypto_hkdf == NULL) { - return -1; - } - - crypto_initialized = 1; return 0; } @@ -191,6 +152,12 @@ static EVP_KDF *crypto_kdf_hkdf(void) { return EVP_KDF_fetch(NULL, "hkdf", NULL); } + +static void crypto_kdf_hkdf_free(EVP_KDF *kdf) { + if (kdf && crypto_hkdf != kdf) { + EVP_KDF_free(kdf); + } +} #else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ # define crypto_aead_aes_128_gcm EVP_aes_128_gcm # define crypto_aead_aes_256_gcm EVP_aes_256_gcm @@ -524,8 +491,8 @@ int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md, const uint8_t *salt, size_t saltlen) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L const EVP_MD *prf = md->native_handle; - EVP_KDF *kdf = crypto_kdf_hkdf(); - EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + EVP_KDF *kdf; + EVP_KDF_CTX *kctx; int mode = EVP_KDF_HKDF_MODE_EXTRACT_ONLY; OSSL_PARAM params[] = { OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode), @@ -539,8 +506,15 @@ int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md, }; int rv = 0; - if (!crypto_initialized) { - EVP_KDF_free(kdf); + kdf = crypto_kdf_hkdf(); + if (!kdf) { + return -1; + } + + kctx = EVP_KDF_CTX_new(kdf); + if (!kctx) { + rv = -1; + goto fail_kdf_ctx_new; } if (EVP_KDF_derive(kctx, dest, (size_t)EVP_MD_size(prf), params) <= 0) { @@ -548,6 +522,8 @@ int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md, } EVP_KDF_CTX_free(kctx); +fail_kdf_ctx_new: + crypto_kdf_hkdf_free(kdf); return rv; #else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ @@ -581,8 +557,8 @@ int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen, size_t infolen) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L const EVP_MD *prf = md->native_handle; - EVP_KDF *kdf = crypto_kdf_hkdf(); - EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + EVP_KDF *kdf; + EVP_KDF_CTX *kctx; int mode = EVP_KDF_HKDF_MODE_EXPAND_ONLY; OSSL_PARAM params[] = { OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode), @@ -596,8 +572,15 @@ int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen, }; int rv = 0; - if (!crypto_initialized) { - EVP_KDF_free(kdf); + kdf = crypto_kdf_hkdf(); + if (!kdf) { + return -1; + } + + kctx = EVP_KDF_CTX_new(kdf); + if (!kctx) { + rv = -1; + goto fail_kdf_ctx_new; } if (EVP_KDF_derive(kctx, dest, destlen, params) <= 0) { @@ -605,6 +588,8 @@ int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen, } EVP_KDF_CTX_free(kctx); +fail_kdf_ctx_new: + crypto_kdf_hkdf_free(kdf); return rv; #else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ @@ -637,8 +622,8 @@ int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen, const uint8_t *info, size_t infolen) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L const EVP_MD *prf = md->native_handle; - EVP_KDF *kdf = crypto_kdf_hkdf(); - EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + EVP_KDF *kdf; + EVP_KDF_CTX *kctx; OSSL_PARAM params[] = { OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, (char *)EVP_MD_get0_name(prf), 0), @@ -652,8 +637,15 @@ int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen, }; int rv = 0; - if (!crypto_initialized) { - EVP_KDF_free(kdf); + kdf = crypto_kdf_hkdf(); + if (!kdf) { + return -1; + } + + kctx = EVP_KDF_CTX_new(kdf); + if (!kctx) { + rv = -1; + goto fail_kdf_ctx_new; } if (EVP_KDF_derive(kctx, dest, destlen, params) <= 0) { @@ -661,6 +653,8 @@ int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen, } EVP_KDF_CTX_free(kctx); +fail_kdf_ctx_new: + crypto_kdf_hkdf_free(kdf); return rv; #else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ diff --git a/deps/ngtcp2/ngtcp2/examples/client.cc b/deps/ngtcp2/ngtcp2/examples/client.cc index 30e078c8e673..0950ebf3a4f0 100644 --- a/deps/ngtcp2/ngtcp2/examples/client.cc +++ b/deps/ngtcp2/ngtcp2/examples/client.cc @@ -105,7 +105,7 @@ namespace { void writecb(struct ev_loop *loop, ev_io *w, int revents) { auto c = static_cast(w->data); - c->on_write(); + (void)c->on_write(); } } // namespace @@ -118,7 +118,7 @@ void readcb(struct ev_loop *loop, ev_io *w, int revents) { return; } - c->on_write(); + (void)c->on_write(); } } // namespace @@ -130,7 +130,7 @@ void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { return; } - c->on_write(); + (void)c->on_write(); } } // namespace @@ -138,7 +138,7 @@ namespace { void change_local_addrcb(struct ev_loop *loop, ev_timer *w, int revents) { auto c = static_cast(w->data); - c->change_local_addr(); + (void)c->change_local_addr(); } } // namespace @@ -158,7 +158,7 @@ void delay_streamcb(struct ev_loop *loop, ev_timer *w, int revents) { ev_timer_stop(loop, w); c->on_extend_max_streams(); - c->on_write(); + (void)c->on_write(); } } // namespace @@ -202,7 +202,7 @@ Client::~Client() { disconnect(); } void Client::disconnect() { tx_.send_blocked = false; - handle_error(); + (void)handle_error(); config.tx_loss_prob = 0; @@ -391,11 +391,18 @@ void Client::recv_version_negotiation(const uint32_t *sv, size_t nsv) { namespace { int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) { + uint64_t rx_app_error_code, uint64_t tx_app_error_code, + void *user_data, void *stream_user_data) { auto c = static_cast(user_data); - if (!c->on_stream_close(stream_id, app_error_code)) { + if (!c->on_stream_close( + stream_id, + (flags & NGTCP2_STREAM_CLOSE2_FLAG_RX_APP_ERROR_CODE_SET) + ? std::make_optional(rx_app_error_code) + : std::nullopt, + (flags & NGTCP2_STREAM_CLOSE2_FLAG_TX_APP_ERROR_CODE_SET) + ? std::make_optional(tx_app_error_code) + : std::nullopt)) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -588,7 +595,7 @@ int recv_new_token(ngtcp2_conn *conn, const uint8_t *token, size_t tokenlen, return 0; } - util::write_token(config.token_file, {token, tokenlen}); + (void)util::write_token(config.token_file, {token, tokenlen}); return 0; } @@ -655,7 +662,6 @@ std::expected Client::init(int fd, const Address &local_addr, .hp_mask = do_hp_mask, .recv_stream_data = ::recv_stream_data, .acked_stream_data_offset = ::acked_stream_data_offset, - .stream_close = stream_close, .recv_retry = ngtcp2_crypto_recv_retry_cb, .extend_max_local_streams_bidi = extend_max_local_streams_bidi, .rand = rand, @@ -674,6 +680,7 @@ std::expected Client::init(int fd, const Address &local_addr, .tls_early_data_rejected = ::early_data_rejected, .get_new_connection_id2 = get_new_connection_id, .get_path_challenge_data2 = ngtcp2_crypto_get_path_challenge_data2_cb, + .stream_close2 = stream_close, }; ngtcp2_cid scid, dcid; @@ -1054,8 +1061,8 @@ std::expected Client::write_streams() { return {}; } - send_packet_or_blocked(ps.path, pi.ecn, - txbuf.first(static_cast(nwrite)), gso_size); + (void)send_packet_or_blocked( + ps.path, pi.ecn, txbuf.first(static_cast(nwrite)), gso_size); return {}; } @@ -1662,13 +1669,17 @@ std::expected Client::handle_error() { {buf.data(), static_cast(nwrite)}); } -std::expected Client::on_stream_close(int64_t stream_id, - uint64_t app_error_code) { +std::expected +Client::on_stream_close(int64_t stream_id, + std::optional rx_app_error_code, + std::optional tx_app_error_code) { if (!config.quiet) { std::println(stderr, "QUIC stream {:#x} closed", stream_id); } - if (auto rv = proto_codec_->on_stream_close(stream_id, app_error_code); !rv) { + if (auto rv = proto_codec_->on_stream_close(stream_id, rx_app_error_code, + tx_app_error_code); + !rv) { return rv; } @@ -1730,7 +1741,7 @@ void Client::on_extend_max_streams() { } if (!config.download.empty()) { - stream->open_file(stream->req.path); + (void)stream->open_file(stream->req.path); } if (auto [_, rv] = streams_.try_emplace(stream_id, std::move(stream)); diff --git a/deps/ngtcp2/ngtcp2/examples/client.h b/deps/ngtcp2/ngtcp2/examples/client.h index f88f3e46664e..c66d2e720c83 100644 --- a/deps/ngtcp2/ngtcp2/examples/client.h +++ b/deps/ngtcp2/ngtcp2/examples/client.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -116,8 +117,9 @@ class Client : public ClientBase { std::expected send_packet_or_blocked(const ngtcp2_path &path, unsigned int ecn, std::span data, size_t gso_size); - std::expected on_stream_close(int64_t stream_id, - uint64_t app_error_code); + std::expected + on_stream_close(int64_t stream_id, std::optional rx_app_error_code, + std::optional tx_app_error_code); void on_extend_max_streams(); std::expected handle_error(); std::expected make_stream_early(); diff --git a/deps/ngtcp2/ngtcp2/examples/debug.cc b/deps/ngtcp2/ngtcp2/examples/debug.cc index 495f42cd3cf3..24acd4829e1f 100644 --- a/deps/ngtcp2/ngtcp2/examples/debug.cc +++ b/deps/ngtcp2/ngtcp2/examples/debug.cc @@ -80,12 +80,12 @@ void print_crypto_data(ngtcp2_encryption_level encryption_level, } std::println(outfile, "Ordered CRYPTO data in {} crypto level", encryption_level_str); - util::hexdump(outfile, data); + (void)util::hexdump(outfile, data); } void print_stream_data(int64_t stream_id, std::span data) { std::println(outfile, "Ordered STREAM data stream_id={:#x}", stream_id); - util::hexdump(outfile, data); + (void)util::hexdump(outfile, data); } void print_initial_secret(std::span data) { @@ -242,7 +242,7 @@ void print_http_end_headers(int64_t stream_id) { void print_http_data(int64_t stream_id, std::span data) { std::println(outfile, "http: stream {:#x} body {} bytes", stream_id, data.size()); - util::hexdump(outfile, data); + (void)util::hexdump(outfile, data); } void print_http_begin_trailers(int64_t stream_id) { diff --git a/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.cc b/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.cc index 9537f862dad6..ed072c8058f7 100644 --- a/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.cc +++ b/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.cc @@ -75,14 +75,18 @@ ProtoCodec::recv_stream_data(uint32_t flags, int64_t stream_id, } std::expected -ProtoCodec::on_stream_close(int64_t stream_id, uint64_t app_error_code) { +ProtoCodec::on_stream_close(int64_t stream_id, + std::optional rx_app_error_code, + std::optional tx_app_error_code) { if (!ngtcp2_is_bidi_stream(stream_id)) { return {}; } if (!config.quiet) { - std::println(stderr, "HTTP stream {:#x} closed with error code {:#x}", - stream_id, app_error_code); + std::println(stderr, + "HTTP stream {:#x} closed with error codes (RX:{}, TX:{})", + stream_id, util::format_app_error_code(rx_app_error_code), + util::format_app_error_code(tx_app_error_code)); } return {}; diff --git a/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.h b/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.h index 224b8f2a7214..9e8e30b49af4 100644 --- a/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.h +++ b/deps/ngtcp2/ngtcp2/examples/hq_client_proto_codec.h @@ -32,6 +32,7 @@ #include #include #include +#include #include @@ -64,8 +65,9 @@ class ProtoCodec { void early_data_rejected() {} - std::expected on_stream_close(int64_t stream_id, - uint64_t app_error_code); + std::expected + on_stream_close(int64_t stream_id, std::optional rx_app_error_code, + std::optional tx_app_error_code); std::expected on_stream_reset(int64_t stream_id) { return {}; } diff --git a/deps/ngtcp2/ngtcp2/examples/hq_server_proto_codec.cc b/deps/ngtcp2/ngtcp2/examples/hq_server_proto_codec.cc index 8490ca24b0eb..8ef3d5fe314c 100644 --- a/deps/ngtcp2/ngtcp2/examples/hq_server_proto_codec.cc +++ b/deps/ngtcp2/ngtcp2/examples/hq_server_proto_codec.cc @@ -60,7 +60,7 @@ namespace { int on_msg_complete(http_parser *htp) { auto s = static_cast(htp->data); s->eos = true; - s->start_response(); + (void)s->start_response(); return 0; } } // namespace @@ -184,7 +184,9 @@ ngtcp2_ssize ProtoCodec::write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, } std::expected -ProtoCodec::on_stream_close(int64_t stream_id, uint64_t app_error_code) { +ProtoCodec::on_stream_close(int64_t stream_id, + std::optional rx_app_error_code, + std::optional tx_app_error_code) { auto stream = handler_->find_stream(stream_id); if (!stream) { return {}; @@ -193,23 +195,23 @@ ProtoCodec::on_stream_close(int64_t stream_id, uint64_t app_error_code) { sendq_.erase(stream); if (!config.quiet) { - std::println(stderr, "HTTP stream {:#x} closed with error code {:#x}", - stream_id, app_error_code); + std::println(stderr, + "HTTP stream {:#x} closed with error codes (RX:{}, TX:{})", + stream_id, util::format_app_error_code(rx_app_error_code), + util::format_app_error_code(tx_app_error_code)); } return {}; } -std::expected -ProtoCodec::send_status_response(Stream *stream, unsigned int status_code) { +void ProtoCodec::send_status_response(Stream *stream, + unsigned int status_code) { stream->status_resp_body = make_status_body(status_code); stream->resp_data = as_uint8_span(std::span{stream->status_resp_body}); sendq_.emplace(stream); handler_->shutdown_read(stream->stream_id, 0); - - return {}; } std::expected ProtoCodec::start_response(Stream *stream) { diff --git a/deps/ngtcp2/ngtcp2/examples/hq_server_proto_codec.h b/deps/ngtcp2/ngtcp2/examples/hq_server_proto_codec.h index a60d616e3da2..a358d24cec68 100644 --- a/deps/ngtcp2/ngtcp2/examples/hq_server_proto_codec.h +++ b/deps/ngtcp2/ngtcp2/examples/hq_server_proto_codec.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -76,16 +77,16 @@ class ProtoCodec { std::expected recv_stream_data(uint32_t flags, int64_t stream_id, std::span data); - std::expected on_stream_close(int64_t stream_id, - uint64_t app_error_code); + std::expected + on_stream_close(int64_t stream_id, std::optional rx_app_error_code, + std::optional tx_app_error_code); std::expected start_response(Stream *stream); static constexpr auto protocol = AppProtocol::HQ; private: - std::expected send_status_response(Stream *stream, - unsigned int status_code); + void send_status_response(Stream *stream, unsigned int status_code); Handler *handler_; ngtcp2_conn *conn_; diff --git a/deps/ngtcp2/ngtcp2/examples/http3_client_proto_codec.cc b/deps/ngtcp2/ngtcp2/examples/http3_client_proto_codec.cc index 82e33acffb19..8a2674796e87 100644 --- a/deps/ngtcp2/ngtcp2/examples/http3_client_proto_codec.cc +++ b/deps/ngtcp2/ngtcp2/examples/http3_client_proto_codec.cc @@ -92,31 +92,45 @@ void ProtoCodec::early_data_rejected() { httpconn_ = nullptr; } -void ProtoCodec::http_stream_close(int64_t stream_id, uint64_t app_error_code) { +void ProtoCodec::http_stream_close(int64_t stream_id, + std::optional rx_app_error_code, + std::optional tx_app_error_code) { if (!ngtcp2_is_bidi_stream(stream_id)) { return; } if (!config.quiet) { - std::println(stderr, "HTTP stream {:#x} closed with error code {:#x}", - stream_id, app_error_code); + std::println(stderr, + "HTTP stream {:#x} closed with error codes (RX:{}, TX:{})", + stream_id, util::format_app_error_code(rx_app_error_code), + util::format_app_error_code(tx_app_error_code)); } } std::expected -ProtoCodec::on_stream_close(int64_t stream_id, uint64_t app_error_code) { +ProtoCodec::on_stream_close(int64_t stream_id, + std::optional rx_app_error_code, + std::optional tx_app_error_code) { if (!httpconn_) { return {}; } - if (app_error_code == 0) { - app_error_code = NGHTTP3_H3_NO_ERROR; + uint32_t flags = NGHTTP3_STREAM_CLOSE_FLAG_NONE; + + if (rx_app_error_code.has_value()) { + flags |= NGHTTP3_STREAM_CLOSE_FLAG_RX_APP_ERROR_CODE_SET; + } + + if (tx_app_error_code.has_value()) { + flags |= NGHTTP3_STREAM_CLOSE_FLAG_TX_APP_ERROR_CODE_SET; } - if (auto rv = nghttp3_conn_close_stream(httpconn_, stream_id, app_error_code); + if (auto rv = nghttp3_conn_close_stream2(httpconn_, flags, stream_id, + rx_app_error_code.value_or(0), + tx_app_error_code.value_or(0)); rv != 0) { if (rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - std::println(stderr, "nghttp3_conn_close_stream: {}", + std::println(stderr, "nghttp3_conn_close_stream2: {}", nghttp3_strerror(rv)); ngtcp2_ccerr_set_application_error( &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, 0); @@ -126,7 +140,7 @@ ProtoCodec::on_stream_close(int64_t stream_id, uint64_t app_error_code) { return {}; } - http_stream_close(stream_id, app_error_code); + http_stream_close(stream_id, rx_app_error_code, tx_app_error_code); return {}; } diff --git a/deps/ngtcp2/ngtcp2/examples/http3_client_proto_codec.h b/deps/ngtcp2/ngtcp2/examples/http3_client_proto_codec.h index 2655e1bf8072..ca7b3af288ae 100644 --- a/deps/ngtcp2/ngtcp2/examples/http3_client_proto_codec.h +++ b/deps/ngtcp2/ngtcp2/examples/http3_client_proto_codec.h @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -59,8 +60,9 @@ class ProtoCodec { void early_data_rejected(); - std::expected on_stream_close(int64_t stream_id, - uint64_t app_error_code); + std::expected + on_stream_close(int64_t stream_id, std::optional rx_app_error_code, + std::optional tx_app_error_code); std::expected on_stream_reset(int64_t stream_id); @@ -89,7 +91,9 @@ class ProtoCodec { static constexpr auto no_error = NGHTTP3_H3_NO_ERROR; private: - void http_stream_close(int64_t stream_id, uint64_t app_error_code); + void http_stream_close(int64_t stream_id, + std::optional rx_app_error_code, + std::optional tx_app_error_code); Client *client_; ngtcp2_conn *conn_; diff --git a/deps/ngtcp2/ngtcp2/examples/http3_server_proto_codec.cc b/deps/ngtcp2/ngtcp2/examples/http3_server_proto_codec.cc index ac0b6132ef0b..b3932116d4e0 100644 --- a/deps/ngtcp2/ngtcp2/examples/http3_server_proto_codec.cc +++ b/deps/ngtcp2/ngtcp2/examples/http3_server_proto_codec.cc @@ -228,19 +228,29 @@ ProtoCodec::recv_stream_data(uint32_t flags, int64_t stream_id, } std::expected -ProtoCodec::on_stream_close(int64_t stream_id, uint64_t app_error_code) { +ProtoCodec::on_stream_close(int64_t stream_id, + std::optional rx_app_error_code, + std::optional tx_app_error_code) { if (!httpconn_) { return {}; } - if (app_error_code == 0) { - app_error_code = NGHTTP3_H3_NO_ERROR; + uint32_t flags = NGHTTP3_STREAM_CLOSE_FLAG_NONE; + + if (rx_app_error_code.has_value()) { + flags |= NGHTTP3_STREAM_CLOSE_FLAG_RX_APP_ERROR_CODE_SET; + } + + if (tx_app_error_code.has_value()) { + flags |= NGHTTP3_STREAM_CLOSE_FLAG_TX_APP_ERROR_CODE_SET; } - if (auto rv = nghttp3_conn_close_stream(httpconn_, stream_id, app_error_code); + if (auto rv = nghttp3_conn_close_stream2(httpconn_, flags, stream_id, + rx_app_error_code.value_or(0), + tx_app_error_code.value_or(0)); rv != 0) { if (rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - std::println(stderr, "nghttp3_conn_close_stream: {}", + std::println(stderr, "nghttp3_conn_close_stream2: {}", nghttp3_strerror(rv)); ngtcp2_ccerr_set_application_error( &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, 0); @@ -250,7 +260,7 @@ ProtoCodec::on_stream_close(int64_t stream_id, uint64_t app_error_code) { return {}; } - http_stream_close(stream_id, app_error_code); + http_stream_close(stream_id, rx_app_error_code, tx_app_error_code); return {}; } @@ -830,14 +840,18 @@ std::expected ProtoCodec::setup_httpconn() { return {}; } -void ProtoCodec::http_stream_close(int64_t stream_id, uint64_t app_error_code) { +void ProtoCodec::http_stream_close(int64_t stream_id, + std::optional rx_app_error_code, + std::optional tx_app_error_code) { if (!ngtcp2_is_bidi_stream(stream_id)) { return; } if (!config.quiet) { - std::println(stderr, "HTTP stream {:#x} closed with error code {:#x}", - stream_id, app_error_code); + std::println(stderr, + "HTTP stream {:#x} closed with error codes (RX:{}, TX:{})", + stream_id, util::format_app_error_code(rx_app_error_code), + util::format_app_error_code(tx_app_error_code)); } } diff --git a/deps/ngtcp2/ngtcp2/examples/http3_server_proto_codec.h b/deps/ngtcp2/ngtcp2/examples/http3_server_proto_codec.h index 08b16b79b7ea..10a61d601593 100644 --- a/deps/ngtcp2/ngtcp2/examples/http3_server_proto_codec.h +++ b/deps/ngtcp2/ngtcp2/examples/http3_server_proto_codec.h @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -68,8 +69,9 @@ class ProtoCodec { std::expected recv_stream_data(uint32_t flags, int64_t stream_id, std::span data); - std::expected on_stream_close(int64_t stream_id, - uint64_t app_error_code); + std::expected + on_stream_close(int64_t stream_id, std::optional rx_app_error_code, + std::optional tx_app_error_code); std::expected start_response(Stream *stream); @@ -107,7 +109,9 @@ class ProtoCodec { std::expected setup_httpconn(); - void http_stream_close(int64_t stream_id, uint64_t app_error_code); + void http_stream_close(int64_t stream_id, + std::optional rx_app_error_code, + std::optional tx_app_error_code); Handler *handler_; ngtcp2_conn *conn_; diff --git a/deps/ngtcp2/ngtcp2/examples/server.cc b/deps/ngtcp2/ngtcp2/examples/server.cc index 20f4a04d2150..79896b539a83 100644 --- a/deps/ngtcp2/ngtcp2/examples/server.cc +++ b/deps/ngtcp2/ngtcp2/examples/server.cc @@ -509,10 +509,17 @@ Stream *Handler::find_stream(int64_t stream_id) const { namespace { int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) { + uint64_t rx_app_error_code, uint64_t tx_app_error_code, + void *user_data, void *stream_user_data) { auto h = static_cast(user_data); - if (!h->on_stream_close(stream_id, app_error_code)) { + if (!h->on_stream_close( + stream_id, + (flags & NGTCP2_STREAM_CLOSE2_FLAG_RX_APP_ERROR_CODE_SET) + ? std::make_optional(rx_app_error_code) + : std::nullopt, + (flags & NGTCP2_STREAM_CLOSE2_FLAG_TX_APP_ERROR_CODE_SET) + ? std::make_optional(tx_app_error_code) + : std::nullopt)) { return NGTCP2_ERR_CALLBACK_FAILURE; } return 0; @@ -728,7 +735,6 @@ Handler::init(const Endpoint &ep, const Address &local_addr, .recv_stream_data = ::recv_stream_data, .acked_stream_data_offset = ::acked_stream_data_offset, .stream_open = stream_open, - .stream_close = stream_close, .rand = rand, .remove_connection_id = remove_connection_id, .update_key = ::update_key, @@ -743,6 +749,7 @@ Handler::init(const Endpoint &ep, const Address &local_addr, .recv_tx_key = ::recv_tx_key, .get_new_connection_id2 = get_new_connection_id, .get_path_challenge_data2 = ngtcp2_crypto_get_path_challenge_data2_cb, + .stream_close2 = stream_close, }; scid_.datalen = NGTCP2_SV_SCIDLEN; @@ -1028,8 +1035,8 @@ std::expected Handler::write_streams() { return {}; } - send_packet(ps.path, pi.ecn, txbuf.first(static_cast(nwrite)), - gso_size); + (void)send_packet(ps.path, pi.ecn, txbuf.first(static_cast(nwrite)), + gso_size); return {}; } @@ -1295,13 +1302,17 @@ Handler::update_key(uint8_t *rx_secret, uint8_t *tx_secret, Server *Handler::server() const { return server_; } -std::expected Handler::on_stream_close(int64_t stream_id, - uint64_t app_error_code) { +std::expected +Handler::on_stream_close(int64_t stream_id, + std::optional rx_app_error_code, + std::optional tx_app_error_code) { if (!config.quiet) { std::println(stderr, "QUIC stream {:#x} closed", stream_id); } - if (auto rv = proto_codec_->on_stream_close(stream_id, app_error_code); !rv) { + if (auto rv = proto_codec_->on_stream_close(stream_id, rx_app_error_code, + tx_app_error_code); + !rv) { return rv; } @@ -1373,7 +1384,7 @@ void Server::disconnect() { auto it = std::ranges::begin(handlers_); auto &h = (*it).second; - h->handle_error(); + (void)h->handle_error(); remove(h); } @@ -1726,9 +1737,9 @@ void Server::read_pkt(const Endpoint &ep, const Address &local_addr, case 0: break; case NGTCP2_ERR_VERSION_NEGOTIATION: - send_version_negotiation(vc.version, {vc.scid, vc.scidlen}, - {vc.dcid, vc.dcidlen}, ep, local_addr, - remote_addr); + (void)send_version_negotiation(vc.version, {vc.scid, vc.scidlen}, + {vc.dcid, vc.dcidlen}, ep, local_addr, + remote_addr); return; default: std::println(stderr, @@ -1750,8 +1761,8 @@ void Server::read_pkt(const Endpoint &ep, const Address &local_addr, } if (!(data[0] & 0x80) && data.size() >= NGTCP2_SV_SCIDLEN + 21) { - send_stateless_reset(data.size(), {vc.dcid, vc.dcidlen}, ep, local_addr, - remote_addr); + (void)send_stateless_reset(data.size(), {vc.dcid, vc.dcidlen}, ep, + local_addr, remote_addr); } return; @@ -1766,13 +1777,13 @@ void Server::read_pkt(const Endpoint &ep, const Address &local_addr, if (config.validate_addr || hd.tokenlen) { std::println(stderr, "Perform stateless address validation"); if (hd.tokenlen == 0) { - send_retry(&hd, ep, local_addr, remote_addr, data.size() * 3); + (void)send_retry(&hd, ep, local_addr, remote_addr, data.size() * 3); return; } if (hd.token[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY2 && hd.dcid.datalen < NGTCP2_MIN_INITIAL_DCIDLEN) { - send_stateless_connection_close(&hd, ep, local_addr, remote_addr); + (void)send_stateless_connection_close(&hd, ep, local_addr, remote_addr); return; } @@ -1780,7 +1791,8 @@ void Server::read_pkt(const Endpoint &ep, const Address &local_addr, case NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY2: if (auto rv = verify_retry_token(&ocid, &hd, remote_addr); !rv) { if (rv.error() != Error::UNREADABLE_TOKEN || config.validate_addr) { - send_stateless_connection_close(&hd, ep, local_addr, remote_addr); + (void)send_stateless_connection_close(&hd, ep, local_addr, + remote_addr); return; } @@ -1796,7 +1808,7 @@ void Server::read_pkt(const Endpoint &ep, const Address &local_addr, case NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR: if (!verify_token(&hd, remote_addr)) { if (config.validate_addr) { - send_retry(&hd, ep, local_addr, remote_addr, data.size() * 3); + (void)send_retry(&hd, ep, local_addr, remote_addr, data.size() * 3); return; } @@ -1811,7 +1823,7 @@ void Server::read_pkt(const Endpoint &ep, const Address &local_addr, std::println(stderr, "Ignore unrecognized token"); } if (config.validate_addr) { - send_retry(&hd, ep, local_addr, remote_addr, data.size() * 3); + (void)send_retry(&hd, ep, local_addr, remote_addr, data.size() * 3); return; } @@ -1829,7 +1841,7 @@ void Server::read_pkt(const Endpoint &ep, const Address &local_addr, if (auto rv = h->on_read(ep, local_addr, remote_addr, pi, data); !rv) { if (rv.error() == Error::RETRY_CONN) { - send_retry(&hd, ep, local_addr, remote_addr, data.size() * 3); + (void)send_retry(&hd, ep, local_addr, remote_addr, data.size() * 3); } return; @@ -1981,7 +1993,7 @@ std::expected Server::send_retry(const ngtcp2_pkt_hd *chd, if (!config.quiet) { std::println(stderr, "Generated address validation token:"); - util::hexdump(stderr, token); + (void)util::hexdump(stderr, token); } Buffer buf{ @@ -2107,7 +2119,7 @@ Server::verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, std::println(stderr, "Verifying Retry token from [{}]:{}", host.data(), port.data()); - util::hexdump(stderr, {hd->token, hd->tokenlen}); + (void)util::hexdump(stderr, {hd->token, hd->tokenlen}); } auto t = util::system_clock_now(); @@ -2153,7 +2165,7 @@ std::expected Server::verify_token(const ngtcp2_pkt_hd *hd, if (!config.quiet) { std::println(stderr, "Verifying token from [{}]:{}", host.data(), port.data()); - util::hexdump(stderr, {hd->token, hd->tokenlen}); + (void)util::hexdump(stderr, {hd->token, hd->tokenlen}); } auto t = util::system_clock_now(); diff --git a/deps/ngtcp2/ngtcp2/examples/server.h b/deps/ngtcp2/ngtcp2/examples/server.h index ea3162a47846..8a71a12a2e7f 100644 --- a/deps/ngtcp2/ngtcp2/examples/server.h +++ b/deps/ngtcp2/ngtcp2/examples/server.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -168,8 +169,9 @@ class Handler : public HandlerBase { uint64_t datalen); uint32_t version() const; void on_stream_open(int64_t stream_id); - std::expected on_stream_close(int64_t stream_id, - uint64_t app_error_code); + std::expected + on_stream_close(int64_t stream_id, std::optional rx_app_error_code, + std::optional tx_app_error_code); void start_draining_period(); std::expected start_closing_period(); std::expected handle_error(); diff --git a/deps/ngtcp2/ngtcp2/examples/util.cc b/deps/ngtcp2/ngtcp2/examples/util.cc index 8f6d7c7668f0..8baf6e978427 100644 --- a/deps/ngtcp2/ngtcp2/examples/util.cc +++ b/deps/ngtcp2/ngtcp2/examples/util.cc @@ -913,6 +913,12 @@ std::filesystem::path realpath(const std::filesystem::path &path) { return abspath; } +std::string format_app_error_code(std::optional app_error_code) { + return app_error_code + .transform([](auto &&r) { return std::format("{:#x}", r); }) + .value_or("(no error)"); +} + } // namespace util std::ostream &operator<<(std::ostream &os, const ngtcp2_cid &cid) { diff --git a/deps/ngtcp2/ngtcp2/examples/util.h b/deps/ngtcp2/ngtcp2/examples/util.h index 1fc855470dcf..40a48a8a4d6f 100644 --- a/deps/ngtcp2/ngtcp2/examples/util.h +++ b/deps/ngtcp2/ngtcp2/examples/util.h @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -596,6 +597,9 @@ get_string(std::string_view uri, const urlparse_url &u, urlparse_url_fields f) { // realpath returns the canonicalized absolute path to |path|. std::filesystem::path realpath(const std::filesystem::path &path); +// format_app_error_code formats |app_error_code|. +std::string format_app_error_code(std::optional app_error_code); + } // namespace util std::ostream &operator<<(std::ostream &os, const ngtcp2_cid &cid); diff --git a/deps/ngtcp2/ngtcp2/examples/util_test.cc b/deps/ngtcp2/ngtcp2/examples/util_test.cc index 46934221d0a7..14a7bd05d926 100644 --- a/deps/ngtcp2/ngtcp2/examples/util_test.cc +++ b/deps/ngtcp2/ngtcp2/examples/util_test.cc @@ -51,6 +51,7 @@ const MunitTest tests[]{ munit_void_test(test_util_decode_hex), munit_void_test(test_util_is_hex_string), munit_void_test(test_util_split_str), + munit_void_test(test_util_format_app_error_code), munit_test_end(), }; } // namespace @@ -622,4 +623,9 @@ void test_util_split_str() { std::ranges::to()))); } +void test_util_format_app_error_code() { + assert_stdstring_equal("(no error)", util::format_app_error_code({})); + assert_stdstring_equal("0xfe01", util::format_app_error_code(0xfe01)); +} + } // namespace ngtcp2 diff --git a/deps/ngtcp2/ngtcp2/examples/util_test.h b/deps/ngtcp2/ngtcp2/examples/util_test.h index 3546bcfc0ce6..b672211fdbf8 100644 --- a/deps/ngtcp2/ngtcp2/examples/util_test.h +++ b/deps/ngtcp2/ngtcp2/examples/util_test.h @@ -50,6 +50,7 @@ munit_void_test_decl(test_util_format_hex) munit_void_test_decl(test_util_decode_hex) munit_void_test_decl(test_util_is_hex_string) munit_void_test_decl(test_util_split_str) +munit_void_test_decl(test_util_format_app_error_code) } // namespace ngtcp2 diff --git a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h index 01f93e8c41ad..9bc9a7b512e6 100644 --- a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h +++ b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h @@ -3000,6 +3000,9 @@ typedef int (*ngtcp2_stream_open)(ngtcp2_conn *conn, int64_t stream_id, * The implementation of this callback should return 0 if it succeeds. * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library * call return immediately. + * + * .. seealso:: + * :type:`ngtcp2_stream_close2` */ typedef int (*ngtcp2_stream_close)(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, uint64_t app_error_code, @@ -3641,11 +3644,87 @@ typedef int (*ngtcp2_get_path_challenge_data2)(ngtcp2_conn *conn, ngtcp2_path_challenge_data *data, void *user_data); +/** + * @macrosection + * + * Stream close flags for :type:`ngtcp2_stream_close2` callback. + */ + +/** + * @macro + * + * :macro:`NGTCP2_STREAM_CLOSE2_FLAG_NONE` indicates no flag set. + * + * .. version-added:: 1.25.0 + */ +#define NGTCP2_STREAM_CLOSE2_FLAG_NONE 0x00U + +/** + * @macro + * + * :macro:`NGTCP2_STREAM_CLOSE2_FLAG_RX_APP_ERROR_CODE_SET` indicates + * that rx_app_error_code parameter is set. + * + * .. version-added:: 1.25.0 + */ +#define NGTCP2_STREAM_CLOSE2_FLAG_RX_APP_ERROR_CODE_SET 0x01U + +/** + * @macro + * + * :macro:`NGTCP2_STREAM_CLOSE2_FLAG_TX_APP_ERROR_CODE_SET` indicates + * that tx_app_error_code parameter is set. + * + * .. version-added:: 1.25.0 + */ +#define NGTCP2_STREAM_CLOSE2_FLAG_TX_APP_ERROR_CODE_SET 0x02U + +/** + * @functypedef + * + * :type:`ngtcp2_stream_close2` is invoked when a stream is closed. + * This callback is not called when QUIC connection is closed before + * existing streams are closed. |flags| is the bitwise-OR of zero or + * more of :macro:`NGTCP2_STREAM_CLOSE2_FLAG_* + * `. |rx_app_error_code| indicates + * the error code that shut down the receiving side of the stream if + * :macro:`NGTCP2_STREAM_CLOSE2_FLAG_RX_APP_ERROR_CODE_SET` is set in + * |flags|. |tx_app_error_code| indicates the error code that shut + * down the sending side of the stream if + * :macro:`NGTCP2_STREAM_CLOSE2_FLAG_TX_APP_ERROR_CODE_SET` is set in + * |flags|. + * + * Because QUIC can close the send and receive sides of a stream + * independently, this callback has 2 application error codes for both + * directions. No error code means that its direction of a stream is + * closed cleanly. For example, a client gets STOP_SENDING frame from + * a server, and it sends back RESET_STREAM frame with the error code + * included in STOP_SENDING frame. This error code is reported as + * |tx_app_error_code| and + * :macro:`NGTCP2_STREAM_CLOSE2_FLAG_TX_APP_ERROR_CODE_SET` is set in + * |flags|. Meanwhile, the client receives the response body without + * any error. Then + * :macro:`NGTCP2_STREAM_CLOSE2_FLAG_RX_APP_ERROR_CODE_SET` is not set + * in |flags|. + * + * The implementation of this callback should return 0 if it succeeds. + * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library + * call return immediately. + * + * .. version-added:: 1.25.0 + */ +typedef int (*ngtcp2_stream_close2)(ngtcp2_conn *conn, uint32_t flags, + int64_t stream_id, + uint64_t rx_app_error_code, + uint64_t tx_app_error_code, void *user_data, + void *stream_user_data); + #define NGTCP2_CALLBACKS_V1 1 #define NGTCP2_CALLBACKS_V2 2 #define NGTCP2_CALLBACKS_V3 3 #define NGTCP2_CALLBACKS_V4 4 -#define NGTCP2_CALLBACKS_VERSION NGTCP2_CALLBACKS_V4 +#define NGTCP2_CALLBACKS_V5 5 +#define NGTCP2_CALLBACKS_VERSION NGTCP2_CALLBACKS_V5 /** * @struct @@ -3726,6 +3805,9 @@ typedef struct ngtcp2_callbacks { /** * :member:`stream_close` is a callback function which is invoked * when a stream is closed. This callback function is optional. + * + * .. seealso:: + * :member:`stream_close2` */ ngtcp2_stream_close stream_close; /** @@ -3984,7 +4066,7 @@ typedef struct ngtcp2_callbacks { */ ngtcp2_get_path_challenge_data2 get_path_challenge_data2; /* The following fields have been added since - NGTCP2_CALLBACKS_V3. */ + NGTCP2_CALLBACKS_V4. */ /** * :member:`recv_stop_sending` is a callback function which is invoked * when a STOP_SENDING frame is received from a remote endpoint. This @@ -3993,6 +4075,17 @@ typedef struct ngtcp2_callbacks { * .. version-added:: 1.24.0 */ ngtcp2_recv_stop_sending recv_stop_sending; + /* The following fields have been added since + NGTCP2_CALLBACKS_V5. */ + /** + * :member:`stream_close2` is a callback function which is invoked + * when a stream is closed. This callback function is optional. If + * both this field and :member:`stream_close` are specified, this + * field takes precedence. + * + * .. version-added:: 1.25.0 + */ + ngtcp2_stream_close2 stream_close2; } ngtcp2_callbacks; /** diff --git a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h index a71100abbaf5..64e8d705fa17 100644 --- a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h +++ b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h @@ -36,7 +36,7 @@ * * Version number of the ngtcp2 library release. */ -#define NGTCP2_VERSION "1.24.0" +#define NGTCP2_VERSION "1.25.0" /** * @macro @@ -46,6 +46,6 @@ * number, 8 bits for minor and 8 bits for patch. Version 1.2.3 * becomes 0x010203. */ -#define NGTCP2_VERSION_NUM 0x011800 +#define NGTCP2_VERSION_NUM 0x011900 #endif /* !defined(NGTCP2_VERSION_H) */ diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.c index 2f194ab13105..9aa976b6747e 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.c @@ -30,44 +30,22 @@ #include "ngtcp2_macro.h" #include "ngtcp2_tstamp.h" -ngtcp2_objalloc_def(acktr_entry, ngtcp2_acktr_entry, oplent) +static int pkt_range_greater(const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs) { + const ngtcp2_pkt_range *a = lhs; + const ngtcp2_pkt_range *b = rhs; -static void acktr_entry_init(ngtcp2_acktr_entry *ent, int64_t pkt_num, - ngtcp2_tstamp tstamp) { - *ent = (ngtcp2_acktr_entry){ - .pkt_num = pkt_num, - .len = 1, - .tstamp = tstamp, - }; + return a->pkt_num > b->pkt_num; } -int ngtcp2_acktr_entry_objalloc_new(ngtcp2_acktr_entry **ent, int64_t pkt_num, - ngtcp2_tstamp tstamp, - ngtcp2_objalloc *objalloc) { - *ent = ngtcp2_objalloc_acktr_entry_get(objalloc); - if (*ent == NULL) { - return NGTCP2_ERR_NOMEM; - } - - acktr_entry_init(*ent, pkt_num, tstamp); - - return 0; -} - -void ngtcp2_acktr_entry_objalloc_del(ngtcp2_acktr_entry *ent, - ngtcp2_objalloc *objalloc) { - ngtcp2_objalloc_acktr_entry_release(objalloc, ent); -} +ngtcp2_ksl_search_def(pkt_range_greater, pkt_range_greater) void ngtcp2_acktr_init(ngtcp2_acktr *acktr, ngtcp2_log *log, const ngtcp2_mem *mem) { - ngtcp2_objalloc_acktr_entry_init(&acktr->objalloc, NGTCP2_ACKTR_MAX_ENT + 1, - mem); - ngtcp2_static_ringbuf_acks_init(&acktr->acks); - ngtcp2_ksl_init(&acktr->ents, ngtcp2_ksl_int64_greater, - ngtcp2_ksl_int64_greater_search, sizeof(int64_t), mem); + ngtcp2_ksl_init(&acktr->ents, pkt_range_greater, ksl_pkt_range_greater_search, + sizeof(ngtcp2_pkt_range), mem); acktr->log = log; acktr->flags = NGTCP2_ACKTR_FLAG_NONE; @@ -79,38 +57,30 @@ void ngtcp2_acktr_init(ngtcp2_acktr *acktr, ngtcp2_log *log, } void ngtcp2_acktr_free(ngtcp2_acktr *acktr) { -#ifdef NOMEMPOOL - ngtcp2_ksl_it it; -#endif /* defined(NOMEMPOOL) */ - if (acktr == NULL) { return; } -#ifdef NOMEMPOOL - for (it = ngtcp2_ksl_begin(&acktr->ents); !ngtcp2_ksl_it_end(&it); - ngtcp2_ksl_it_next(&it)) { - ngtcp2_acktr_entry_objalloc_del(ngtcp2_ksl_it_get(&it), &acktr->objalloc); - } -#endif /* defined(NOMEMPOOL) */ - ngtcp2_ksl_free(&acktr->ents); - - ngtcp2_objalloc_free(&acktr->objalloc); } int ngtcp2_acktr_add(ngtcp2_acktr *acktr, int64_t pkt_num, int active_ack, ngtcp2_tstamp ts) { ngtcp2_ksl_it it, prev_it; - ngtcp2_acktr_entry *ent, *prev_ent, *delent; + ngtcp2_pkt_range *ent, *prev_ent; + ngtcp2_pkt_range key = { + .pkt_num = pkt_num, + .len = 1, + }; + ngtcp2_pkt_range old_key; int rv; int added = 0; if (ngtcp2_ksl_len(&acktr->ents)) { - it = ngtcp2_ksl_lower_bound(&acktr->ents, &pkt_num); + it = ngtcp2_ksl_lower_bound(&acktr->ents, &key); if (ngtcp2_ksl_it_end(&it)) { ngtcp2_ksl_it_prev(&it); - ent = ngtcp2_ksl_it_get(&it); + ent = (ngtcp2_pkt_range *)ngtcp2_ksl_it_key(&it); assert(ent->pkt_num >= pkt_num + (int64_t)ent->len); @@ -119,36 +89,37 @@ int ngtcp2_acktr_add(ngtcp2_acktr *acktr, int64_t pkt_num, int active_ack, added = 1; } } else { - ent = ngtcp2_ksl_it_get(&it); + ent = (ngtcp2_pkt_range *)ngtcp2_ksl_it_key(&it); assert(ent->pkt_num != pkt_num); if (ngtcp2_ksl_it_begin(&it)) { if (ent->pkt_num + 1 == pkt_num) { - ngtcp2_ksl_update_key(&acktr->ents, &ent->pkt_num, &pkt_num); - ent->pkt_num = pkt_num; - ent->tstamp = ts; - ++ent->len; + old_key = *ent; + key.len = ent->len + 1; + + ngtcp2_ksl_update_key(&acktr->ents, &old_key, &key); added = 1; } } else { prev_it = it; ngtcp2_ksl_it_prev(&prev_it); - prev_ent = ngtcp2_ksl_it_get(&prev_it); + prev_ent = (ngtcp2_pkt_range *)ngtcp2_ksl_it_key(&prev_it); assert(prev_ent->pkt_num >= pkt_num + (int64_t)prev_ent->len); if (ent->pkt_num + 1 == pkt_num) { if (prev_ent->pkt_num == pkt_num + (int64_t)prev_ent->len) { prev_ent->len += ent->len + 1; - ngtcp2_ksl_remove_hint(&acktr->ents, NULL, &it, &ent->pkt_num); - ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc); + old_key = *ent; + + ngtcp2_ksl_remove_hint(&acktr->ents, NULL, &it, &old_key); added = 1; } else { - ngtcp2_ksl_update_key(&acktr->ents, &ent->pkt_num, &pkt_num); - ent->pkt_num = pkt_num; - ent->tstamp = ts; - ++ent->len; + old_key = *ent; + key.len = ent->len + 1; + + ngtcp2_ksl_update_key(&acktr->ents, &old_key, &key); added = 1; } } else if (prev_ent->pkt_num == pkt_num + (int64_t)prev_ent->len) { @@ -160,13 +131,8 @@ int ngtcp2_acktr_add(ngtcp2_acktr *acktr, int64_t pkt_num, int active_ack, } if (!added) { - rv = ngtcp2_acktr_entry_objalloc_new(&ent, pkt_num, ts, &acktr->objalloc); - if (rv != 0) { - return rv; - } - rv = ngtcp2_ksl_insert(&acktr->ents, NULL, &ent->pkt_num, ent); + rv = ngtcp2_ksl_insert(&acktr->ents, NULL, &key, NULL); if (rv != 0) { - ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc); return rv; } } @@ -181,9 +147,8 @@ int ngtcp2_acktr_add(ngtcp2_acktr *acktr, int64_t pkt_num, int active_ack, if (ngtcp2_ksl_len(&acktr->ents) > NGTCP2_ACKTR_MAX_ENT) { it = ngtcp2_ksl_end(&acktr->ents); ngtcp2_ksl_it_prev(&it); - delent = ngtcp2_ksl_it_get(&it); - ngtcp2_ksl_remove_hint(&acktr->ents, NULL, &it, &delent->pkt_num); - ngtcp2_acktr_entry_objalloc_del(delent, &acktr->objalloc); + old_key = *(const ngtcp2_pkt_range *)ngtcp2_ksl_it_key(&it); + ngtcp2_ksl_remove_hint(&acktr->ents, NULL, &it, &old_key); } if (acktr->max_pkt_num < pkt_num) { @@ -194,16 +159,19 @@ int ngtcp2_acktr_add(ngtcp2_acktr *acktr, int64_t pkt_num, int active_ack, return 0; } -void ngtcp2_acktr_forget(ngtcp2_acktr *acktr, ngtcp2_acktr_entry *ent) { +void ngtcp2_acktr_forget(ngtcp2_acktr *acktr, int64_t pkt_num) { ngtcp2_ksl_it it; + ngtcp2_pkt_range key = { + .pkt_num = pkt_num, + }; - it = ngtcp2_ksl_lower_bound(&acktr->ents, &ent->pkt_num); - assert(*(int64_t *)ngtcp2_ksl_it_key(&it) == (int64_t)ent->pkt_num); + it = ngtcp2_ksl_lower_bound(&acktr->ents, &key); + assert(pkt_num == + ((const ngtcp2_pkt_range *)ngtcp2_ksl_it_key(&it))->pkt_num); for (; !ngtcp2_ksl_it_end(&it);) { - ent = ngtcp2_ksl_it_get(&it); - ngtcp2_ksl_remove_hint(&acktr->ents, &it, &it, &ent->pkt_num); - ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc); + key = *(const ngtcp2_pkt_range *)ngtcp2_ksl_it_key(&it); + ngtcp2_ksl_remove_hint(&acktr->ents, &it, &it, &key); } } @@ -229,39 +197,33 @@ ngtcp2_acktr_ack_entry *ngtcp2_acktr_add_ack(ngtcp2_acktr *acktr, return ent; } -/* - * acktr_remove removes |ent| from |acktr|. |it| must point to the - * node whose key identifies |ent|. The iterator which points to the - * entry next to |ent| is assigned to |it|. - */ -static void acktr_remove(ngtcp2_acktr *acktr, ngtcp2_ksl_it *it, - ngtcp2_acktr_entry *ent) { - ngtcp2_ksl_remove_hint(&acktr->ents, it, it, &ent->pkt_num); - ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc); -} - static void acktr_on_ack(ngtcp2_acktr *acktr, ngtcp2_ringbuf *rb, size_t ack_ent_offset) { ngtcp2_acktr_ack_entry *ack_ent; - ngtcp2_acktr_entry *ent; + ngtcp2_pkt_range *ent; ngtcp2_ksl_it it; + ngtcp2_pkt_range key; assert(ngtcp2_ringbuf_len(rb)); ack_ent = ngtcp2_ringbuf_get(rb, ack_ent_offset); + key = (ngtcp2_pkt_range){ + .pkt_num = ack_ent->largest_ack, + }; + /* Assume that ngtcp2_pkt_validate_ack(fr) returns 0 */ - it = ngtcp2_ksl_lower_bound(&acktr->ents, &ack_ent->largest_ack); + it = ngtcp2_ksl_lower_bound(&acktr->ents, &key); for (; !ngtcp2_ksl_it_end(&it);) { - ent = ngtcp2_ksl_it_get(&it); - acktr_remove(acktr, &it, ent); + key = *(const ngtcp2_pkt_range *)ngtcp2_ksl_it_key(&it); + ngtcp2_ksl_remove_hint(&acktr->ents, &it, &it, &key); } if (ngtcp2_ksl_len(&acktr->ents)) { assert(ngtcp2_ksl_it_end(&it)); ngtcp2_ksl_it_prev(&it); - ent = ngtcp2_ksl_it_get(&it); + ent = (ngtcp2_pkt_range *)ngtcp2_ksl_it_key(&it); assert(ent->pkt_num > ack_ent->largest_ack); @@ -344,8 +306,7 @@ int ngtcp2_acktr_create_ack_frame(ngtcp2_acktr *acktr, ngtcp2_ack *ack, int64_t last_pkt_num; ngtcp2_ack_range *range; ngtcp2_ksl_it it; - ngtcp2_acktr_entry *rpkt; - ngtcp2_tstamp largest_ack_ts; + const ngtcp2_pkt_range *rpkt; size_t num_acks; if (acktr->flags & NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK) { @@ -374,11 +335,10 @@ int ngtcp2_acktr_create_ack_frame(ngtcp2_acktr *acktr, ngtcp2_ack *ack, } ack->rangecnt = 0; - rpkt = ngtcp2_ksl_it_get(&it); + rpkt = ngtcp2_ksl_it_key(&it); if (rpkt->pkt_num == acktr->max_pkt_num) { last_pkt_num = rpkt->pkt_num - (int64_t)(rpkt->len - 1); - largest_ack_ts = rpkt->tstamp; ack->largest_ack = rpkt->pkt_num; ack->first_ack_range = rpkt->len - 1; @@ -386,7 +346,6 @@ int ngtcp2_acktr_create_ack_frame(ngtcp2_acktr *acktr, ngtcp2_ack *ack, --num_acks; } else if (rpkt->pkt_num + 1 == acktr->max_pkt_num) { last_pkt_num = rpkt->pkt_num - (int64_t)(rpkt->len - 1); - largest_ack_ts = acktr->max_pkt_ts; ack->largest_ack = acktr->max_pkt_num; ack->first_ack_range = rpkt->len; @@ -396,13 +355,12 @@ int ngtcp2_acktr_create_ack_frame(ngtcp2_acktr *acktr, ngtcp2_ack *ack, assert(rpkt->pkt_num < acktr->max_pkt_num); last_pkt_num = acktr->max_pkt_num; - largest_ack_ts = acktr->max_pkt_ts; ack->largest_ack = acktr->max_pkt_num; ack->first_ack_range = 0; } if (type == NGTCP2_PKT_1RTT) { - ack->ack_delay_unscaled = ts - largest_ack_ts; + ack->ack_delay_unscaled = ts - acktr->max_pkt_ts; ack->ack_delay = ack->ack_delay_unscaled / NGTCP2_MICROSECONDS / (1ULL << ack_delay_exponent); } else { @@ -413,7 +371,7 @@ int ngtcp2_acktr_create_ack_frame(ngtcp2_acktr *acktr, ngtcp2_ack *ack, num_acks = ngtcp2_min(num_acks, NGTCP2_MAX_ACK_RANGES); for (; ack->rangecnt < num_acks; ngtcp2_ksl_it_next(&it)) { - rpkt = ngtcp2_ksl_it_get(&it); + rpkt = ngtcp2_ksl_it_key(&it); range = &ack->ranges[ack->rangecnt++]; range->gap = (uint64_t)(last_pkt_num - rpkt->pkt_num - 2); diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.h index 026c2cabe873..6c0e7ccf0d12 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.h @@ -35,59 +35,17 @@ #include "ngtcp2_ringbuf.h" #include "ngtcp2_ksl.h" #include "ngtcp2_pkt.h" -#include "ngtcp2_objalloc.h" -/* NGTCP2_ACKTR_MAX_ENT is the maximum number of ngtcp2_acktr_entry +/* NGTCP2_ACKTR_MAX_ENT is the maximum number of ngtcp2_pkt_range which ngtcp2_acktr stores. */ #define NGTCP2_ACKTR_MAX_ENT (NGTCP2_MAX_ACK_RANGES + 1) typedef struct ngtcp2_log ngtcp2_log; -/* - * ngtcp2_acktr_entry is a range of packets which need to be acked. - */ -typedef struct ngtcp2_acktr_entry { - union { - struct { - /* pkt_num is the largest packet number to acknowledge in this - range. */ - int64_t pkt_num; - /* len is the consecutive packets started from pkt_num which - includes pkt_num itself counting in decreasing order. So pkt_num - = 987 and len = 2, this entry includes packet 987 and 986. */ - size_t len; - /* tstamp is the timestamp when a packet denoted by pkt_num is - received. */ - ngtcp2_tstamp tstamp; - }; - - ngtcp2_opl_entry oplent; - }; -} ngtcp2_acktr_entry; - -ngtcp2_objalloc_decl(acktr_entry, ngtcp2_acktr_entry, oplent) - -/* - * ngtcp2_acktr_entry_objalloc_new allocates memory for ent, and - * initializes it with the given parameters. The pointer to the - * allocated object is stored to |*ent|. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGTCP2_ERR_NOMEM - * Out of memory. - */ -int ngtcp2_acktr_entry_objalloc_new(ngtcp2_acktr_entry **ent, int64_t pkt_num, - ngtcp2_tstamp tstamp, - ngtcp2_objalloc *objalloc); - -/* - * ngtcp2_acktr_entry_objalloc_del deallocates memory allocated for - * |ent|. - */ -void ngtcp2_acktr_entry_objalloc_del(ngtcp2_acktr_entry *ent, - ngtcp2_objalloc *objalloc); +typedef struct ngtcp2_pkt_range { + int64_t pkt_num; + size_t len; +} ngtcp2_pkt_range; typedef struct ngtcp2_acktr_ack_entry { /* largest_ack is the largest packet number in outgoing ACK frame */ @@ -114,14 +72,13 @@ ngtcp2_static_ringbuf_def(acks, 32, sizeof(ngtcp2_acktr_ack_entry)) * ngtcp2_acktr tracks received packets which we have to send ack. */ typedef struct ngtcp2_acktr { - ngtcp2_objalloc objalloc; ngtcp2_static_ringbuf_acks acks; - /* ents includes ngtcp2_acktr_entry sorted by decreasing order of - packet number. */ + /* ents includes ngtcp2_pkt_range as key sorted by decreasing order + of packet number. */ ngtcp2_ksl ents; ngtcp2_log *log; - /* first_unacked_ts is timestamp when ngtcp2_acktr_entry is added - first time after the last outgoing ACK frame. */ + /* first_unacked_ts is timestamp when a packet to acknowledge is + added first time after the last outgoing ACK frame. */ ngtcp2_tstamp first_unacked_ts; /* rx_npkt is the number of ACK eliciting packets received without sending ACK. */ @@ -158,8 +115,7 @@ void ngtcp2_acktr_init(ngtcp2_acktr *acktr, ngtcp2_log *log, const ngtcp2_mem *mem); /* - * ngtcp2_acktr_free frees resources allocated for |acktr|. It frees - * any ngtcp2_acktr_entry added to |acktr|. + * ngtcp2_acktr_free frees resources allocated for |acktr|. */ void ngtcp2_acktr_free(ngtcp2_acktr *acktr); @@ -180,10 +136,10 @@ int ngtcp2_acktr_add(ngtcp2_acktr *acktr, int64_t pkt_num, int active_ack, /* * ngtcp2_acktr_forget removes all entries which have the packet - * number that is equal to or less than ent->pkt_num. This function - * assumes that |acktr| includes |ent|. + * number that is equal to or less than |pkt_num|. This function + * assumes that |acktr| includes the entry whose key is |pkt_num|. */ -void ngtcp2_acktr_forget(ngtcp2_acktr *acktr, ngtcp2_acktr_entry *ent); +void ngtcp2_acktr_forget(ngtcp2_acktr *acktr, int64_t pkt_num); /* * ngtcp2_acktr_get returns the iterator to pointer to the entry which @@ -211,7 +167,7 @@ ngtcp2_acktr_add_ack(ngtcp2_acktr *acktr, int64_t pkt_num, int64_t largest_ack); * ngtcp2_acktr_recv_ack processes the incoming ACK frame |fr|. * |pkt_num| is a packet number which includes |fr|. If we receive * ACK which acknowledges the ACKs added by ngtcp2_acktr_add_ack, - * ngtcp2_acktr_entry which the outgoing ACK acknowledges is removed. + * ngtcp2_pkt_range which the outgoing ACK acknowledges is removed. */ void ngtcp2_acktr_recv_ack(ngtcp2_acktr *acktr, const ngtcp2_ack *fr); diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c index b4cf2b02bbf4..99512f347dc3 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c @@ -64,6 +64,8 @@ #define NGTCP2_BBR_MAX_DRAIN_ROUNDS 3 +#define NGTCP2_BBR_MAX_BDP (10 * (1ULL << 30)) + static void bbr_on_init(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, ngtcp2_tstamp initial_ts); @@ -182,9 +184,9 @@ static void bbr_probe_inflight_longterm_upward(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack); -static void bbr_adapt_longterm_model(ngtcp2_cc_bbr *bbr, - const ngtcp2_conn_stat *cstat, - const ngtcp2_cc_ack *ack); +static int bbr_adapt_longterm_model(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); static int bbr_is_time_to_probe_bw(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat, @@ -205,12 +207,13 @@ static void bbr_handle_inflight_too_high(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat, const ngtcp2_rs *rs, ngtcp2_tstamp ts); -static void bbr_note_loss(ngtcp2_cc_bbr *bbr); +static void bbr_note_loss(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat); -static void bbr_save_state_upon_loss(ngtcp2_cc_bbr *bbr); +static void bbr_save_state_upon_loss(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat); static void bbr_handle_spurious_loss_detection(ngtcp2_cc_bbr *bbr, - const ngtcp2_conn_stat *cstat); + ngtcp2_conn_stat *cstat); static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat, @@ -317,7 +320,7 @@ static void bbr_on_init(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, bbr->loss_round_start = 0; bbr->loss_round_delivered = UINT64_MAX; - bbr->rounds_since_bw_probe = 0; + bbr->rounds_since_probe_up = 0; bbr->max_bw = 0; bbr->bw = 0; @@ -331,13 +334,15 @@ static void bbr_on_init(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, bbr->offload_budget = 0; - bbr->probe_up_cnt = UINT64_MAX; + bbr->probe_up_acked_per_inc = UINT64_MAX; bbr->cycle_stamp = UINT64_MAX; - bbr->ack_phase = 0; + bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_INIT; bbr->bw_probe_wait = 0; - bbr->bw_probe_samples = 0; + bbr->is_bw_probe_sample = 0; bbr->bw_probe_up_rounds = 0; - bbr->bw_probe_up_acks = 0; + bbr->bw_probe_up_acked = 0; + bbr->prev_probe_too_high = 0; + bbr->prev_probe_precautionary = 0; bbr->inflight_longterm = UINT64_MAX; @@ -353,14 +358,14 @@ static void bbr_on_init(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, bbr->bdp = 0; bbr->drain_start_round = 0; - bbr->undo_state = 0; + bbr->undo_state = NGTCP2_BBR_STATE_NONE; bbr->undo_bw_shortterm = 0; bbr->undo_inflight_shortterm = 0; bbr->undo_inflight_longterm = 0; } static void bbr_reset_congestion_signals(ngtcp2_cc_bbr *bbr) { - bbr->loss_in_round = 0; + bbr->is_loss_in_round = 0; bbr->bw_latest = 0; bbr->inflight_latest = 0; } @@ -543,7 +548,7 @@ static void bbr_update_congestion_signals(ngtcp2_cc_bbr *bbr, bbr_adapt_lower_bounds_from_congestion(bbr, cstat); - bbr->loss_in_round = 0; + bbr->is_loss_in_round = 0; } static void @@ -553,7 +558,7 @@ bbr_adapt_lower_bounds_from_congestion(ngtcp2_cc_bbr *bbr, return; } - if (bbr->loss_in_round) { + if (bbr->is_loss_in_round) { bbr_init_lower_bounds(bbr, cstat); bbr_loss_lower_bounds(bbr); } @@ -601,7 +606,7 @@ static void bbr_update_round(ngtcp2_cc_bbr *bbr, const ngtcp2_cc_ack *ack) { bbr_start_round(bbr); ++bbr->round_count; - ++bbr->rounds_since_bw_probe; + ++bbr->rounds_since_probe_up; bbr->round_start = 1; bbr->bytes_lost_in_round = 0; @@ -705,7 +710,7 @@ static void bbr_start_probe_bw_down(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts) { bbr_reset_congestion_signals(bbr); - bbr->probe_up_cnt = UINT64_MAX; + bbr->probe_up_acked_per_inc = UINT64_MAX; bbr_pick_probe_wait(bbr); @@ -735,7 +740,8 @@ static void bbr_start_probe_bw_refill(ngtcp2_cc_bbr *bbr) { bbr_reset_shortterm_model(bbr); bbr->bw_probe_up_rounds = 0; - bbr->bw_probe_up_acks = 0; + bbr->bw_probe_up_acked = 0; + bbr->prev_probe_precautionary = 0; bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_REFILLING; bbr_start_round(bbr); @@ -770,7 +776,9 @@ static void bbr_update_probe_bw_cycle_phase(ngtcp2_cc_bbr *bbr, return; } - bbr_adapt_longterm_model(bbr, cstat, ack); + if (bbr_adapt_longterm_model(bbr, cstat, ack)) { + return; + } if (!bbr_is_in_probe_bw_state(bbr)) { return; @@ -795,13 +803,14 @@ static void bbr_update_probe_bw_cycle_phase(ngtcp2_cc_bbr *bbr, break; case NGTCP2_BBR_STATE_PROBE_BW_REFILL: if (bbr->round_start) { - bbr->bw_probe_samples = 1; + bbr->is_bw_probe_sample = 1; bbr_start_probe_bw_up(bbr, cstat); } break; case NGTCP2_BBR_STATE_PROBE_BW_UP: if (bbr_is_time_to_go_down(bbr, cstat)) { + bbr->prev_probe_too_high = 0; bbr_start_probe_bw_down(bbr, ts); } @@ -821,6 +830,13 @@ static int bbr_is_time_to_cruise(ngtcp2_cc_bbr *bbr, static int bbr_is_time_to_go_down(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat) { + if (bbr->prev_probe_too_high && + cstat->bytes_in_flight >= bbr->inflight_longterm) { + bbr->prev_probe_precautionary = 1; + + return 1; + } + if (bbr->rst->is_cwnd_limited && cstat->cwnd >= bbr->inflight_longterm) { bbr_reset_full_bw(bbr); bbr->full_bw = cstat->delivery_rate_sec; @@ -859,11 +875,11 @@ static uint64_t bbr_inflight_with_headroom(const ngtcp2_cc_bbr *bbr, static void bbr_raise_inflight_longterm_slope(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat) { - uint64_t growth_this_round = cstat->max_tx_udp_payload_size - << bbr->bw_probe_up_rounds; + uint64_t growth_this_round = 1ULL << bbr->bw_probe_up_rounds; bbr->bw_probe_up_rounds = ngtcp2_min(bbr->bw_probe_up_rounds + 1, 30); - bbr->probe_up_cnt = ngtcp2_max(cstat->cwnd / growth_this_round, 1); + bbr->probe_up_acked_per_inc = + ngtcp2_max(cstat->cwnd / growth_this_round, cstat->max_tx_udp_payload_size); } static void bbr_probe_inflight_longterm_upward(ngtcp2_cc_bbr *bbr, @@ -875,14 +891,12 @@ static void bbr_probe_inflight_longterm_upward(ngtcp2_cc_bbr *bbr, return; } - bbr->bw_probe_up_acks += ack->bytes_delivered; + bbr->bw_probe_up_acked += ack->bytes_delivered; - if (bbr->probe_up_cnt != UINT64_MAX && - bbr->bw_probe_up_acks >= - bbr->probe_up_cnt * cstat->max_tx_udp_payload_size) { - delta = bbr->bw_probe_up_acks / bbr->probe_up_cnt; - bbr->bw_probe_up_acks -= delta * bbr->probe_up_cnt; - bbr->inflight_longterm += delta; + if (bbr->bw_probe_up_acked >= bbr->probe_up_acked_per_inc) { + delta = bbr->bw_probe_up_acked / bbr->probe_up_acked_per_inc; + bbr->bw_probe_up_acked -= delta * bbr->probe_up_acked_per_inc; + bbr->inflight_longterm += delta * cstat->max_tx_udp_payload_size; } if (bbr->round_start) { @@ -890,9 +904,9 @@ static void bbr_probe_inflight_longterm_upward(ngtcp2_cc_bbr *bbr, } } -static void bbr_adapt_longterm_model(ngtcp2_cc_bbr *bbr, - const ngtcp2_conn_stat *cstat, - const ngtcp2_cc_ack *ack) { +static int bbr_adapt_longterm_model(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { if (bbr->ack_phase == NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STARTING && bbr->round_start) { bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_FEEDBACK; @@ -900,14 +914,24 @@ static void bbr_adapt_longterm_model(ngtcp2_cc_bbr *bbr, if (bbr->ack_phase == NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STOPPING && bbr->round_start) { + bbr->is_bw_probe_sample = 0; + bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_INIT; + if (bbr_is_in_probe_bw_state(bbr) && !bbr->rst->rs.is_app_limited) { bbr_advance_max_bw_filter(bbr); } + + if (bbr_is_in_probe_bw_state(bbr) && bbr->prev_probe_precautionary && + !bbr->prev_probe_too_high) { + bbr_start_probe_bw_refill(bbr); + + return 1; + } } if (!bbr_is_inflight_too_high(bbr, &bbr->rst->rs)) { if (bbr->inflight_longterm == UINT64_MAX) { - return; + return 0; } if (bbr->rst->rs.tx_in_flight > bbr->inflight_longterm) { @@ -918,6 +942,8 @@ static void bbr_adapt_longterm_model(ngtcp2_cc_bbr *bbr, bbr_probe_inflight_longterm_upward(bbr, cstat, ack); } } + + return 0; } static int bbr_is_time_to_probe_bw(ngtcp2_cc_bbr *bbr, @@ -934,7 +960,7 @@ static int bbr_is_time_to_probe_bw(ngtcp2_cc_bbr *bbr, } static void bbr_pick_probe_wait(ngtcp2_cc_bbr *bbr) { - bbr->rounds_since_bw_probe = ngtcp2_pcg32_rand_n(bbr->pcg, 2); + bbr->rounds_since_probe_up = ngtcp2_pcg32_rand_n(bbr->pcg, 2); bbr->bw_probe_wait = 2 * NGTCP2_SECONDS + ngtcp2_pcg32_rand_n(bbr->pcg, NGTCP2_SECONDS + 1); } @@ -944,7 +970,7 @@ static int bbr_is_reno_coexistence_probe_time(const ngtcp2_cc_bbr *bbr, uint64_t reno_rounds = bbr_target_inflight(bbr, cstat) / cstat->max_tx_udp_payload_size; - return bbr->rounds_since_bw_probe >= ngtcp2_min(reno_rounds, 63); + return bbr->rounds_since_probe_up >= ngtcp2_min(reno_rounds, 63); } static uint64_t bbr_target_inflight(const ngtcp2_cc_bbr *bbr, @@ -963,7 +989,8 @@ static void bbr_handle_inflight_too_high(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat, const ngtcp2_rs *rs, ngtcp2_tstamp ts) { - bbr->bw_probe_samples = 0; + bbr->prev_probe_too_high = 1; + bbr->is_bw_probe_sample = 0; if (!rs->is_app_limited) { bbr->inflight_longterm = ngtcp2_max( @@ -976,16 +1003,18 @@ static void bbr_handle_inflight_too_high(ngtcp2_cc_bbr *bbr, } } -static void bbr_note_loss(ngtcp2_cc_bbr *bbr) { - if (!bbr->loss_in_round) { +static void bbr_note_loss(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat) { + if (!bbr->is_loss_in_round) { bbr->loss_round_delivered = bbr->rst->delivered; - bbr_save_state_upon_loss(bbr); + bbr_save_state_upon_loss(bbr, cstat); } - bbr->loss_in_round = 1; + bbr->is_loss_in_round = 1; } -static void bbr_save_state_upon_loss(ngtcp2_cc_bbr *bbr) { +static void bbr_save_state_upon_loss(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat) { + bbr_save_cwnd(bbr, cstat); bbr->undo_state = bbr->state; bbr->undo_bw_shortterm = bbr->bw_shortterm; bbr->undo_inflight_shortterm = bbr->inflight_shortterm; @@ -993,8 +1022,10 @@ static void bbr_save_state_upon_loss(ngtcp2_cc_bbr *bbr) { } static void bbr_handle_spurious_loss_detection(ngtcp2_cc_bbr *bbr, - const ngtcp2_conn_stat *cstat) { - bbr->loss_in_round = 0; + ngtcp2_conn_stat *cstat) { + bbr_restore_cwnd(bbr, cstat); + + bbr->is_loss_in_round = 0; bbr_reset_full_bw(bbr); @@ -1004,19 +1035,21 @@ static void bbr_handle_spurious_loss_detection(ngtcp2_cc_bbr *bbr, bbr->inflight_longterm = ngtcp2_max(bbr->inflight_longterm, bbr->undo_inflight_longterm); - if (bbr->state != NGTCP2_BBR_STATE_PROBE_RTT && - bbr->state != bbr->undo_state) { - switch (bbr->undo_state) { - case NGTCP2_BBR_STATE_STARTUP: + if (bbr->undo_state == NGTCP2_BBR_STATE_STARTUP && + bbr->state != NGTCP2_BBR_STATE_STARTUP) { + bbr->full_bw_reached = 0; + + if (bbr->state != NGTCP2_BBR_STATE_PROBE_RTT) { bbr_enter_startup(bbr); - break; - case NGTCP2_BBR_STATE_PROBE_BW_UP: - bbr_start_probe_bw_up(bbr, cstat); - break; - default: - break; + } + } else if (bbr->undo_state == NGTCP2_BBR_STATE_PROBE_BW_UP && + bbr->state != NGTCP2_BBR_STATE_PROBE_BW_UP) { + if (bbr->state != NGTCP2_BBR_STATE_PROBE_RTT) { + bbr_start_probe_bw_refill(bbr); } } + + bbr->undo_state = NGTCP2_BBR_STATE_NONE; } static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, @@ -1024,9 +1057,9 @@ static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) { ngtcp2_rs rs = {0}; - bbr_note_loss(bbr); + bbr_note_loss(bbr, cstat); - if (!bbr->bw_probe_samples) { + if (!bbr->is_bw_probe_sample) { return; } @@ -1197,7 +1230,12 @@ static uint64_t bbr_bdp_multiple(ngtcp2_cc_bbr *bbr, uint64_t gain_h) { return bbr->initial_cwnd; } - bbr->bdp = ngtcp2_max(bbr->bw * bbr->min_rtt / NGTCP2_SECONDS, 1); + if (bbr->min_rtt && + bbr->bw > NGTCP2_BBR_MAX_BDP * NGTCP2_SECONDS / bbr->min_rtt) { + bbr->bdp = NGTCP2_BBR_MAX_BDP; + } else { + bbr->bdp = ngtcp2_max(bbr->bw * bbr->min_rtt / NGTCP2_SECONDS, 1); + } return (uint64_t)(bbr->bdp * gain_h / 100); } @@ -1369,7 +1407,6 @@ static void bbr_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, bbr->in_loss_recovery = 1; bbr->round_count_at_recovery = bbr->round_start ? bbr->round_count : bbr->round_count + 1; - bbr_save_cwnd(bbr, cstat); cstat->congestion_recovery_start_ts = ts; } @@ -1385,7 +1422,6 @@ static void bbr_cc_on_spurious_congestion(ngtcp2_cc *cc, bbr->in_loss_recovery = 0; bbr->round_count_at_recovery = UINT64_MAX; - bbr_restore_cwnd(bbr, cstat); bbr_handle_spurious_loss_detection(bbr, cstat); } @@ -1399,7 +1435,6 @@ static void bbr_cc_on_persistent_congestion(ngtcp2_cc *cc, bbr->in_loss_recovery = 0; bbr->round_count_at_recovery = UINT64_MAX; - bbr_save_cwnd(bbr, cstat); cstat->cwnd = cstat->bytes_in_flight + cstat->max_tx_udp_payload_size; cstat->cwnd = ngtcp2_max(cstat->cwnd, min_pipe_cwnd(cstat->max_tx_udp_payload_size)); diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h index 5a8b470b893f..09dacf81bd55 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h @@ -38,6 +38,7 @@ typedef struct ngtcp2_rst ngtcp2_rst; typedef struct ngtcp2_pcg32 ngtcp2_pcg32; typedef enum ngtcp2_bbr_state { + NGTCP2_BBR_STATE_NONE, NGTCP2_BBR_STATE_STARTUP, NGTCP2_BBR_STATE_DRAIN, NGTCP2_BBR_STATE_PROBE_BW_DOWN, @@ -48,6 +49,7 @@ typedef enum ngtcp2_bbr_state { } ngtcp2_bbr_state; typedef enum ngtcp2_bbr_ack_phase { + NGTCP2_BBR_ACK_PHASE_ACKS_INIT, NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STARTING, NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STOPPING, NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_FEEDBACK, @@ -81,7 +83,7 @@ typedef struct ngtcp2_cc_bbr { uint64_t extra_acked_delivered; /* Congestion signals */ - int loss_in_round; + int is_loss_in_round; uint64_t bw_latest; uint64_t inflight_latest; @@ -113,7 +115,7 @@ typedef struct ngtcp2_cc_bbr { uint64_t undo_inflight_longterm; uint64_t loss_round_delivered; - uint64_t rounds_since_bw_probe; + uint64_t rounds_since_probe_up; uint64_t max_bw; uint64_t bw; uint64_t cycle_count; @@ -121,12 +123,12 @@ typedef struct ngtcp2_cc_bbr { uint64_t bytes_lost_in_round; size_t loss_events_in_round; uint64_t offload_budget; - uint64_t probe_up_cnt; + uint64_t probe_up_acked_per_inc; ngtcp2_tstamp cycle_stamp; ngtcp2_bbr_ack_phase ack_phase; ngtcp2_duration bw_probe_wait; size_t bw_probe_up_rounds; - uint64_t bw_probe_up_acks; + uint64_t bw_probe_up_acked; uint64_t inflight_longterm; ngtcp2_duration probe_rtt_min_delay; ngtcp2_tstamp probe_rtt_min_stamp; @@ -135,9 +137,11 @@ typedef struct ngtcp2_cc_bbr { uint64_t bdp; uint64_t drain_start_round; int loss_round_start; - int bw_probe_samples; + int is_bw_probe_sample; int probe_rtt_expired; int in_loss_recovery; + int prev_probe_too_high; + int prev_probe_precautionary; } ngtcp2_cc_bbr; void ngtcp2_cc_bbr_init(ngtcp2_cc_bbr *bbr, ngtcp2_log *log, diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_callbacks.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_callbacks.c index cbd1d6772751..08c9876346f3 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_callbacks.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_callbacks.c @@ -63,6 +63,9 @@ size_t ngtcp2_callbackslen_version(int callbacks_version) { switch (callbacks_version) { case NGTCP2_CALLBACKS_VERSION: return sizeof(callbacks); + case NGTCP2_CALLBACKS_V4: + return offsetof(ngtcp2_callbacks, recv_stop_sending) + + sizeof(callbacks.recv_stop_sending); case NGTCP2_CALLBACKS_V3: return offsetof(ngtcp2_callbacks, get_path_challenge_data2) + sizeof(callbacks.get_path_challenge_data2); diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c index 4a295d2aab6a..12cca44b07e5 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c @@ -60,7 +60,7 @@ /* NGTCP2_MAX_INITIAL_CRYPTO_OFFSET is the maximum offset of CRYPTO data at Initial encryption level. */ -#define NGTCP2_MAX_INITIAL_CRYPTO_OFFSET (16 * 1024) +#define NGTCP2_MAX_INITIAL_CRYPTO_OFFSET (64 * 1024) /* NGTCP2_MAX_HANDSHAKE_CRYPTO_OFFSET is the maximum offset of CRYPTO data at Handshake encryption level. */ #define NGTCP2_MAX_HANDSHAKE_CRYPTO_OFFSET (64 * 1024) @@ -192,12 +192,43 @@ static int conn_call_stream_open(ngtcp2_conn *conn, ngtcp2_strm *strm) { static int conn_call_stream_close(ngtcp2_conn *conn, ngtcp2_strm *strm) { int rv; - uint32_t flags = NGTCP2_STREAM_CLOSE_FLAG_NONE; + uint32_t flags; + uint64_t rx_app_error_code; + + if (conn->callbacks.stream_close2) { + flags = NGTCP2_STREAM_CLOSE2_FLAG_NONE; + + if (strm->flags & NGTCP2_STRM_FLAG_TX_STOP_SENDING_APP_ERROR_CODE_SET) { + flags |= NGTCP2_STREAM_CLOSE2_FLAG_RX_APP_ERROR_CODE_SET; + rx_app_error_code = strm->tx.stop_sending_app_error_code; + } else if (strm->flags & NGTCP2_STRM_FLAG_RX_APP_ERROR_CODE_SET) { + flags |= NGTCP2_STREAM_CLOSE2_FLAG_RX_APP_ERROR_CODE_SET; + rx_app_error_code = strm->rx.app_error_code; + } else { + rx_app_error_code = 0; + } + + if (strm->flags & NGTCP2_STRM_FLAG_TX_RESET_STREAM_APP_ERROR_CODE_SET) { + flags |= NGTCP2_STREAM_CLOSE2_FLAG_TX_APP_ERROR_CODE_SET; + } + + rv = conn->callbacks.stream_close2(conn, flags, strm->stream_id, + rx_app_error_code, + strm->tx.reset_stream_app_error_code, + conn->user_data, strm->stream_user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; + } if (!conn->callbacks.stream_close) { return 0; } + flags = NGTCP2_STREAM_CLOSE_FLAG_NONE; + if (strm->flags & NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET) { flags |= NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET; } @@ -5997,7 +6028,7 @@ conn_emit_pending_crypto_data(ngtcp2_conn *conn, } for (;;) { - datalen = ngtcp2_rob_data_at(strm->rx.rob, &data, rx_offset); + datalen = (size_t)ngtcp2_rob_data_at(strm->rx.rob, &data, rx_offset); if (datalen == 0) { assert(rx_offset == ngtcp2_strm_rx_offset(strm)); return 0; @@ -7181,7 +7212,7 @@ int ngtcp2_conn_init_stream(ngtcp2_conn *conn, ngtcp2_strm *strm, */ static int conn_emit_pending_stream_data(ngtcp2_conn *conn, ngtcp2_strm *strm, uint64_t rx_offset) { - size_t datalen; + uint64_t datalen; const uint8_t *data; int rv; uint64_t offset; @@ -7194,9 +7225,13 @@ static int conn_emit_pending_stream_data(ngtcp2_conn *conn, ngtcp2_strm *strm, for (;;) { /* Stop calling callback if application has called - ngtcp2_conn_shutdown_stream_read() inside the callback. - Because it doubly counts connection window. */ + ngtcp2_conn_shutdown_stream_read() inside the callback. */ if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) { + datalen = ngtcp2_strm_discard_ordered_data(strm, rx_offset); + if (datalen) { + ngtcp2_conn_extend_max_offset(conn, datalen); + } + return 0; } @@ -7218,17 +7253,12 @@ static int conn_emit_pending_stream_data(ngtcp2_conn *conn, ngtcp2_strm *strm, sdflags |= NGTCP2_STREAM_DATA_FLAG_0RTT; } - rv = conn_call_recv_stream_data(conn, strm, sdflags, offset, data, datalen); + rv = conn_call_recv_stream_data(conn, strm, sdflags, offset, data, + (size_t)datalen); if (rv != 0) { return rv; } - /* ngtcp2_conn_shutdown_stream_read from a callback will free - strm->rx.rob. */ - if (!strm->rx.rob) { - return 0; - } - ngtcp2_rob_pop(strm->rx.rob, rx_offset - datalen, datalen); } } @@ -7508,6 +7538,19 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr, return NGTCP2_ERR_FLOW_CONTROL; } + if (fr->fin) { + if (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) { + if (strm->rx.last_offset != fr_end_offset) { + return NGTCP2_ERR_FINAL_SIZE; + } + } else if (strm->rx.last_offset > fr_end_offset) { + return NGTCP2_ERR_FINAL_SIZE; + } + } else if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) && + strm->rx.last_offset < fr_end_offset) { + return NGTCP2_ERR_FINAL_SIZE; + } + if (strm->rx.last_offset < fr_end_offset) { uint64_t len = fr_end_offset - strm->rx.last_offset; @@ -7516,20 +7559,12 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr, } conn->rx.offset += len; - - if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) { - ngtcp2_conn_extend_max_offset(conn, len); - } } rx_offset = ngtcp2_strm_rx_offset(strm); if (fr->fin) { if (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) { - if (strm->rx.last_offset != fr_end_offset) { - return NGTCP2_ERR_FINAL_SIZE; - } - if (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED) { if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { return NGTCP2_ERR_INTERNAL; @@ -7545,19 +7580,12 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr, return 0; } - } else if (strm->rx.last_offset > fr_end_offset) { - return NGTCP2_ERR_FINAL_SIZE; } else { strm->rx.last_offset = fr_end_offset; ngtcp2_strm_shutdown(strm, NGTCP2_STRM_FLAG_SHUT_RD); } } else { - if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) && - strm->rx.last_offset < fr_end_offset) { - return NGTCP2_ERR_FINAL_SIZE; - } - strm->rx.last_offset = ngtcp2_max(strm->rx.last_offset, fr_end_offset); if (fr_end_offset <= rx_offset) { @@ -7595,31 +7623,32 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr, } if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) { - return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm); - } - - fin = (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) && - rx_offset == strm->rx.last_offset; + datalen += ngtcp2_strm_discard_ordered_data(strm, rx_offset); + ngtcp2_conn_extend_max_offset(conn, datalen); + } else { + fin = (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) && + rx_offset == strm->rx.last_offset; - assert(fin || datalen); + assert(fin || datalen); - if (fin) { - sdflags |= NGTCP2_STREAM_DATA_FLAG_FIN; - } - if (!conn_is_tls_handshake_completed(conn)) { - sdflags |= NGTCP2_STREAM_DATA_FLAG_0RTT; - } - rv = conn_call_recv_stream_data(conn, strm, sdflags, offset, data, - (size_t)datalen); - if (rv != 0) { - return rv; - } + if (fin) { + sdflags |= NGTCP2_STREAM_DATA_FLAG_FIN; + } + if (!conn_is_tls_handshake_completed(conn)) { + sdflags |= NGTCP2_STREAM_DATA_FLAG_0RTT; + } + rv = conn_call_recv_stream_data(conn, strm, sdflags, offset, data, + (size_t)datalen); + if (rv != 0) { + return rv; + } - rv = conn_emit_pending_stream_data(conn, strm, rx_offset); - if (rv != 0) { - return rv; + rv = conn_emit_pending_stream_data(conn, strm, rx_offset); + if (rv != 0) { + return rv; + } } - } else if (fr->datacnt && !(strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING)) { + } else if (fr->datacnt) { nwrite = ngtcp2_strm_recv_reordering(strm, fr->data[0].base, fr->data[0].len, fr->offset); if (nwrite < 0) { @@ -7645,7 +7674,8 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr, */ static int conn_reset_stream(ngtcp2_conn *conn, ngtcp2_strm *strm, uint64_t app_error_code) { - strm->flags |= NGTCP2_STRM_FLAG_SEND_RESET_STREAM; + strm->flags |= NGTCP2_STRM_FLAG_SEND_RESET_STREAM | + NGTCP2_STRM_FLAG_TX_RESET_STREAM_APP_ERROR_CODE_SET; strm->tx.reset_stream_app_error_code = app_error_code; if (ngtcp2_strm_is_tx_queued(strm)) { @@ -7669,7 +7699,8 @@ static int conn_reset_stream(ngtcp2_conn *conn, ngtcp2_strm *strm, */ static int conn_stop_sending(ngtcp2_conn *conn, ngtcp2_strm *strm, uint64_t app_error_code) { - strm->flags |= NGTCP2_STRM_FLAG_SEND_STOP_SENDING; + strm->flags |= NGTCP2_STRM_FLAG_SEND_STOP_SENDING | + NGTCP2_STRM_FLAG_TX_STOP_SENDING_APP_ERROR_CODE_SET; strm->tx.stop_sending_app_error_code = app_error_code; if (ngtcp2_strm_is_tx_queued(strm)) { @@ -7842,19 +7873,18 @@ static int conn_recv_reset_stream(ngtcp2_conn *conn, return rv; } + conn->rx.offset += datalen; + /* Extend connection flow control window for the amount of data which are not passed to application. */ - if (!(strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING)) { - ngtcp2_conn_extend_max_offset(conn, strm->rx.last_offset - - ngtcp2_strm_rx_offset(strm)); - } - - conn->rx.offset += datalen; - ngtcp2_conn_extend_max_offset(conn, datalen); + ngtcp2_conn_extend_max_offset(conn, + fr->final_size - ngtcp2_strm_rx_offset(strm)); strm->rx.last_offset = fr->final_size; - strm->flags |= - NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_RESET_STREAM_RECVED; + strm->flags |= NGTCP2_STRM_FLAG_SHUT_RD | + NGTCP2_STRM_FLAG_RESET_STREAM_RECVED | + NGTCP2_STRM_FLAG_RX_APP_ERROR_CODE_SET; + strm->rx.app_error_code = fr->app_error_code; ngtcp2_strm_set_app_error_code(strm, fr->app_error_code); @@ -8531,10 +8561,6 @@ static int conn_recv_stream_data_blocked(ngtcp2_conn *conn, } conn->rx.offset += datalen; - - if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) { - ngtcp2_conn_extend_max_offset(conn, datalen); - } } strm->rx.last_offset = fr->offset; @@ -8812,13 +8838,16 @@ static void conn_rotate_keys(ngtcp2_conn *conn, int64_t pkt_num, /* * conn_path_validation_in_progress returns nonzero if path validation - * against |path| is underway. + * against |path| is underway. Ignore the path validation with + * NGTCP2_PV_FLAG_DONT_CARE flag set, that is performed after the + * successful migration. Client might migrate back to this path. */ static int conn_path_validation_in_progress(const ngtcp2_conn *conn, const ngtcp2_path *path) { const ngtcp2_pv *pv = conn->pv; - return pv && ngtcp2_path_eq(&pv->dcid.ps.path, path); + return pv && ngtcp2_path_eq(&pv->dcid.ps.path, path) && + !(pv->flags & NGTCP2_PV_FLAG_DONT_CARE); } /* @@ -12998,16 +13027,9 @@ static int conn_shutdown_stream_read(ngtcp2_conn *conn, ngtcp2_strm *strm, return 0; } - /* Extend connection flow control window for the amount of data - which are not passed to application. */ - if (!(strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED)) { - ngtcp2_conn_extend_max_offset(conn, strm->rx.last_offset - - ngtcp2_strm_rx_offset(strm)); - } - strm->flags |= NGTCP2_STRM_FLAG_STOP_SENDING; - ngtcp2_strm_discard_reordered_data(strm); + ngtcp2_strm_stop_buffering_reordered_data(strm); return conn_stop_sending(conn, strm, app_error_code); } @@ -13504,6 +13526,7 @@ int ngtcp2_conn_on_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts) { } ++cstat->pto_count; + cstat->pto_count = ngtcp2_min(cstat->pto_count, 30); ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_LDC, "pto_count=", cstat->pto_count); diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.c index b88985234e56..716e51e9a748 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.c @@ -36,8 +36,11 @@ static ngtcp2_ksl_blk null_blk; ngtcp2_objalloc_def(ksl_blk, ngtcp2_ksl_blk, oplent) +#define NGTCP2_KSL_ALIGNED_BLKLEN \ + ((sizeof(ngtcp2_ksl_blk) + 0x7U) & ~(size_t)0x7U) + static size_t ksl_blklen(size_t aligned_keylen) { - return sizeof(ngtcp2_ksl_blk) + NGTCP2_KSL_MAX_NBLK * aligned_keylen; + return NGTCP2_KSL_ALIGNED_BLKLEN + NGTCP2_KSL_MAX_NBLK * aligned_keylen; } /* @@ -79,7 +82,7 @@ static ngtcp2_ksl_blk *ksl_blk_objalloc_new(ngtcp2_ksl *ksl) { return NULL; } - blk->keys = (uint8_t *)blk + sizeof(*blk); + blk->keys = (uint8_t *)blk + NGTCP2_KSL_ALIGNED_BLKLEN; blk->aligned_keylen = (uint16_t)ksl->aligned_keylen; return blk; @@ -222,22 +225,21 @@ static int ksl_split_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) { * Out of memory. */ static int ksl_split_root(ngtcp2_ksl *ksl) { - ngtcp2_ksl_blk *rblk = NULL, *lblk, *nroot = NULL; + ngtcp2_ksl_blk *rblk, *lblk, *nroot; + + nroot = ksl_blk_objalloc_new(ksl); + if (nroot == NULL) { + return NGTCP2_ERR_NOMEM; + } rblk = ksl_split_blk(ksl, ksl->root); if (rblk == NULL) { + ksl_blk_objalloc_del(ksl, nroot); return NGTCP2_ERR_NOMEM; } lblk = ksl->root; - nroot = ksl_blk_objalloc_new(ksl); - - if (nroot == NULL) { - ksl_blk_objalloc_del(ksl, rblk); - return NGTCP2_ERR_NOMEM; - } - nroot->next = nroot->prev = NULL; nroot->n = 2; nroot->leaf = 0; diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.c index 12b81eb80332..f9abba46daf9 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.c @@ -128,6 +128,8 @@ void ngtcp2_pv_handle_entry_expiry(ngtcp2_pv *pv, ngtcp2_tstamp ts) { } ++pv->round; + pv->round = ngtcp2_min(pv->round, 10); + pv->probe_pkt_left = NGTCP2_PV_NUM_PROBE_PKT; } diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.c index e45351be2b55..56b0d81fc13a 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.c @@ -29,66 +29,32 @@ #include "ngtcp2_macro.h" -int ngtcp2_rob_gap_new(ngtcp2_rob_gap **pg, uint64_t begin, uint64_t end, - const ngtcp2_mem *mem) { - *pg = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_rob_gap)); - if (*pg == NULL) { - return NGTCP2_ERR_NOMEM; - } - - **pg = (ngtcp2_rob_gap){ - .range = - { - .begin = begin, - .end = end, - }, - }; - - return 0; -} - -void ngtcp2_rob_gap_del(ngtcp2_rob_gap *g, const ngtcp2_mem *mem) { - ngtcp2_mem_free(mem, g); -} - -int ngtcp2_rob_data_new(ngtcp2_rob_data **pd, uint64_t offset, size_t chunk, - const ngtcp2_mem *mem) { - *pd = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_rob_data) + chunk); +static int rob_data_new(uint8_t **pd, size_t chunk, const ngtcp2_mem *mem) { + *pd = ngtcp2_mem_malloc(mem, chunk); if (*pd == NULL) { return NGTCP2_ERR_NOMEM; } - **pd = (ngtcp2_rob_data){ - .range = - { - .begin = offset, - .end = offset + chunk, - }, - .begin = (uint8_t *)(*pd) + sizeof(ngtcp2_rob_data), - }; - return 0; } -void ngtcp2_rob_data_del(ngtcp2_rob_data *d, const ngtcp2_mem *mem) { +static void rob_data_del(uint8_t *d, const ngtcp2_mem *mem) { ngtcp2_mem_free(mem, d); } int ngtcp2_rob_init(ngtcp2_rob *rob, size_t chunk, const ngtcp2_mem *mem) { int rv; - ngtcp2_rob_gap *g; + static const ngtcp2_range g = { + .end = UINT64_MAX, + }; ngtcp2_ksl_init(&rob->gapksl, ngtcp2_ksl_range_compar, ngtcp2_ksl_range_search, sizeof(ngtcp2_range), mem); - rv = ngtcp2_rob_gap_new(&g, 0, UINT64_MAX, mem); + rv = ngtcp2_ksl_insert(&rob->gapksl, NULL, &g, NULL); if (rv != 0) { - goto fail_rob_gap_new; - } - - rv = ngtcp2_ksl_insert(&rob->gapksl, NULL, &g->range, g); - if (rv != 0) { - goto fail_gapksl_ksl_insert; + ngtcp2_ksl_free(&rob->gapksl); + return rv; } ngtcp2_ksl_init(&rob->dataksl, ngtcp2_ksl_range_compar, @@ -96,14 +62,9 @@ int ngtcp2_rob_init(ngtcp2_rob *rob, size_t chunk, const ngtcp2_mem *mem) { rob->chunk = chunk; rob->mem = mem; + rob->discard_data = 0; return 0; - -fail_gapksl_ksl_insert: - ngtcp2_rob_gap_del(g, mem); -fail_rob_gap_new: - ngtcp2_ksl_free(&rob->gapksl); - return rv; } void ngtcp2_rob_free(ngtcp2_rob *rob) { @@ -115,12 +76,7 @@ void ngtcp2_rob_free(ngtcp2_rob *rob) { for (it = ngtcp2_ksl_begin(&rob->dataksl); !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) { - ngtcp2_rob_data_del(ngtcp2_ksl_it_get(&it), rob->mem); - } - - for (it = ngtcp2_ksl_begin(&rob->gapksl); !ngtcp2_ksl_it_end(&it); - ngtcp2_ksl_it_next(&it)) { - ngtcp2_rob_gap_del(ngtcp2_ksl_it_get(&it), rob->mem); + rob_data_del(ngtcp2_ksl_it_get(&it), rob->mem); } ngtcp2_ksl_free(&rob->dataksl); @@ -131,12 +87,18 @@ static int rob_write_data(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, size_t len) { size_t n; int rv; - ngtcp2_rob_data *d; + uint8_t *d; ngtcp2_range range = { .begin = offset, .end = offset + len, }; ngtcp2_ksl_it it; + const ngtcp2_range *r; + uint64_t chunk_offset; + + if (rob->discard_data) { + return 0; + } for (it = ngtcp2_ksl_lower_bound_search(&rob->dataksl, &range, ngtcp2_ksl_range_exclusive_search); @@ -144,25 +106,34 @@ static int rob_write_data(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, if (ngtcp2_ksl_it_end(&it)) { d = NULL; } else { + r = ngtcp2_ksl_it_key(&it); d = ngtcp2_ksl_it_get(&it); } - if (d == NULL || offset < d->range.begin) { - rv = ngtcp2_rob_data_new(&d, (offset / rob->chunk) * rob->chunk, - rob->chunk, rob->mem); + if (d == NULL || offset < r->begin) { + rv = rob_data_new(&d, rob->chunk, rob->mem); if (rv != 0) { return rv; } - rv = ngtcp2_ksl_insert(&rob->dataksl, &it, &d->range, d); + chunk_offset = (offset / rob->chunk) * rob->chunk; + + rv = ngtcp2_ksl_insert(&rob->dataksl, &it, + &(ngtcp2_range){ + .begin = chunk_offset, + .end = chunk_offset + rob->chunk, + }, + d); if (rv != 0) { - ngtcp2_rob_data_del(d, rob->mem); + rob_data_del(d, rob->mem); return rv; } + + r = ngtcp2_ksl_it_key(&it); } - n = (size_t)ngtcp2_min((uint64_t)len, d->range.begin + rob->chunk - offset); - memcpy(d->begin + (offset - d->range.begin), data, n); + n = (size_t)ngtcp2_min((uint64_t)len, r->end - offset); + memcpy(d + (offset - r->begin), data, n); offset += n; data += n; len -= n; @@ -174,7 +145,7 @@ static int rob_write_data(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, ngtcp2_ssize ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, size_t datalen) { int rv; - ngtcp2_rob_gap *g; + ngtcp2_range g; ngtcp2_range m, l, r; ngtcp2_range q = { .begin = offset, @@ -188,18 +159,16 @@ ngtcp2_ssize ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, ngtcp2_ksl_range_exclusive_search); for (; !ngtcp2_ksl_it_end(&it);) { - g = ngtcp2_ksl_it_get(&it); - - m = ngtcp2_range_intersect(&q, &g->range); + g = *(const ngtcp2_range *)ngtcp2_ksl_it_key(&it); + m = ngtcp2_range_intersect(&q, &g); mlen = (size_t)ngtcp2_range_len(&m); if (mlen == 0) { break; } - if (ngtcp2_range_eq(&g->range, &m)) { - ngtcp2_ksl_remove_hint(&rob->gapksl, &it, &it, &g->range); - ngtcp2_rob_gap_del(g, rob->mem); + if (ngtcp2_range_eq(&g, &m)) { + ngtcp2_ksl_remove_hint(&rob->gapksl, &it, &it, &g); rv = rob_write_data(rob, m.begin, data + (m.begin - offset), mlen); if (rv != 0) { @@ -211,29 +180,19 @@ ngtcp2_ssize ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, continue; } - ngtcp2_range_cut(&l, &r, &g->range, &m); + ngtcp2_range_cut(&l, &r, &g, &m); if (ngtcp2_range_len(&l)) { - ngtcp2_ksl_update_key(&rob->gapksl, &g->range, &l); - g->range = l; + ngtcp2_ksl_update_key(&rob->gapksl, &g, &l); if (ngtcp2_range_len(&r)) { - ngtcp2_rob_gap *ng; - - rv = ngtcp2_rob_gap_new(&ng, r.begin, r.end, rob->mem); + rv = ngtcp2_ksl_insert(&rob->gapksl, &it, &r, NULL); if (rv != 0) { return rv; } - - rv = ngtcp2_ksl_insert(&rob->gapksl, &it, &ng->range, ng); - if (rv != 0) { - ngtcp2_rob_gap_del(ng, rob->mem); - return rv; - } } } else if (ngtcp2_range_len(&r)) { - ngtcp2_ksl_update_key(&rob->gapksl, &g->range, &r); - g->range = r; + ngtcp2_ksl_update_key(&rob->gapksl, &g, &r); } rv = rob_write_data(rob, m.begin, data + (m.begin - offset), mlen); @@ -250,50 +209,56 @@ ngtcp2_ssize ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, } void ngtcp2_rob_remove_prefix(ngtcp2_rob *rob, uint64_t offset) { - ngtcp2_rob_gap *g; - ngtcp2_rob_data *d; + ngtcp2_range g; + ngtcp2_range r; + uint8_t *d; ngtcp2_ksl_it it; it = ngtcp2_ksl_begin(&rob->gapksl); for (; !ngtcp2_ksl_it_end(&it);) { - g = ngtcp2_ksl_it_get(&it); - if (offset <= g->range.begin) { + g = *(const ngtcp2_range *)ngtcp2_ksl_it_key(&it); + if (offset <= g.begin) { break; } - if (offset < g->range.end) { - ngtcp2_ksl_update_key(&rob->gapksl, &g->range, + if (offset < g.end) { + ngtcp2_ksl_update_key(&rob->gapksl, &g, &(ngtcp2_range){ .begin = offset, - .end = g->range.end, + .end = g.end, }); - g->range.begin = offset; break; } - ngtcp2_ksl_remove_hint(&rob->gapksl, &it, &it, &g->range); - ngtcp2_rob_gap_del(g, rob->mem); + ngtcp2_ksl_remove_hint(&rob->gapksl, &it, &it, &g); + } + + if (rob->discard_data) { + return; } it = ngtcp2_ksl_begin(&rob->dataksl); for (; !ngtcp2_ksl_it_end(&it);) { - d = ngtcp2_ksl_it_get(&it); - if (offset < d->range.begin + rob->chunk) { + r = *(const ngtcp2_range *)ngtcp2_ksl_it_key(&it); + if (offset < r.end) { return; } - ngtcp2_ksl_remove_hint(&rob->dataksl, &it, &it, &d->range); - ngtcp2_rob_data_del(d, rob->mem); + d = ngtcp2_ksl_it_get(&it); + + ngtcp2_ksl_remove_hint(&rob->dataksl, &it, &it, &r); + rob_data_del(d, rob->mem); } } -size_t ngtcp2_rob_data_at(const ngtcp2_rob *rob, const uint8_t **pdest, - uint64_t offset) { - ngtcp2_rob_gap *g; - ngtcp2_rob_data *d; +uint64_t ngtcp2_rob_data_at(const ngtcp2_rob *rob, const uint8_t **pdest, + uint64_t offset) { + const ngtcp2_range *g; + const ngtcp2_range *r; + uint8_t *d; ngtcp2_ksl_it it; it = ngtcp2_ksl_begin(&rob->gapksl); @@ -301,55 +266,80 @@ size_t ngtcp2_rob_data_at(const ngtcp2_rob *rob, const uint8_t **pdest, return 0; } - g = ngtcp2_ksl_it_get(&it); + g = ngtcp2_ksl_it_key(&it); - if (g->range.begin <= offset) { + if (g->begin <= offset) { return 0; } + if (rob->discard_data) { + *pdest = NULL; + + return g->begin - offset; + } + it = ngtcp2_ksl_begin(&rob->dataksl); + r = ngtcp2_ksl_it_key(&it); d = ngtcp2_ksl_it_get(&it); assert(d); - assert(d->range.begin <= offset); - assert(offset < d->range.begin + rob->chunk); + assert(r->begin <= offset); + assert(offset < r->end); - *pdest = d->begin + (offset - d->range.begin); + *pdest = d + (offset - r->begin); - return (size_t)(ngtcp2_min(g->range.begin, d->range.begin + rob->chunk) - - offset); + return ngtcp2_min(g->begin, r->end) - offset; } -void ngtcp2_rob_pop(ngtcp2_rob *rob, uint64_t offset, size_t len) { +void ngtcp2_rob_pop(ngtcp2_rob *rob, uint64_t offset, uint64_t len) { ngtcp2_ksl_it it; - ngtcp2_rob_data *d; + ngtcp2_range r; + uint8_t *d; + + if (rob->discard_data) { + return; + } it = ngtcp2_ksl_begin(&rob->dataksl); + r = *(const ngtcp2_range *)ngtcp2_ksl_it_key(&it); d = ngtcp2_ksl_it_get(&it); assert(d); - if (offset + len < d->range.begin + rob->chunk) { + if (offset + len < r.end) { return; } - ngtcp2_ksl_remove_hint(&rob->dataksl, NULL, &it, &d->range); - ngtcp2_rob_data_del(d, rob->mem); + ngtcp2_ksl_remove_hint(&rob->dataksl, NULL, &it, &r); + rob_data_del(d, rob->mem); } uint64_t ngtcp2_rob_first_gap_offset(const ngtcp2_rob *rob) { ngtcp2_ksl_it it = ngtcp2_ksl_begin(&rob->gapksl); - ngtcp2_rob_gap *g; + const ngtcp2_range *g; if (ngtcp2_ksl_it_end(&it)) { return UINT64_MAX; } - g = ngtcp2_ksl_it_get(&it); + g = ngtcp2_ksl_it_key(&it); - return g->range.begin; + return g->begin; } int ngtcp2_rob_data_buffered(const ngtcp2_rob *rob) { return ngtcp2_ksl_len(&rob->dataksl) != 0; } + +void ngtcp2_rob_discard_data(ngtcp2_rob *rob) { + ngtcp2_ksl_it it; + + rob->discard_data = 1; + + for (it = ngtcp2_ksl_begin(&rob->dataksl); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + rob_data_del(ngtcp2_ksl_it_get(&it), rob->mem); + } + + ngtcp2_ksl_clear(&rob->dataksl); +} diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.h index 60a1c5b46a0d..6b616ea9b81a 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.h @@ -35,71 +35,6 @@ #include "ngtcp2_range.h" #include "ngtcp2_ksl.h" -/* - * ngtcp2_rob_gap represents the gap, which is the range of stream - * data that is not received yet. - */ -typedef struct ngtcp2_rob_gap { - /* range is the range of this gap. */ - ngtcp2_range range; -} ngtcp2_rob_gap; - -/* - * ngtcp2_rob_gap_new allocates new ngtcp2_rob_gap object, and assigns - * its pointer to |*pg|. The caller should call ngtcp2_rob_gap_del to - * delete it when it is no longer used. The range of the gap is - * [begin, end). |mem| is custom memory allocator to allocate memory. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGTCP2_ERR_NOMEM - * Out of memory. - */ -int ngtcp2_rob_gap_new(ngtcp2_rob_gap **pg, uint64_t begin, uint64_t end, - const ngtcp2_mem *mem); - -/* - * ngtcp2_rob_gap_del deallocates |g|. It deallocates the memory - * pointed by |g| it self. |mem| is custom memory allocator to - * deallocate memory. - */ -void ngtcp2_rob_gap_del(ngtcp2_rob_gap *g, const ngtcp2_mem *mem); - -/* - * ngtcp2_rob_data holds the buffered stream data. - */ -typedef struct ngtcp2_rob_data { - /* range is the range of this data. */ - ngtcp2_range range; - /* begin points to the buffer. */ - uint8_t *begin; -} ngtcp2_rob_data; - -/* - * ngtcp2_rob_data_new allocates new ngtcp2_rob_data object, and - * assigns its pointer to |*pd|. The caller should call - * ngtcp2_rob_data_del to delete it when it is no longer used. - * |offset| is the stream offset of the first byte of this data. - * |chunk| is the size of the buffer. |offset| must be multiple of - * |chunk|. |mem| is custom memory allocator to allocate memory. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGTCP2_ERR_NOMEM - * Out of memory. - */ -int ngtcp2_rob_data_new(ngtcp2_rob_data **pd, uint64_t offset, size_t chunk, - const ngtcp2_mem *mem); - -/* - * ngtcp2_rob_data_del deallocates |d|. It deallocates the memory - * pointed by |d| itself. |mem| is custom memory allocator to - * deallocate memory. - */ -void ngtcp2_rob_data_del(ngtcp2_rob_data *d, const ngtcp2_mem *mem); - /* * ngtcp2_rob is the reorder buffer which reassembles stream data * received in out of order. @@ -115,6 +50,9 @@ typedef struct ngtcp2_rob { const ngtcp2_mem *mem; /* chunk is the size of each buffer in data field */ size_t chunk; + /* discard_data, if nonzero, stops buffering data. If it is + nonzero, ngtcp2_ksl_empty(&dataksl) always returns nonzero. */ + int discard_data; } ngtcp2_rob; /* @@ -138,6 +76,10 @@ void ngtcp2_rob_free(ngtcp2_rob *rob); * ngtcp2_rob_push adds new data pointed by |data| of length |datalen| * at the stream offset |offset|. * + * If ngtcp2_rob_discard_data is called, this function does not buffer + * data. The return value is the number of bytes that would be + * buffered if ngtcp2_rob_discard_data has not been called. + * * This function returns the number of data newly buffered if it * succeeds, or one of the following negative error codes: * @@ -160,9 +102,11 @@ void ngtcp2_rob_remove_prefix(ngtcp2_rob *rob, uint64_t offset); * valid length of available data. If no data is available, it * returns 0. This function only returns the data before the first * gap. It returns 0 even if data is available after the first gap. + * If ngtcp2_rob_discard_data has been called, NULL is assigned to + * |*pdest| if this function returns nonzero. */ -size_t ngtcp2_rob_data_at(const ngtcp2_rob *rob, const uint8_t **pdest, - uint64_t offset); +uint64_t ngtcp2_rob_data_at(const ngtcp2_rob *rob, const uint8_t **pdest, + uint64_t offset); /* * ngtcp2_rob_pop clears data at stream offset |offset| of length @@ -174,8 +118,10 @@ size_t ngtcp2_rob_data_at(const ngtcp2_rob *rob, const uint8_t **pdest, * * Caller should call this function from offset 0 in non-decreasing * order. + * + * ngtcp2_rob_pop is noop if ngtcp2_rob_discard_data has been called. */ -void ngtcp2_rob_pop(ngtcp2_rob *rob, uint64_t offset, size_t len); +void ngtcp2_rob_pop(ngtcp2_rob *rob, uint64_t offset, uint64_t len); /* * ngtcp2_rob_first_gap_offset returns the offset to the first gap. @@ -188,4 +134,10 @@ uint64_t ngtcp2_rob_first_gap_offset(const ngtcp2_rob *rob); */ int ngtcp2_rob_data_buffered(const ngtcp2_rob *rob); +/* + * ngtcp2_rob_discard_data discards the buffered data, and stops + * buffering data any further. + */ +void ngtcp2_rob_discard_data(ngtcp2_rob *rob); + #endif /* !defined(NGTCP2_ROB_H) */ diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.c index 70aa8933a3b8..aa01434bf16e 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.c @@ -134,6 +134,10 @@ ngtcp2_ssize ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data, if (strm->rx.cont_offset) { ngtcp2_rob_remove_prefix(strm->rx.rob, strm->rx.cont_offset); } + + if (strm->flags & NGTCP2_STRM_FLAG_NO_REORDERED_DATA_BUFFERING) { + ngtcp2_rob_discard_data(strm->rx.rob); + } } nwrite = ngtcp2_rob_push(strm->rx.rob, offset, data, datalen); @@ -157,18 +161,6 @@ void ngtcp2_strm_update_rx_offset(ngtcp2_strm *strm, uint64_t offset) { ngtcp2_rob_remove_prefix(strm->rx.rob, offset); } -void ngtcp2_strm_discard_reordered_data(ngtcp2_strm *strm) { - if (strm->rx.rob == NULL) { - return; - } - - strm->rx.cont_offset = ngtcp2_strm_rx_offset(strm); - - ngtcp2_rob_free(strm->rx.rob); - ngtcp2_mem_free(strm->mem, strm->rx.rob); - strm->rx.rob = NULL; -} - void ngtcp2_strm_shutdown(ngtcp2_strm *strm, uint32_t flags) { strm->flags |= flags & NGTCP2_STRM_FLAG_SHUT_RDWR; } @@ -745,3 +737,41 @@ int ngtcp2_strm_require_retransmit_stream_data_blocked( return fr->offset == strm->tx.max_offset && !(strm->flags & NGTCP2_STRM_FLAG_SHUT_WR); } + +uint64_t ngtcp2_strm_discard_ordered_data(ngtcp2_strm *strm, + uint64_t rx_offset) { + uint64_t datalen; + const uint8_t *data; + uint64_t orig_rx_offset = rx_offset; + + if (!strm->rx.rob) { + return 0; + } + + for (;;) { + datalen = ngtcp2_rob_data_at(strm->rx.rob, &data, rx_offset); + if (datalen == 0) { + break; + } + + ngtcp2_rob_pop(strm->rx.rob, rx_offset, datalen); + + rx_offset += datalen; + } + + return rx_offset - orig_rx_offset; +} + +void ngtcp2_strm_stop_buffering_reordered_data(ngtcp2_strm *strm) { + if (strm->flags & NGTCP2_STRM_FLAG_NO_REORDERED_DATA_BUFFERING) { + return; + } + + strm->flags |= NGTCP2_STRM_FLAG_NO_REORDERED_DATA_BUFFERING; + + if (!strm->rx.rob) { + return; + } + + ngtcp2_rob_discard_data(strm->rx.rob); +} diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.h index 0d5d8a63a62c..0c47873c3db2 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_strm.h @@ -89,6 +89,18 @@ typedef struct ngtcp2_frame_chain ngtcp2_frame_chain; /* NGTCP2_STRM_FLAG_ANY_SENT indicates that any STREAM frame, including empty one, has been sent. */ #define NGTCP2_STRM_FLAG_ANY_SENT 0x1000U +/* NGTCP2_STRM_FLAG_NO_REORDERED_DATA_BUFFERING is set when + ngtcp2_strm_stop_buffering_reordered_data is called. */ +#define NGTCP2_STRM_FLAG_NO_REORDERED_DATA_BUFFERING 0x2000U +/* NGTCP2_STRM_FLAG_RX_APP_ERROR_CODE_SET is set when + ngtcp2_strm.rx.app_error_code is set. */ +#define NGTCP2_STRM_FLAG_RX_APP_ERROR_CODE_SET 0x4000U +/* NGTCP2_STRM_FLAG_TX_RESET_STREAM_APP_ERROR_CODE_SET is set when + ngtcp2_strm.tx.reset_stream_app_error_code is set. */ +#define NGTCP2_STRM_FLAG_TX_RESET_STREAM_APP_ERROR_CODE_SET 0x8000U +/* NGTCP2_STRM_FLAG_TX_STOP_SENDING_APP_ERROR_CODE_SET is set when + ngtcp2_strm.tx.stop_sending_app_error_code is set. */ +#define NGTCP2_STRM_FLAG_TX_STOP_SENDING_APP_ERROR_CODE_SET 0x10000U typedef struct ngtcp2_strm ngtcp2_strm; @@ -138,10 +150,18 @@ struct ngtcp2_strm { multiple STREAM frames in one lost packet. */ int64_t last_lost_pkt_num; /* stop_sending_app_error_code is the application specific - error code that is sent along with STOP_SENDING. */ + error code that is sent along with STOP_SENDING. If this + field is set, + NGTCP2_STRM_FLAG_TX_STOP_SENDING_APP_ERROR_CODE_SET is set. + This field is eventually passed to ngtcp2_stream_close2 + callback as rx_app_error_code parameter. */ uint64_t stop_sending_app_error_code; /* reset_stream_app_error_code is the application specific - error code that is sent along with RESET_STREAM. */ + error code that is sent along with RESET_STREAM. If this + field is set, + NGTCP2_STRM_FLAG_TX_RESET_STREAM_APP_ERROR_CODE_SET is set. + This field is eventually passed to ngtcp2_stream_close2 + callback as tx_app_error_code parameter. */ uint64_t reset_stream_app_error_code; } tx; @@ -166,6 +186,12 @@ struct ngtcp2_strm { uint64_t unsent_max_offset; /* window is the stream-level flow control window size. */ uint64_t window; + /* app_error_code is the application error code that is + received in RESET_STREAM frame. If this field is set, + NGTCP2_STRM_FLAG_RX_APP_ERROR_CODE_SET is set. This field + is eventually passed to ngtcp2_stream_close2 callback as + rx_app_error_code parameter. */ + uint64_t app_error_code; } rx; const ngtcp2_mem *mem; @@ -208,6 +234,10 @@ uint64_t ngtcp2_strm_rx_offset(const ngtcp2_strm *strm); /* * ngtcp2_strm_recv_reordering handles reordered data. * + * If ngtcp2_strm_stop_buffering_reordered_data has been called, this + * function only records the range of the reordered data. The actual + * data is not buffered. + * * It returns the number of bytes newly buffered if it succeeds, or * one of the following negative error codes: * @@ -223,12 +253,6 @@ ngtcp2_ssize ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data, */ void ngtcp2_strm_update_rx_offset(ngtcp2_strm *strm, uint64_t offset); -/* - * ngtcp2_strm_discard_reordered_data discards all buffered reordered - * data. - */ -void ngtcp2_strm_discard_reordered_data(ngtcp2_strm *strm); - /* * ngtcp2_strm_shutdown shutdowns |strm|. |flags| should be one of * NGTCP2_STRM_FLAG_SHUT_RD, NGTCP2_STRM_FLAG_SHUT_WR, and @@ -358,4 +382,19 @@ int ngtcp2_strm_require_retransmit_max_stream_data( int ngtcp2_strm_require_retransmit_stream_data_blocked( const ngtcp2_strm *strm, const ngtcp2_stream_data_blocked *fr); +/* + * ngtcp2_strm_discard_ordered_data discards the ordered data starting + * at |rx_offset|. It stops when it finds a gap, which means that a + * portion of the data has not been received yet. It returns the size + * of the buffered bytes discarded. + */ +uint64_t ngtcp2_strm_discard_ordered_data(ngtcp2_strm *strm, + uint64_t rx_offset); + +/* + * ngtcp2_strm_stop_buffering_reordered_data discards the buffered + * reordered data, and stops buffering data any further. + */ +void ngtcp2_strm_stop_buffering_reordered_data(ngtcp2_strm *strm); + #endif /* !defined(NGTCP2_STRM_H) */ diff --git a/src/quic/session.cc b/src/quic/session.cc index ca519e3fb43b..1bea15fbadb4 100644 --- a/src/quic/session.cc +++ b/src/quic/session.cc @@ -1698,7 +1698,10 @@ struct Session::Impl final : public MemoryRetainer { ngtcp2_crypto_get_path_challenge_data2_cb, #ifdef NGTCP2_CALLBACKS_V4 on_receive_stream_stop_sending, -#endif +#ifdef NGTCP2_CALLBACKS_V5 + nullptr, +#endif // NGTCP2_CALLBACKS_V5 +#endif // NGTCP2_CALLBACKS_V4 }; static constexpr ngtcp2_callbacks SERVER = { @@ -1749,7 +1752,10 @@ struct Session::Impl final : public MemoryRetainer { ngtcp2_crypto_get_path_challenge_data2_cb, #ifdef NGTCP2_CALLBACKS_V4 on_receive_stream_stop_sending, -#endif +#ifdef NGTCP2_CALLBACKS_V5 + nullptr, +#endif // NGTCP2_CALLBACKS_V5 +#endif // NGTCP2_CALLBACKS_V4 }; }; From 5cef7673ae3a98bca692bd449cb8afe49df4016a Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 4 Aug 2026 12:05:47 +0100 Subject: [PATCH 04/24] sqlite: prevent database close during callbacks Co-authored-by: Asroy Cristian Sitorus Signed-off-by: Matteo Collina PR-URL: https://github.com/nodejs/node/pull/64743 Reviewed-By: Yagiz Nizipli Reviewed-By: Edy Silva Reviewed-By: James M Snell --- src/node_sqlite.cc | 42 +++++++++++++++++++++----- src/node_sqlite.h | 18 +++++++++++ test/parallel/test-sqlite-udf-close.js | 39 ++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 test/parallel/test-sqlite-udf-close.js diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 272e6ffd0aeb..038af9812f9e 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -349,6 +349,7 @@ class CustomAggregate { Global CustomAggregate::*mptr) { CustomAggregate* self = static_cast(sqlite3_user_data(ctx)); + CallbackDepthGuard guard(self->db_); Environment* env = self->env_; Isolate* isolate = env->isolate(); auto agg = self->GetAggregate(ctx); @@ -395,12 +396,18 @@ class CustomAggregate { return; } + if (!self->db_->IsOpen()) { + THROW_ERR_INVALID_STATE(env, "database is not open"); + return; + } + agg->value.Reset(isolate, ret); } static inline void xValueBase(sqlite3_context* ctx, bool is_final) { CustomAggregate* self = static_cast(sqlite3_user_data(ctx)); + CallbackDepthGuard guard(self->db_); Environment* env = self->env_; Isolate* isolate = env->isolate(); auto agg = self->GetAggregate(ctx); @@ -426,6 +433,9 @@ class CustomAggregate { .ToLocal(&result)) { self->db_->SetIgnoreNextSQLiteError(true); sqlite3_result_error(ctx, "", 0); + } else if (!self->db_->IsOpen()) { + THROW_ERR_INVALID_STATE(env, "database is not open"); + return; } } else { result = Local::New(isolate, agg->value); @@ -457,6 +467,10 @@ class CustomAggregate { auto fn = start_v.As(); MaybeLocal retval = fn->Call(env_->context(), Null(isolate), 0, nullptr); + if (!db_->IsOpen()) { + THROW_ERR_INVALID_STATE(env_, "database is not open"); + return nullptr; + } if (!retval.ToLocal(&start_v)) { db_->SetIgnoreNextSQLiteError(true); sqlite3_result_error(ctx, "", 0); @@ -669,6 +683,7 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx, sqlite3_value** argv) { UserDefinedFunction* self = static_cast(sqlite3_user_data(ctx)); + CallbackDepthGuard guard(self->db_); Environment* env = self->env_; Isolate* isolate = env->isolate(); auto recv = Undefined(isolate); @@ -700,6 +715,12 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx, MaybeLocal retval = fn->Call(env->context(), recv, argc, js_argv.data()); + + if (!self->db_->IsOpen()) { + THROW_ERR_INVALID_STATE(env, "database is not open"); + return; + } + Local result; if (!retval.ToLocal(&result)) { // Ignore the SQLite error because a JavaScript exception is pending. @@ -1433,6 +1454,8 @@ void DatabaseSync::Close(const FunctionCallbackInfo& args) { ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); + THROW_AND_RETURN_ON_BAD_STATE( + env, db->IsInCallback(), "database cannot be closed while in a callback"); db->FinalizeStatements(); db->DeleteSessions(); int r = sqlite3_close_v2(db->connection_); @@ -2381,13 +2404,17 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo& args) { BaseObjectPtr guard(db); ArrayBufferViewContents buf(args[0]); - int r = sqlite3changeset_apply( - db->connection_, - buf.length(), - const_cast(static_cast(buf.data())), - context.filterCallback ? xFilter : nullptr, - xConflict, - static_cast(&context)); + int r; + { + CallbackDepthGuard guard(db); + r = sqlite3changeset_apply( + db->connection_, + buf.length(), + const_cast(static_cast(buf.data())), + context.filterCallback ? xFilter : nullptr, + xConflict, + static_cast(&context)); + } if (r == SQLITE_OK) { args.GetReturnValue().Set(true); return; @@ -2522,6 +2549,7 @@ int DatabaseSync::AuthorizerCallback(void* user_data, const char* param3, const char* param4) { DatabaseSync* db = static_cast(user_data); + CallbackDepthGuard guard(db); Environment* env = db->env(); Isolate* isolate = env->isolate(); HandleScope handle_scope(isolate); diff --git a/src/node_sqlite.h b/src/node_sqlite.h index 48463b215cb3..9046b022eea4 100644 --- a/src/node_sqlite.h +++ b/src/node_sqlite.h @@ -229,6 +229,10 @@ class DatabaseSync : public BaseObject { void SetIgnoreNextSQLiteError(bool ignore); bool ShouldIgnoreSQLiteError(); + void IncrementCallbackDepth() { ++callback_depth_; } + void DecrementCallbackDepth() { --callback_depth_; } + bool IsInCallback() const { return callback_depth_ > 0; } + SET_MEMORY_INFO_NAME(DatabaseSync) SET_SELF_SIZE(DatabaseSync) @@ -242,6 +246,7 @@ class DatabaseSync : public BaseObject { bool enable_load_extension_; sqlite3* connection_; bool ignore_next_sqlite_error_; + int callback_depth_ = 0; std::set backups_; std::unordered_set sessions_; @@ -401,6 +406,19 @@ class SQLTagStore : public BaseObject { friend class StatementExecutionHelper; }; +class CallbackDepthGuard { + public: + explicit CallbackDepthGuard(DatabaseSync* db) : db_(db) { + db_->IncrementCallbackDepth(); + } + ~CallbackDepthGuard() { db_->DecrementCallbackDepth(); } + CallbackDepthGuard(const CallbackDepthGuard&) = delete; + CallbackDepthGuard& operator=(const CallbackDepthGuard&) = delete; + + private: + DatabaseSync* db_; +}; + class UserDefinedFunction { public: UserDefinedFunction(Environment* env, diff --git a/test/parallel/test-sqlite-udf-close.js b/test/parallel/test-sqlite-udf-close.js new file mode 100644 index 000000000000..86794029b457 --- /dev/null +++ b/test/parallel/test-sqlite-udf-close.js @@ -0,0 +1,39 @@ +'use strict'; + +const { skipIfSQLiteMissing } = require('../common'); +skipIfSQLiteMissing(); +const assert = require('node:assert'); +const { test } = require('node:test'); +const { DatabaseSync } = require('node:sqlite'); + +for (const method of ['all', 'get', 'run', 'iterate']) { + test(`database.close() from a UDF during statement.${method}()`, () => { + const db = new DatabaseSync(':memory:'); + db.exec(` + CREATE TABLE data (value INTEGER); + INSERT INTO data VALUES (1), (2), (3); + `); + + db.function('close_db', (value) => { + db.close(); + return value; + }); + + const statement = db.prepare('SELECT close_db(value) FROM data'); + assert.throws(() => { + if (method === 'iterate') { + for (const row of statement.iterate()) { + assert.ok(row); + } + } else { + statement[method](); + } + }, { + code: 'ERR_INVALID_STATE', + message: 'database cannot be closed while in a callback', + }); + + assert.strictEqual(db.isOpen, true); + db.close(); + }); +} From 3fc98b802ec22f86b0973e601f24a8cd1c354b63 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 4 Aug 2026 12:06:00 +0100 Subject: [PATCH 05/24] stream: cut per-chunk allocations in pipeTo readableStreamPipeTo allocated, for every chunk written to the destination, a { promise, resolve, reject } write request record that it immediately marked as handled, and drove its loop with an async step()/run() pair whose implicit promises cost one allocation and one reaction per iteration. The parked-read path additionally allocated a read request object, a PromiseWithResolvers record, and a microtask closure per chunk; this is the steady state for pipeThrough, since a TransformStream's readable side has a high water mark of zero. Replace the per-write records with a single per-pipe tracker that the write request queue holds once per pending write and whose resolve()/reject() methods maintain a pending-write count, drive the pump loop with plain callbacks instead of async functions, and reuse one read request and one forwarding function across all chunks, the same pattern tee uses since c543cfb72b7. Benchmark results (benchmark/compare.js --runs 20): webstreams/pipe-to.js +29.9% to +35.8% across all 16 configurations (all 99.9% confidence); a pipeThrough(TransformStream) passthrough loop improves ~17%; every other webstreams benchmark is unchanged. Signed-off-by: Matteo Collina PR-URL: https://github.com/nodejs/node/pull/64890 Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli --- lib/internal/webstreams/readablestream.js | 157 ++++++++++------- lib/internal/webstreams/writablestream.js | 56 ++++++ .../test-webstreams-pipeto-write-request.js | 162 ++++++++++++++++++ 3 files changed, 310 insertions(+), 65 deletions(-) create mode 100644 test/parallel/test-webstreams-pipeto-write-request.js diff --git a/lib/internal/webstreams/readablestream.js b/lib/internal/webstreams/readablestream.js index b25166957614..e1e80eb953c0 100644 --- a/lib/internal/webstreams/readablestream.js +++ b/lib/internal/webstreams/readablestream.js @@ -46,10 +46,6 @@ const { DOMException, } = internalBinding('messaging'); -const { - markPromiseAsHandled, -} = internalBinding('util'); - const { isArrayBufferView, isDataView, @@ -139,7 +135,7 @@ const { writableStreamCloseQueuedOrInFlight, writableStreamDefaultWriterCloseWithErrorPropagation, writableStreamDefaultWriterRelease, - writableStreamDefaultWriterWrite, + writableStreamDefaultWriterWriteWithRequest, writerClosedPromise, writerReadyPromise, } = require('internal/webstreams/writablestream'); @@ -1530,8 +1526,38 @@ function readableStreamPipeTo( const promise = PromiseWithResolvers(); - const state = { - currentWrite: PromiseResolve(), + // One shared write request tracks every chunk written to the + // destination, instead of a { promise, resolve, reject } record per + // write. `stall` is armed by waitForPendingWrites() during shutdown; + // `failed`/`failure` latch a write that could not proceed. + const writeTracker = { + // Non-undefined: queue entries are discriminated from kNilRequest + // by `promise === undefined`. + promise: null, + pending: 0, + failed: false, + failure: undefined, + stall: null, + resolve() { + if (--this.pending === 0 && this.stall !== null) { + const stall = this.stall; + this.stall = null; + if (this.failed) + stall.reject(this.failure); + else + stall.resolve(); + } + }, + reject(error) { + this.pending--; + this.failed = true; + this.failure = error; + if (this.stall !== null) { + const stall = this.stall; + this.stall = null; + stall.reject(error); + } + }, }; // The error here can be undefined. The rejected arg @@ -1548,11 +1574,14 @@ function readableStreamPipeTo( promise.resolve(); } - async function waitForCurrentWrite() { - const write = state.currentWrite; - await write; - if (write !== state.currentWrite) - await waitForCurrentWrite(); + function waitForPendingWrites() { + if (writeTracker.pending === 0) { + return writeTracker.failed ? + PromiseReject(writeTracker.failure) : + PromiseResolve(); + } + writeTracker.stall = PromiseWithResolvers(); + return writeTracker.stall.promise; } function shutdownWithAnAction(action, rejected, originalError) { @@ -1561,7 +1590,7 @@ function readableStreamPipeTo( if (dest[kState].state === 'writable' && !writableStreamCloseQueuedOrInFlight(dest)) { PromisePrototypeThen( - waitForCurrentWrite(), + waitForPendingWrites(), complete, (error) => finalize(true, error)); return; @@ -1582,7 +1611,7 @@ function readableStreamPipeTo( if (dest[kState].state === 'writable' && !writableStreamCloseQueuedOrInFlight(dest)) { PromisePrototypeThen( - waitForCurrentWrite(), + waitForPendingWrites(), () => finalize(rejected, error), (error) => finalize(true, error)); return; @@ -1639,25 +1668,46 @@ function readableStreamPipeTo( PromisePrototypeThen(promise, action, () => {}); } - async function step() { - if (shuttingDown) return true; + // The pump loop is callback-driven to avoid per-iteration promise + // allocations. At most one read is in flight at a time, so one read + // request and one forwarding function are reused for every chunk; + // the chunk travels through `pendingChunk`. + let pendingChunk; + let readRequest; + + // Ready promise rejection is handled by the destination-errored + // watcher. + function ignoreReadyRejection() {} + + function forwardChunk() { + const chunk = pendingChunk; + pendingChunk = undefined; + writableStreamDefaultWriterWriteWithRequest(writer, chunk, writeTracker); + pump(); + } + + function pump() { + if (shuttingDown) return; if (dest[kState].backpressure) { - await writerReadyPromise(writer).promise; - if (shuttingDown) return true; + PromisePrototypeThen( + writerReadyPromise(writer).promise, + pump, + ignoreReadyRejection); + return; } const controller = source[kState].controller; // Fast path: batch reads when data is buffered in a default controller. - // This avoids creating PipeToReadableStreamReadRequest objects and - // reduces promise allocation overhead. + // This avoids parking read requests and reduces promise allocation + // overhead. if (source[kState].state === 'readable' && isReadableStreamDefaultController(controller) && controller[kState].queue.length > 0) { while (controller[kState].queue.length > 0) { - if (shuttingDown) return true; + if (shuttingDown) return; const chunk = dequeueValue(controller); @@ -1668,8 +1718,7 @@ function readableStreamPipeTo( // Write the chunk - we're already in a separate microtask from enqueue // because we awaited the writer ready promise above. - state.currentWrite = writableStreamDefaultWriterWrite(writer, chunk); - markPromiseAsHandled(state.currentWrite); + writableStreamDefaultWriterWriteWithRequest(writer, chunk, writeTracker); // Check backpressure after each write if (dest[kState].backpressure) { @@ -1686,24 +1735,29 @@ function readableStreamPipeTo( // Check if stream closed during batch if (source[kState].state === 'closed') { - return true; + return; } - // Yield to microtask queue between batches to allow events/signals to fire - return false; + // Yield to microtask queue between batches to allow events/signals + // to fire + queueMicrotask(pump); + return; } - // Slow path: use read request for async reads - const promise = PromiseWithResolvers(); - // eslint-disable-next-line no-use-before-define - readableStreamDefaultReaderRead(reader, new PipeToReadableStreamReadRequest(writer, state, promise)); - - return promise.promise; - } - - async function run() { - // Run until step resolves as true - while (!await step()); + // Slow path: park a lazily materialized read request. Close and + // error are handled by the source watchers. + readRequest ??= { + [kChunk](chunk) { + // Per spec, pipeTo must queue a microtask for the write to avoid + // synchronous write during enqueue(). See WHATWG Streams spec + // "ReadableStreamPipeTo" step 15's "chunk steps". + pendingChunk = chunk; + queueMicrotask(forwardChunk); + }, + [kClose]() {}, + [kError]() {}, + }; + readableStreamDefaultReaderRead(reader, readRequest); } if (signal !== undefined) { @@ -1715,7 +1769,7 @@ function readableStreamPipeTo( disposable = addAbortListener(signal, abortAlgorithm); } - setPromiseHandled(run()); + pump(); watchErrored(source, readerClosedPromise(reader).promise, (error) => { if (!preventAbort) { @@ -1760,33 +1814,6 @@ function readableStreamPipeTo( return promise.promise; } -class PipeToReadableStreamReadRequest { - constructor(writer, state, promise) { - this.writer = writer; - this.state = state; - this.promise = promise; - } - - [kChunk](chunk) { - // Per spec, pipeTo must queue a microtask for the write to avoid - // synchronous write during enqueue(). See WHATWG Streams spec - // "ReadableStreamPipeTo" step 15's "chunk steps". - queueMicrotask(() => { - this.state.currentWrite = writableStreamDefaultWriterWrite(this.writer, chunk); - markPromiseAsHandled(this.state.currentWrite); - this.promise.resolve(false); - }); - } - - [kClose]() { - this.promise.resolve(true); - } - - [kError](error) { - this.promise.reject(error); - } -} - function readableStreamTee(stream, cloneForBranch2) { if (isReadableByteStreamController(stream[kState].controller)) { return readableByteStreamTee(stream); diff --git a/lib/internal/webstreams/writablestream.js b/lib/internal/webstreams/writablestream.js index f0eb687be129..87e3bcaa2850 100644 --- a/lib/internal/webstreams/writablestream.js +++ b/lib/internal/webstreams/writablestream.js @@ -1001,6 +1001,61 @@ function writableStreamDefaultWriterWrite(writer, chunk) { return promise; } +// Variant of writableStreamDefaultWriterWrite for pipeTo: the caller +// provides a shared request object instead of a per-write promise record. +// `pending` is incremented before the controller write, which can settle +// requests synchronously when the stream starts erroring; precondition +// failures are latched on `failed`/`failure`. +function writableStreamDefaultWriterWriteWithRequest(writer, chunk, request) { + const writerState = writer[kState]; + const stream = writerState.stream; + assert(stream !== undefined); + const streamState = stream[kState]; + const { + controller, + } = streamState; + const chunkSize = writableStreamDefaultControllerGetChunkSize( + controller, + chunk); + if (stream !== writerState.stream) { + request.failed = true; + request.failure = + new ERR_INVALID_STATE.TypeError('Mismatched WritableStreams'); + return; + } + const { + state, + } = streamState; + + if (state === 'errored') { + request.failed = true; + request.failure = streamState.storedError; + return; + } + + if (streamState.closeQueuedOrInFlight || state === 'closed') { + request.failed = true; + request.failure = + new ERR_INVALID_STATE.TypeError('WritableStream is closed'); + return; + } + + if (state === 'erroring') { + request.failed = true; + request.failure = streamState.storedError; + return; + } + + assert(state === 'writable'); + + let writeRequests = streamState.writeRequests; + if (writeRequests === kEmptyQueue) + writeRequests = streamState.writeRequests = new Queue(); + writeRequests.push(request); + request.pending++; + writableStreamDefaultControllerWrite(controller, chunk, chunkSize); +} + function writableStreamDefaultWriterRelease(writer) { const { stream, @@ -1376,6 +1431,7 @@ module.exports = { writableStreamCloseQueuedOrInFlight, writableStreamAddWriteRequest, writableStreamDefaultWriterWrite, + writableStreamDefaultWriterWriteWithRequest, writableStreamDefaultWriterRelease, writableStreamDefaultWriterGetDesiredSize, writableStreamDefaultWriterEnsureReadyPromiseRejected, diff --git a/test/parallel/test-webstreams-pipeto-write-request.js b/test/parallel/test-webstreams-pipeto-write-request.js new file mode 100644 index 000000000000..23e973a400da --- /dev/null +++ b/test/parallel/test-webstreams-pipeto-write-request.js @@ -0,0 +1,162 @@ +// Flags: --expose-internals --no-warnings +'use strict'; + +// Exercises writableStreamDefaultWriterWriteWithRequest: settle paths +// and the latched precondition failures. + +const common = require('../common'); +const assert = require('assert'); + +const { + WritableStream, + WritableStreamDefaultWriter, + ReadableStream, +} = require('stream/web'); + +const { + writableStreamDefaultWriterWriteWithRequest, +} = require('internal/webstreams/writablestream'); + +function makeRequest(overrides = {}) { + return { + promise: null, + pending: 0, + failed: false, + failure: undefined, + resolve: common.mustNotCall('resolve'), + reject: common.mustNotCall('reject'), + ...overrides, + }; +} + +{ + // A write to a writable destination bumps `pending` and settles through + // request.resolve(). + const request = makeRequest({ + resolve: common.mustCall(function() { + assert.strictEqual(this, request); + assert.strictEqual(this.pending, 1); + assert.strictEqual(this.failed, false); + }), + }); + const ws = new WritableStream({ + write: common.mustCall((chunk) => { + assert.strictEqual(chunk, 'chunk'); + }), + }); + const writer = new WritableStreamDefaultWriter(ws); + writableStreamDefaultWriterWriteWithRequest(writer, 'chunk', request); + assert.strictEqual(request.pending, 1); +} + +{ + // A write whose sink rejects settles through request.reject() with the + // sink's error. + const error = new Error('sink failure'); + const request = makeRequest({ + reject: common.mustCall(function(reason) { + assert.strictEqual(this, request); + assert.strictEqual(reason, error); + }), + }); + const ws = new WritableStream({ + write: common.mustCall(() => Promise.reject(error)), + }); + const writer = new WritableStreamDefaultWriter(ws); + writableStreamDefaultWriterWriteWithRequest(writer, 'chunk', request); + assert.strictEqual(request.pending, 1); +} + +{ + // Writing to an errored destination latches the stored error without + // queueing the write. + const error = new Error('start failure'); + const ws = new WritableStream({ + start(controller) { controller.error(error); }, + }); + queueMicrotask(common.mustCall(() => { + const writer = new WritableStreamDefaultWriter(ws); + const request = makeRequest(); + writableStreamDefaultWriterWriteWithRequest(writer, 'chunk', request); + assert.strictEqual(request.pending, 0); + assert.strictEqual(request.failed, true); + assert.strictEqual(request.failure, error); + })); +} + +{ + // Writing to a destination with a queued close latches an + // ERR_INVALID_STATE TypeError. + const ws = new WritableStream({}); + const writer = new WritableStreamDefaultWriter(ws); + writer.close().then(common.mustCall()); + const request = makeRequest(); + writableStreamDefaultWriterWriteWithRequest(writer, 'chunk', request); + assert.strictEqual(request.pending, 0); + assert.strictEqual(request.failed, true); + assert.match(request.failure.message, /WritableStream is closed/); +} + +{ + // Writing to an erroring destination latches the abort reason. The + // stream stays in the 'erroring' state until its start algorithm + // settles, so aborting right after construction reaches it + // deterministically. + const reason = new Error('abort reason'); + const ws = new WritableStream({}); + const writer = new WritableStreamDefaultWriter(ws); + writer.abort(reason).then(common.mustCall()); + const request = makeRequest(); + writableStreamDefaultWriterWriteWithRequest(writer, 'chunk', request); + assert.strictEqual(request.pending, 0); + assert.strictEqual(request.failed, true); + assert.strictEqual(request.failure, reason); +} + +{ + // A size algorithm that detaches the writer makes the write latch a + // mismatched-streams error. + let writer; + const ws = new WritableStream({}, { + size: common.mustCall(() => { + writer.releaseLock(); + return 1; + }), + highWaterMark: 1, + }); + writer = new WritableStreamDefaultWriter(ws); + const request = makeRequest(); + writableStreamDefaultWriterWriteWithRequest(writer, 'chunk', request); + assert.strictEqual(request.pending, 0); + assert.strictEqual(request.failed, true); + assert.match(request.failure.message, /Mismatched WritableStreams/); +} + +{ + // End to end: aborting a pipe with a write in flight still waits for + // the in-flight write before aborting the destination, and the pipe + // rejects with an AbortError. + const ac = new AbortController(); + const order = []; + const { promise: gate, resolve: openGate } = Promise.withResolvers(); + let i = 0; + const rs = new ReadableStream({ + pull(controller) { controller.enqueue(i++); }, + }); + const pipe = rs.pipeTo(new WritableStream({ + write: common.mustCall((chunk) => { + order.push(`write:${chunk}`); + ac.abort(); + queueMicrotask(() => { + order.push('settle:0'); + openGate(); + }); + return gate; + }), + abort: common.mustCall(() => { + order.push('abort'); + assert.deepStrictEqual(order, ['write:0', 'settle:0', 'abort']); + }), + }), { signal: ac.signal }); + assert.rejects(pipe, { name: 'AbortError' }).then(common.mustCall()); +} From 72768c7ff61b6aacf5a49646693cf38d48d36ec2 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 4 Aug 2026 13:52:01 +0200 Subject: [PATCH 06/24] tools: add ./tools/nix/pkcs11.nix to nix-changes.yml Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64967 Reviewed-By: Antoine du Hamel Reviewed-By: Colin Ihrig --- .github/workflows/nix-changes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix-changes.yml b/.github/workflows/nix-changes.yml index ef05b0bfc8ed..84c4337bab19 100644 --- a/.github/workflows/nix-changes.yml +++ b/.github/workflows/nix-changes.yml @@ -78,7 +78,6 @@ jobs: - name: Compute requisites before change shell: bash # See https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference, we want the pipefail option. - # TODO(panva): add `// import ./tools/nix/pkcs11.nix {}` once landed run: | git reset HEAD^ --hard nix-store --query --references "$( @@ -88,6 +87,7 @@ jobs: ++ builtins.attrValues ( { inherit (import {}) nixfmt-tree sccache; } // import ./tools/nix/openssl-matrix.nix {} + // import ./tools/nix/pkcs11.nix {} )")" \ | xargs nix-store --realise \ | xargs nix-store --query --requisites \ From 5b940dd1244ba4cf29806dbfaa357dcdf12f91f2 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Wed, 29 Jul 2026 15:02:25 +0200 Subject: [PATCH 07/24] tls: drop hand-rolled TLS client hello parser This existed for 'resumeSession', which needed to do an async lookup though SSL_CTX_sess_set_get_cb is sync-only. Nowadays both OpenSSL & BoringSSL have an early ClientHello callback for suspend/resume to handle this properly, so it was redundant, in addition to being complicated and generally a bit fragile & scary. This PR switches to use the modern OpenSSL/BoringSSL mechanisms for this and drops the client hello parser & related infrastructure completely. In addition, there's a new test here, covering a fixed bug: the hello parser silently dropped fragmented hellos, which we now do handle correctly. Signed-off-by: Tim Perry PR-URL: https://github.com/nodejs/node/pull/64827 Reviewed-By: Matteo Collina Reviewed-By: Filip Skokan --- lib/internal/tls/wrap.js | 9 +- node.gyp | 52 ---- src/crypto/README.md | 41 ++- src/crypto/crypto_clienthello-inl.h | 90 ------- src/crypto/crypto_clienthello.cc | 238 ------------------ src/crypto/crypto_clienthello.h | 131 ---------- src/crypto/crypto_context.cc | 8 + src/crypto/crypto_context.h | 6 + src/crypto/crypto_tls.cc | 177 +++++++------ src/crypto/crypto_tls.h | 26 +- test/cctest/test_crypto_clienthello.cc | 133 ---------- test/fuzzers/fuzz_ClientHelloParser.cc | 16 -- .../test-tls-client-hello-fragmented.js | 75 ++++++ .../test-tls-clienthello-sync-write.js | 53 ++++ 14 files changed, 283 insertions(+), 772 deletions(-) delete mode 100644 src/crypto/crypto_clienthello-inl.h delete mode 100644 src/crypto/crypto_clienthello.cc delete mode 100644 src/crypto/crypto_clienthello.h delete mode 100644 test/cctest/test_crypto_clienthello.cc delete mode 100644 test/fuzzers/fuzz_ClientHelloParser.cc create mode 100644 test/parallel/test-tls-client-hello-fragmented.js create mode 100644 test/parallel/test-tls-clienthello-sync-write.js diff --git a/lib/internal/tls/wrap.js b/lib/internal/tls/wrap.js index c1e7fbcae338..51fea0e99614 100644 --- a/lib/internal/tls/wrap.js +++ b/lib/internal/tls/wrap.js @@ -262,8 +262,8 @@ function loadSession(hello) { return owner.destroy(new ERR_SOCKET_CLOSED()); owner._handle.loadSession(session); - // Session is loaded. End the parser to allow handshaking to continue. - owner._handle.endParser(); + // Session is loaded. Let the handshake continue. + owner._handle.clientHelloDone(); } if (hello.sessionId.length <= 0 || @@ -281,8 +281,8 @@ function loadSession(hello) { // Sessions with tickets can be resumed directly from the ticket, no server // session storage is necessary. // Without a call to a resumeSession listener, a session will never be - // loaded, so end the parser to allow handshaking to continue. - owner._handle.endParser(); + // loaded, so let the handshake continue. + owner._handle.clientHelloDone(); } } @@ -970,7 +970,6 @@ TLSSocket.prototype._init = function(socket, wrap) { if (this.server) { if (this.server.listenerCount('resumeSession') > 0 || this.server.listenerCount('newSession') > 0) { - // Also starts the client hello parser as a side effect. ssl.enableSessionCallbacks(); } if (this.server.listenerCount('OCSPRequest') > 0) diff --git a/node.gyp b/node.gyp index 30eeedd40ce9..44193542fb38 100644 --- a/node.gyp +++ b/node.gyp @@ -417,7 +417,6 @@ 'src/crypto/crypto_rsa.cc', 'src/crypto/crypto_spkac.cc', 'src/crypto/crypto_util.cc', - 'src/crypto/crypto_clienthello.cc', 'src/crypto/crypto_dh.cc', 'src/crypto/crypto_hash.cc', 'src/crypto/crypto_keys.cc', @@ -427,7 +426,6 @@ 'src/crypto/crypto_x509.cc', 'src/crypto/crypto_argon2.h', 'src/crypto/crypto_bio.h', - 'src/crypto/crypto_clienthello-inl.h', 'src/crypto/crypto_dh.h', 'src/crypto/crypto_hmac.h', 'src/crypto/crypto_kmac.h', @@ -443,7 +441,6 @@ 'src/crypto/crypto_keygen.h', 'src/crypto/crypto_scrypt.h', 'src/crypto/crypto_tls.h', - 'src/crypto/crypto_clienthello.h', 'src/crypto/crypto_context.h', 'src/crypto/crypto_ec.h', 'src/crypto/crypto_pqc.h', @@ -473,7 +470,6 @@ 'src/tracing/trace_event_legacy.h', ], 'node_cctest_openssl_sources': [ - 'test/cctest/test_crypto_clienthello.cc', 'test/cctest/test_node_crypto.cc', 'test/cctest/test_node_crypto_env.cc', ], @@ -1316,54 +1312,6 @@ }], ], }, # fuzz_env - { # fuzz_ClientHelloParser.cc - 'target_name': 'fuzz_ClientHelloParser', - 'type': 'executable', - 'dependencies': [ - '<(node_lib_target_name)', - ], - 'includes': [ - 'node.gypi' - ], - 'include_dirs': [ - 'src', - 'tools/msvs/genfiles', - 'deps/v8/include', - 'deps/cares/include', - 'deps/uv/include', - 'test/cctest', - ], - 'defines': [ - 'NODE_ARCH="<(target_arch)"', - 'NODE_PLATFORM="<(OS)"', - 'NODE_WANT_INTERNALS=1', - ], - 'sources': [ - 'test/fuzzers/fuzz_ClientHelloParser.cc', - ], - 'conditions': [ - [ 'node_shared_hdr_histogram=="false"', { - 'dependencies': [ - 'deps/histogram/histogram.gyp:histogram', - ], - }], - [ 'node_shared_uvwasi=="false"', { - 'dependencies': [ 'deps/uvwasi/uvwasi.gyp:uvwasi' ], - 'include_dirs': [ 'deps/uvwasi/include' ], - }], - ['OS=="linux" or OS=="openharmony"', { - 'ldflags': [ '-fsanitize=fuzzer' ] - }], - # Ensure that ossfuzz flag has been set and that we are on Linux - [ 'OS not in "linux openharmony" or ossfuzz!="true"', { - 'type': 'none', - }], - # Avoid excessive LTO - ['enable_lto=="true"', { - 'ldflags': [ '-fno-lto' ], - }], - ], - }, # fuzz_ClientHelloParser.cc { # fuzz_strings 'target_name': 'fuzz_strings', 'type': 'executable', diff --git a/src/crypto/README.md b/src/crypto/README.md index cc5093a385ca..ad06cf989276 100644 --- a/src/crypto/README.md +++ b/src/crypto/README.md @@ -30,27 +30,26 @@ throughout the rest of the code. The rest of the files are structured by their function, as detailed in the following table: -| File (\*.h/\*.cc) | Description | -| -------------------- | -------------------------------------------------------------------------- | -| `crypto_aes` | AES Cipher support. | -| `crypto_argon2` | Argon2 key / bit generation implementation. | -| `crypto_cipher` | General Encryption/Decryption utilities. | -| `crypto_clienthello` | TLS/SSL client hello parser implementation. Used during SSL/TLS handshake. | -| `crypto_context` | Implementation of the `SecureContext` object. | -| `crypto_dh` | Diffie-Hellman Key Agreement implementation. | -| `crypto_dsa` | DSA (Digital Signature) Key Generation functions. | -| `crypto_ec` | Elliptic-curve cryptography implementation. | -| `crypto_hash` | Basic hash (e.g. SHA-256) functions. | -| `crypto_hkdf` | HKDF (Key derivation) implementation. | -| `crypto_hmac` | HMAC implementations. | -| `crypto_keys` | Utilities for using and generating secret, private, and public keys. | -| `crypto_pbkdf2` | PBKDF2 key / bit generation implementation. | -| `crypto_rsa` | RSA Key Generation functions. | -| `crypto_scrypt` | Scrypt key / bit generation implementation. | -| `crypto_sig` | General digital signature and verification utilities. | -| `crypto_spkac` | Netscape SPKAC certificate utilities. | -| `crypto_ssl` | Implementation of the `SSLWrap` object. | -| `crypto_timing` | Implementation of the TimingSafeEqual. | +| File (\*.h/\*.cc) | Description | +| ----------------- | -------------------------------------------------------------------- | +| `crypto_aes` | AES Cipher support. | +| `crypto_argon2` | Argon2 key / bit generation implementation. | +| `crypto_cipher` | General Encryption/Decryption utilities. | +| `crypto_context` | Implementation of the `SecureContext` object. | +| `crypto_dh` | Diffie-Hellman Key Agreement implementation. | +| `crypto_dsa` | DSA (Digital Signature) Key Generation functions. | +| `crypto_ec` | Elliptic-curve cryptography implementation. | +| `crypto_hash` | Basic hash (e.g. SHA-256) functions. | +| `crypto_hkdf` | HKDF (Key derivation) implementation. | +| `crypto_hmac` | HMAC implementations. | +| `crypto_keys` | Utilities for using and generating secret, private, and public keys. | +| `crypto_pbkdf2` | PBKDF2 key / bit generation implementation. | +| `crypto_rsa` | RSA Key Generation functions. | +| `crypto_scrypt` | Scrypt key / bit generation implementation. | +| `crypto_sig` | General digital signature and verification utilities. | +| `crypto_spkac` | Netscape SPKAC certificate utilities. | +| `crypto_ssl` | Implementation of the `SSLWrap` object. | +| `crypto_timing` | Implementation of the TimingSafeEqual. | When new crypto protocols are added, they will be added into their own `crypto_` `*.h` and `*.cc` files. diff --git a/src/crypto/crypto_clienthello-inl.h b/src/crypto/crypto_clienthello-inl.h deleted file mode 100644 index 1b8a0c00c307..000000000000 --- a/src/crypto/crypto_clienthello-inl.h +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -#ifndef SRC_CRYPTO_CRYPTO_CLIENTHELLO_INL_H_ -#define SRC_CRYPTO_CRYPTO_CLIENTHELLO_INL_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "crypto/crypto_clienthello.h" -#include "util.h" - -namespace node { -namespace crypto { -inline ClientHelloParser::ClientHelloParser() - : state_(kEnded), - onhello_cb_(nullptr), - onend_cb_(nullptr), - cb_arg_(nullptr) { - Reset(); -} - -inline void ClientHelloParser::Reset() { - frame_len_ = 0; - body_offset_ = 0; - extension_offset_ = 0; - session_size_ = 0; - session_id_ = nullptr; - tls_ticket_size_ = -1; - tls_ticket_ = nullptr; - servername_size_ = 0; - servername_ = nullptr; -} - -inline void ClientHelloParser::Start(ClientHelloParser::OnHelloCb onhello_cb, - ClientHelloParser::OnEndCb onend_cb, - void* cb_arg) { - if (!IsEnded()) - return; - Reset(); - - CHECK_NOT_NULL(onhello_cb); - - state_ = kWaiting; - onhello_cb_ = onhello_cb; - onend_cb_ = onend_cb; - cb_arg_ = cb_arg; -} - -inline void ClientHelloParser::End() { - if (state_ == kEnded) - return; - state_ = kEnded; - if (onend_cb_ != nullptr) { - onend_cb_(cb_arg_); - onend_cb_ = nullptr; - } -} - -inline bool ClientHelloParser::IsEnded() const { - return state_ == kEnded; -} - -inline bool ClientHelloParser::IsPaused() const { - return state_ == kPaused; -} - -} // namespace crypto -} // namespace node - -#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#endif // SRC_CRYPTO_CRYPTO_CLIENTHELLO_INL_H_ diff --git a/src/crypto/crypto_clienthello.cc b/src/crypto/crypto_clienthello.cc deleted file mode 100644 index 203289ae2911..000000000000 --- a/src/crypto/crypto_clienthello.cc +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -#include "crypto/crypto_clienthello.h" // NOLINT(build/include_inline) -#include "crypto/crypto_clienthello-inl.h" - -namespace node { -namespace crypto { -void ClientHelloParser::Parse(const uint8_t* data, size_t avail) { - switch (state_) { - case kWaiting: - if (!ParseRecordHeader(data, avail)) - break; - [[fallthrough]]; - case kTLSHeader: - ParseHeader(data, avail); - break; - case kPaused: - // Just nop - case kEnded: - // Already ended, just ignore it - break; - default: - break; - } -} - - -bool ClientHelloParser::ParseRecordHeader(const uint8_t* data, size_t avail) { - // >= 5 bytes for header parsing - if (avail < 5) - return false; - - if (data[0] == kChangeCipherSpec || - data[0] == kAlert || - data[0] == kHandshake || - data[0] == kApplicationData) { - frame_len_ = (data[3] << 8) + data[4]; - state_ = kTLSHeader; - body_offset_ = 5; - } else { - End(); - return false; - } - - // Sanity check (too big frame, or too small) - // Let OpenSSL handle it - if (frame_len_ >= kMaxTLSFrameLen) { - End(); - return false; - } - - return true; -} - - -void ClientHelloParser::ParseHeader(const uint8_t* data, size_t avail) { - ClientHello hello; - - // We need at least six bytes (one byte for kClientHello, three bytes for the - // length of the handshake message, and two bytes for the protocol version). - // If the client sent a frame that suggests a smaller ClientHello, give up. - if (frame_len_ < 6) return End(); - - // >= 5 + frame size bytes for frame parsing - if (body_offset_ + frame_len_ > avail) - return; - - // Check hello protocol version. Protocol tuples that we know about: - // - // (3,1) TLS v1.0 - // (3,2) TLS v1.1 - // (3,3) TLS v1.2 - // - // Note that TLS v1.3 uses a TLS v1.2 handshake so requires no specific - // support here. - if (data[body_offset_ + 4] != 0x03 || - data[body_offset_ + 5] < 0x01 || - data[body_offset_ + 5] > 0x03) { - return End(); - } - - if (data[body_offset_] == kClientHello) { - if (state_ == kTLSHeader) { - if (!ParseTLSClientHello(data, avail)) - return End(); - } else { - // We couldn't get here, but whatever - return End(); - } - - // Check if we overflowed (do not reply with any private data) - if (session_id_ == nullptr || - session_size_ > 32 || - session_id_ + session_size_ > data + avail) { - return End(); - } - } - - state_ = kPaused; - hello.session_id_ = session_id_; - hello.session_size_ = session_size_; - hello.has_ticket_ = tls_ticket_ != nullptr && tls_ticket_size_ != 0; - hello.servername_ = servername_; - hello.servername_size_ = static_cast(servername_size_); - onhello_cb_(cb_arg_, hello); -} - - -void ClientHelloParser::ParseExtension(const uint16_t type, - const uint8_t* data, - size_t len) { - // NOTE: In case of anything we're just returning back, ignoring the problem. - // That's because we're heavily relying on OpenSSL to solve any problem with - // incoming data. - switch (type) { - case kServerName: - { - if (len < 2) - return; - uint32_t server_names_len = (data[0] << 8) + data[1]; - if (server_names_len + 2 > len) - return; - for (size_t offset = 2; offset < 2 + server_names_len; ) { - if (offset + 3 > len) - return; - uint8_t name_type = data[offset]; - if (name_type != kServernameHostname) - return; - uint16_t name_len = (data[offset + 1] << 8) + data[offset + 2]; - offset += 3; - if (offset + name_len > len) - return; - servername_ = data + offset; - servername_size_ = name_len; - offset += name_len; - } - } - break; - case kTLSSessionTicket: - tls_ticket_size_ = len; - tls_ticket_ = data + len; - break; - default: - // Ignore - break; - } -} - - -bool ClientHelloParser::ParseTLSClientHello(const uint8_t* data, size_t avail) { - const uint8_t* body; - - // Skip frame header, hello header, protocol version and random data - size_t session_offset = body_offset_ + 4 + 2 + 32; - - if (session_offset + 1 >= avail) - return false; - - body = data + session_offset; - session_size_ = *body; - session_id_ = body + 1; - - size_t cipher_offset = session_offset + 1 + session_size_; - - // Session OOB failure - if (cipher_offset + 1 >= avail) - return false; - - uint16_t cipher_len = - (data[cipher_offset] << 8) + data[cipher_offset + 1]; - size_t comp_offset = cipher_offset + 2 + cipher_len; - - // Cipher OOB failure - if (comp_offset >= avail) - return false; - - uint8_t comp_len = data[comp_offset]; - size_t extension_offset = comp_offset + 1 + comp_len; - - // Compression OOB failure - if (extension_offset > avail) - return false; - - // No extensions present - if (extension_offset == avail) - return true; - - size_t ext_off = extension_offset + 2; - - // Parse known extensions - while (ext_off < avail) { - // Extension OOB - if (ext_off + 4 > avail) - return false; - - uint16_t ext_type = (data[ext_off] << 8) + data[ext_off + 1]; - uint16_t ext_len = (data[ext_off + 2] << 8) + data[ext_off + 3]; - ext_off += 4; - - // Extension OOB - if (ext_off + ext_len > avail) - return false; - - ParseExtension(ext_type, - data + ext_off, - ext_len); - - ext_off += ext_len; - } - - // Extensions OOB failure - if (ext_off > avail) - return false; - - return true; -} - -} // namespace crypto -} // namespace node diff --git a/src/crypto/crypto_clienthello.h b/src/crypto/crypto_clienthello.h deleted file mode 100644 index 3af08bc6475e..000000000000 --- a/src/crypto/crypto_clienthello.h +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -#ifndef SRC_CRYPTO_CRYPTO_CLIENTHELLO_H_ -#define SRC_CRYPTO_CRYPTO_CLIENTHELLO_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include // size_t -#include - -namespace node { -namespace crypto { -// Parse the client hello so we can do async session resumption. OpenSSL's -// session resumption uses synchronous callbacks, see SSL_CTX_sess_set_get_cb -// and get_session_cb. -// -// TLS1.3 handshakes masquerade as TLS1.2 session resumption, and to do this, -// they always include a session_id in the ClientHello, making up a bogus value -// if necessary. The parser can't know if its a bogus id, and will cause a -// 'newSession' event to be emitted. This should do no harm, the id won't be -// found, and the handshake will continue. -class ClientHelloParser { - public: - inline ClientHelloParser(); - - class ClientHello { - public: - inline uint8_t session_size() const { return session_size_; } - inline const uint8_t* session_id() const { return session_id_; } - inline bool has_ticket() const { return has_ticket_; } - inline uint8_t servername_size() const { return servername_size_; } - inline const uint8_t* servername() const { return servername_; } - - private: - uint8_t session_size_; - const uint8_t* session_id_; - bool has_ticket_; - uint8_t servername_size_; - const uint8_t* servername_; - - friend class ClientHelloParser; - }; - - typedef void (*OnHelloCb)(void* arg, const ClientHello& hello); - typedef void (*OnEndCb)(void* arg); - - void Parse(const uint8_t* data, size_t avail); - - inline void Reset(); - inline void Start(OnHelloCb onhello_cb, OnEndCb onend_cb, void* cb_arg); - inline void End(); - inline bool IsPaused() const; - inline bool IsEnded() const; - - private: - static const size_t kMaxTLSFrameLen = 16 * 1024 + 5; - static const size_t kMaxSSLExFrameLen = 32 * 1024; - static const uint8_t kServernameHostname = 0; - static const size_t kMinStatusRequestSize = 5; - - enum ParseState { - kWaiting, - kTLSHeader, - kPaused, - kEnded - }; - - enum FrameType { - kChangeCipherSpec = 20, - kAlert = 21, - kHandshake = 22, - kApplicationData = 23, - kOther = 255 - }; - - enum HandshakeType { - kClientHello = 1 - }; - - enum ExtensionType { - kServerName = 0, - kTLSSessionTicket = 35 - }; - - bool ParseRecordHeader(const uint8_t* data, size_t avail); - void ParseHeader(const uint8_t* data, size_t avail); - void ParseExtension(const uint16_t type, - const uint8_t* data, - size_t len); - bool ParseTLSClientHello(const uint8_t* data, size_t avail); - - ParseState state_; - OnHelloCb onhello_cb_; - OnEndCb onend_cb_; - void* cb_arg_; - size_t frame_len_ = 0; - size_t body_offset_ = 0; - size_t extension_offset_ = 0; - uint8_t session_size_ = 0; - const uint8_t* session_id_ = nullptr; - uint16_t servername_size_ = 0; - const uint8_t* servername_ = nullptr; - uint16_t tls_ticket_size_ = -1; - const uint8_t* tls_ticket_ = nullptr; -}; - -} // namespace crypto -} // namespace node - -#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#endif // SRC_CRYPTO_CRYPTO_CLIENTHELLO_H_ diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index 5447de596f98..d6387a55bd78 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -1689,6 +1689,14 @@ void SecureContext::SetNewSessionCallback(NewSessionCb cb) { SSL_CTX_sess_set_new_cb(ctx_.get(), cb); } +void SecureContext::SetClientHelloCallback(ClientHelloCb cb) { +#ifdef OPENSSL_IS_BORINGSSL + SSL_CTX_set_select_certificate_cb(ctx_.get(), cb); +#else + SSL_CTX_set_client_hello_cb(ctx_.get(), cb, nullptr); +#endif +} + void SecureContext::SetGetSessionCallback(GetSessionCb cb) { SSL_CTX_sess_set_get_cb(ctx_.get(), cb); } diff --git a/src/crypto/crypto_context.h b/src/crypto/crypto_context.h index 95ddea4c262d..c65f553e40fd 100644 --- a/src/crypto/crypto_context.h +++ b/src/crypto/crypto_context.h @@ -35,6 +35,11 @@ class SecureContext final : public BaseObject { using KeylogCb = void (*)(const SSL*, const char*); using NewSessionCb = int (*)(SSL*, SSL_SESSION*); using SelectSNIContextCb = int (*)(SSL*, int*, void*); +#ifdef OPENSSL_IS_BORINGSSL + using ClientHelloCb = ssl_select_cert_result_t (*)(const SSL_CLIENT_HELLO*); +#else + using ClientHelloCb = int (*)(SSL*, int*, void*); +#endif ~SecureContext() override; @@ -74,6 +79,7 @@ class SecureContext final : public BaseObject { ncrypto::SSLPointer CreateSSL(); + void SetClientHelloCallback(ClientHelloCb cb); void SetGetSessionCallback(GetSessionCb cb); void SetKeylogCallback(KeylogCb cb); void SetNewSessionCallback(NewSessionCb cb); diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc index ce44ad15fa79..9981704fb34a 100644 --- a/src/crypto/crypto_tls.cc +++ b/src/crypto/crypto_tls.cc @@ -23,7 +23,6 @@ #include #include "async_wrap-inl.h" #include "crypto/crypto_bio.h" -#include "crypto/crypto_clienthello-inl.h" #include "crypto/crypto_common.h" #include "crypto/crypto_context.h" #include "crypto/crypto_util.h" @@ -103,42 +102,43 @@ SSL_SESSION* GetSessionCallback( return w->ReleaseSession(); } -void OnClientHello( - void* arg, - const ClientHelloParser::ClientHello& hello) { - TLSWrap* w = static_cast(arg); - Environment* env = w->env(); - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); +// The TLS library invokes this before version negotiation and before session +// or ticket resumption, which makes async session lookup possible. If required +// the handshake is suspended here and resumed once JS has answered. +#ifdef OPENSSL_IS_BORINGSSL +ssl_select_cert_result_t EarlyClientHelloCallback(const SSL_CLIENT_HELLO* ch) { + TLSWrap* w = static_cast(SSL_get_app_data(ch->ssl)); + if (!w->should_suspend_for_client_hello()) return ssl_select_cert_success; - Local hello_obj = Object::New(env->isolate()); - Local servername = (hello.servername() == nullptr) - ? String::Empty(env->isolate()) - : OneByteString(env->isolate(), - hello.servername(), - hello.servername_size()); - Local buf = - Buffer::Copy( - env, - reinterpret_cast(hello.session_id()), - hello.session_size()).FromMaybe(Local()); + const uint8_t* ext; + size_t ext_len; + bool has_ticket = SSL_early_callback_ctx_extension_get( + ch, TLSEXT_TYPE_session_ticket, &ext, &ext_len) && + ext_len > 0; - if ((buf.IsEmpty() || - hello_obj->Set(env->context(), env->session_id_string(), buf) - .IsNothing()) || - hello_obj->Set(env->context(), env->servername_string(), servername) - .IsNothing() || - hello_obj - ->Set(env->context(), - env->tls_ticket_string(), - Boolean::New(env->isolate(), hello.has_ticket())) - .IsNothing()) { - return; - } + return w->OnEarlyClientHello(ch->session_id, ch->session_id_len, has_ticket) + ? ssl_select_cert_success + : ssl_select_cert_retry; +} +#else +int EarlyClientHelloCallback(SSL* s, int* al, void* arg) { + TLSWrap* w = static_cast(SSL_get_app_data(s)); + if (!w->should_suspend_for_client_hello()) return SSL_CLIENT_HELLO_SUCCESS; + + const unsigned char* session_id; + size_t session_id_len = SSL_client_hello_get0_session_id(s, &session_id); + + const unsigned char* ext; + size_t ext_len; + bool has_ticket = SSL_client_hello_get0_ext( + s, TLSEXT_TYPE_session_ticket, &ext, &ext_len) == 1 && + ext_len > 0; - Local argv[] = { hello_obj }; - w->MakeCallback(env->onclienthello_string(), arraysize(argv), argv); + return w->OnEarlyClientHello(session_id, session_id_len, has_ticket) + ? SSL_CLIENT_HELLO_SUCCESS + : SSL_CLIENT_HELLO_RETRY; } +#endif void KeylogCallback(const SSL* s, const char* line) { TLSWrap* w = static_cast(SSL_get_app_data(s)); @@ -425,6 +425,7 @@ TLSWrap::TLSWrap(Environment* env, ssl_ = sc_->CreateSSL(); CHECK(ssl_); + sc_->SetClientHelloCallback(EarlyClientHelloCallback); sc_->SetGetSessionCallback(GetSessionCallback); sc_->SetNewSessionCallback(NewSessionCallback); @@ -473,6 +474,61 @@ void TLSWrap::NewSessionDoneCb() { Cycle(); } +// N.b. TLS1.3 ClientHellos carry a fake legacy_session_id (middlebox compat), +// and so emit spurious 'resumeSession'/'newSession' events here. +bool TLSWrap::OnEarlyClientHello(const unsigned char* session_id, + size_t session_id_len, + bool has_ticket) { + if (!hello_emitted_) { + hello_emitted_ = true; + Debug(this, "Scheduling onclienthello"); + + // The hello data is only valid inside the library callback, and JS must + // not run while we are on its stack: a handler that synchronously wrote + // to the socket would re-enter SSL mid-handshake. Copy what we need and + // emit from a fresh stack instead. + std::vector id(session_id, session_id + session_id_len); + BaseObjectPtr strong_ref{this}; + env()->SetImmediate( + [this, strong_ref, id = std::move(id), has_ticket](Environment* env) { + if (ssl_) EmitClientHello(id, has_ticket); + }); + } + return hello_answered_; +} + +void TLSWrap::EmitClientHello(const std::vector& session_id, + bool has_ticket) { + Debug(this, "Emitting onclienthello"); + Environment* env = this->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local hello_obj = Object::New(env->isolate()); + Local buf = + Buffer::Copy(env, + reinterpret_cast(session_id.data()), + session_id.size()) + .FromMaybe(Local()); + + if ((buf.IsEmpty() || + hello_obj->Set(env->context(), env->session_id_string(), buf) + .IsNothing()) || + hello_obj + ->Set(env->context(), + env->tls_ticket_string(), + Boolean::New(env->isolate(), has_ticket)) + .IsNothing()) { + // Continue the handshake unresumed rather than leaving it suspended. + hello_answered_ = true; + Cycle(); + return; + } + + Local argv[] = {hello_obj}; + MakeCallback(env->onclienthello_string(), arraysize(argv), argv); +} + void TLSWrap::InitSSL() { // Initialize SSL – OpenSSL takes ownership of these. enc_in_ = NodeBIO::New(env()).release(); @@ -641,12 +697,6 @@ void TLSWrap::SSLInfoCallback(const SSL* ssl_, int where, int ret) { void TLSWrap::EncOut() { Debug(this, "Trying to write encrypted output"); - // Ignore cycling data if ClientHello wasn't yet parsed - if (!hello_parser_.IsEnded()) { - Debug(this, "Returning from EncOut(), hello_parser_ active"); - return; - } - // Write in progress if (write_size_ != 0) { Debug(this, "Returning from EncOut(), write currently in progress"); @@ -785,11 +835,6 @@ void TLSWrap::OnStreamAfterWrite(WriteWrap* req_wrap, int status) { void TLSWrap::ClearOut() { Debug(this, "Trying to read cleartext output"); - // Ignore cycling data if ClientHello wasn't yet parsed - if (!hello_parser_.IsEnded()) { - Debug(this, "Returning from ClearOut(), hello_parser_ active"); - return; - } // No reads after EOF if (eof_) { @@ -912,11 +957,6 @@ void TLSWrap::ClearOut() { void TLSWrap::ClearIn() { Debug(this, "Trying to write cleartext input"); - // Ignore cycling data if ClientHello wasn't yet parsed - if (!hello_parser_.IsEnded()) { - Debug(this, "Returning from ClearIn(), hello_parser_ active"); - return; - } if (ssl_ == nullptr) { Debug(this, "Returning from ClearIn(), ssl_ == nullptr"); @@ -1181,20 +1221,7 @@ void TLSWrap::OnStreamRead(ssize_t nread, const uv_buf_t& buf) { // Commit the amount of data actually read into the peeked/allocated buffer // from the underlying stream. - NodeBIO* enc_in = NodeBIO::FromBIO(enc_in_); - enc_in->Commit(nread); - - // Parse ClientHello first, if we need to. It's only parsed if session event - // listeners are used on the server side. "ended" is the initial state, so - // can mean parsing was never started, or that parsing is finished. Either - // way, ended means we can give the buffered data to SSL. - if (!hello_parser_.IsEnded()) { - size_t avail = 0; - uint8_t* data = reinterpret_cast(enc_in->Peek(&avail)); - CHECK_IMPLIES(data == nullptr, avail == 0); - Debug(this, "Passing %zu bytes to the hello parser", avail); - return hello_parser_.Parse(data, avail); - } + NodeBIO::FromBIO(enc_in_)->Commit(nread); // Cycle OpenSSL's state Cycle(); @@ -1253,15 +1280,6 @@ void TLSWrap::EnableSessionCallbacks(const FunctionCallbackInfo& args) { ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This()); CHECK_NOT_NULL(wrap->ssl_); wrap->enable_session_callbacks(); - - // Clients don't use the HelloParser. - if (wrap->is_client()) - return; - - NodeBIO::FromBIO(wrap->enc_in_)->set_initial(kMaxHelloLength); - wrap->hello_parser_.Start(OnClientHello, - OnClientHelloParseEnd, - wrap); } void TLSWrap::EnableKeylogCallback(const FunctionCallbackInfo& args) { @@ -1336,7 +1354,7 @@ void TLSWrap::Destroy() { void TLSWrap::EnableCertCb(const FunctionCallbackInfo& args) { TLSWrap* wrap; ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This()); - wrap->WaitForCertCb(OnClientHelloParseEnd, wrap); + wrap->WaitForCertCb(ResumeAfterCertCb, wrap); } void TLSWrap::WaitForCertCb(CertCb cb, void* arg) { @@ -1344,9 +1362,9 @@ void TLSWrap::WaitForCertCb(CertCb cb, void* arg) { cert_cb_arg_ = arg; } -void TLSWrap::OnClientHelloParseEnd(void* arg) { +void TLSWrap::ResumeAfterCertCb(void* arg) { TLSWrap* c = static_cast(arg); - Debug(c, "OnClientHelloParseEnd()"); + Debug(c, "ResumeAfterCertCb()"); c->Cycle(); } @@ -2033,10 +2051,11 @@ void TLSWrap::ExportKeyingMaterial(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(buffer); } -void TLSWrap::EndParser(const FunctionCallbackInfo& args) { +void TLSWrap::ClientHelloDone(const FunctionCallbackInfo& args) { TLSWrap* w; ASSIGN_OR_RETURN_UNWRAP(&w, args.This()); - w->hello_parser_.End(); + w->hello_answered_ = true; + w->Cycle(); } void TLSWrap::Renegotiate(const FunctionCallbackInfo& args) { @@ -2214,10 +2233,10 @@ void TLSWrap::Initialize( t->Inherit(AsyncWrap::GetConstructorTemplate(env)); SetProtoMethod(isolate, t, "certCbDone", CertCbDone); + SetProtoMethod(isolate, t, "clientHelloDone", ClientHelloDone); SetProtoMethod(isolate, t, "destroySSL", DestroySSL); SetProtoMethod(isolate, t, "enableCertCb", EnableCertCb); SetProtoMethod(isolate, t, "enableALPNCb", EnableALPNCb); - SetProtoMethod(isolate, t, "endParser", EndParser); SetProtoMethod(isolate, t, "enableKeylogCallback", EnableKeylogCallback); SetProtoMethod(isolate, t, "enableSessionCallbacks", EnableSessionCallbacks); SetProtoMethod(isolate, t, "enableTrace", EnableTrace); @@ -2285,10 +2304,10 @@ void TLSWrap::RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(GetWriteQueueSize); registry->Register(CertCbDone); + registry->Register(ClientHelloDone); registry->Register(DestroySSL); registry->Register(EnableCertCb); registry->Register(EnableALPNCb); - registry->Register(EndParser); registry->Register(EnableKeylogCallback); registry->Register(EnableSessionCallbacks); registry->Register(EnableTrace); diff --git a/src/crypto/crypto_tls.h b/src/crypto/crypto_tls.h index 87063b50bb74..61f773b9c609 100644 --- a/src/crypto/crypto_tls.h +++ b/src/crypto/crypto_tls.h @@ -25,7 +25,6 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "crypto/crypto_context.h" -#include "crypto/crypto_clienthello.h" #include "async_wrap.h" #include "stream_wrap.h" @@ -66,6 +65,11 @@ class TLSWrap : public AsyncWrap, inline bool is_cert_cb_running() const { return cert_cb_running_; } inline bool is_waiting_cert_cb() const { return cert_cb_ != nullptr; } inline bool has_session_callbacks() const { return session_callbacks_; } + // We need to suspend the ClientHello only for server session id + // callbacks, and only on the first pass. + inline bool should_suspend_for_client_hello() const { + return is_server() && session_callbacks_ && !hello_answered_; + } inline void set_cert_cb_running(bool on = true) { cert_cb_running_ = on; } inline void set_awaiting_new_session(bool on = true) { awaiting_new_session_ = on; @@ -105,6 +109,12 @@ class TLSWrap : public AsyncWrap, // Called by the done() callback of the 'newSession' event. void NewSessionDoneCb(); + // Schedules 'onclienthello'; returns false to suspend the handshake until + // clientHelloDone(). The emit itself must not run on the library's stack. + bool OnEarlyClientHello(const unsigned char* session_id, + size_t session_id_len, + bool has_ticket); + // Implement MemoryRetainer: void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(TLSWrap) @@ -122,9 +132,6 @@ class TLSWrap : public AsyncWrap, static constexpr int kClearOutChunkSize = 16384; - // Maximum number of bytes for hello parser - static constexpr int kMaxHelloLength = 16384; - // Usual ServerHello + Certificate size static constexpr int kInitialClientBufferLength = 4096; @@ -140,6 +147,8 @@ class TLSWrap : public AsyncWrap, } void WaitForCertCb(CertCb cb, void* arg); + void EmitClientHello(const std::vector& session_id, + bool has_ticket); TLSWrap(Environment* env, v8::Local obj, @@ -180,6 +189,7 @@ class TLSWrap : public AsyncWrap, static int SelectSNIContextCallback(SSL* s, int* ad, void* arg); static void CertCbDone(const v8::FunctionCallbackInfo& args); + static void ClientHelloDone(const v8::FunctionCallbackInfo& args); static void DestroySSL(const v8::FunctionCallbackInfo& args); static void EnableCertCb(const v8::FunctionCallbackInfo& args); static void EnableALPNCb(const v8::FunctionCallbackInfo& args); @@ -188,7 +198,6 @@ class TLSWrap : public AsyncWrap, static void EnableSessionCallbacks( const v8::FunctionCallbackInfo& args); static void EnableTrace(const v8::FunctionCallbackInfo& args); - static void EndParser(const v8::FunctionCallbackInfo& args); static void ExportKeyingMaterial( const v8::FunctionCallbackInfo& args); static void GetALPNNegotiatedProto( @@ -215,7 +224,7 @@ class TLSWrap : public AsyncWrap, static void IsSessionReused(const v8::FunctionCallbackInfo& args); static void LoadSession(const v8::FunctionCallbackInfo& args); static void NewSessionDone(const v8::FunctionCallbackInfo& args); - static void OnClientHelloParseEnd(void* arg); + static void ResumeAfterCertCb(void* arg); static void Receive(const v8::FunctionCallbackInfo& args); static void Renegotiate(const v8::FunctionCallbackInfo& args); static void RequestOCSP(const v8::FunctionCallbackInfo& args); @@ -257,7 +266,6 @@ class TLSWrap : public AsyncWrap, Kind kind_; ncrypto::SSLSessionPointer next_sess_; ncrypto::SSLPointer ssl_; - ClientHelloParser hello_parser_; v8::Global ocsp_response_; BaseObjectPtr sni_context_; BaseObjectPtr sc_; @@ -274,6 +282,10 @@ class TLSWrap : public AsyncWrap, bool session_callbacks_ = false; bool awaiting_new_session_ = false; + // 'onclienthello' has been emitted for this connection. + bool hello_emitted_ = false; + // JS has answered it by calling clientHelloDone(). + bool hello_answered_ = false; bool in_dowrite_ = false; bool started_ = false; bool shutdown_ = false; diff --git a/test/cctest/test_crypto_clienthello.cc b/test/cctest/test_crypto_clienthello.cc deleted file mode 100644 index 870857cf9061..000000000000 --- a/test/cctest/test_crypto_clienthello.cc +++ /dev/null @@ -1,133 +0,0 @@ -#include "crypto/crypto_clienthello-inl.h" -#include "gtest/gtest.h" - -// If the test is being compiled with an address sanitizer enabled, it should -// catch the memory violation, so do not use a guard page. -#ifdef __SANITIZE_ADDRESS__ -#define NO_GUARD_PAGE -#elif defined(__has_feature) -#if __has_feature(address_sanitizer) -#define NO_GUARD_PAGE -#endif -#endif - -// If the test is running without an address sanitizer, see if we can use -// mprotect() or VirtualProtect() to cause a segmentation fault when spatial -// safety is violated. -#if !defined(NO_GUARD_PAGE) -#ifdef __linux__ -#include -#include -#if defined(_SC_PAGE_SIZE) && defined(PROT_NONE) && defined(PROT_READ) && \ - defined(PROT_WRITE) -#define USE_MPROTECT -#endif -#elif defined(_WIN32) && defined(_MSC_VER) -#include -#include -#define USE_VIRTUALPROTECT -#endif -#endif - -#if defined(USE_MPROTECT) -size_t GetPageSize() { - int page_size = sysconf(_SC_PAGE_SIZE); - CHECK_GE(page_size, 1); - return page_size; -} -#elif defined(USE_VIRTUALPROTECT) -size_t GetPageSize() { - SYSTEM_INFO system_info; - GetSystemInfo(&system_info); - return system_info.dwPageSize; -} -#endif - -template -class OverrunGuardedBuffer { - public: - OverrunGuardedBuffer() { -#if defined(USE_MPROTECT) || defined(USE_VIRTUALPROTECT) - size_t page = GetPageSize(); - CHECK_GE(page, N); -#endif -#ifdef USE_MPROTECT - // Place the packet right before a guard page, which, when accessed, causes - // a segmentation fault. - alloc_base = static_cast(aligned_alloc(page, 2 * page)); - CHECK_NOT_NULL(alloc_base); - uint8_t* second_page = alloc_base + page; - CHECK_EQ(mprotect(second_page, page, PROT_NONE), 0); - data_base = second_page - N; -#elif defined(USE_VIRTUALPROTECT) - // On Windows, it works almost the same way. - alloc_base = static_cast( - VirtualAlloc(nullptr, 2 * page, MEM_COMMIT, PAGE_READWRITE)); - CHECK_NOT_NULL(alloc_base); - uint8_t* second_page = alloc_base + page; - DWORD old_prot; - CHECK_NE(VirtualProtect(second_page, page, PAGE_NOACCESS, &old_prot), 0); - CHECK_EQ(old_prot, PAGE_READWRITE); - data_base = second_page - N; -#else - // Place the packet in a regular allocated buffer. The bug causes undefined - // behavior, which might crash the process, and when it does not, address - // sanitizers and valgrind will catch it. - alloc_base = static_cast(malloc(N)); - CHECK_NOT_NULL(alloc_base); - data_base = alloc_base; -#endif - } - - OverrunGuardedBuffer(const OverrunGuardedBuffer& other) = delete; - OverrunGuardedBuffer& operator=(const OverrunGuardedBuffer& other) = delete; - - ~OverrunGuardedBuffer() { -#if defined(USE_MPROTECT) || defined(USE_VIRTUALPROTECT) - size_t page = GetPageSize(); -#endif -#ifdef USE_VIRTUALPROTECT - VirtualFree(alloc_base, 2 * page, MEM_RELEASE); -#else -#ifdef USE_MPROTECT - // Revert page protection such that the memory can be free()'d. - uint8_t* second_page = alloc_base + page; - CHECK_EQ(mprotect(second_page, page, PROT_READ | PROT_WRITE), 0); -#endif - free(alloc_base); -#endif - } - - uint8_t* data() { - return data_base; - } - - private: - uint8_t* alloc_base; - uint8_t* data_base; -}; - -// Test that ClientHelloParser::ParseHeader() does not blindly trust the client -// to send a valid frame length and subsequently does not read out-of-bounds. -TEST(NodeCrypto, ClientHelloParserParseHeaderOutOfBoundsRead) { - using node::crypto::ClientHelloParser; - - // This is the simplest packet triggering the bug. - const uint8_t packet[] = {0x16, 0x03, 0x01, 0x00, 0x00}; - OverrunGuardedBuffer buffer; - memcpy(buffer.data(), packet, sizeof(packet)); - - // Let the ClientHelloParser parse the packet. This should not lead to a - // segmentation fault or to undefined behavior. - node::crypto::ClientHelloParser parser; - bool end_cb_called = false; - parser.Start([](void* arg, auto hello) { GTEST_FAIL(); }, - [](void* arg) { - bool* end_cb_called = static_cast(arg); - EXPECT_FALSE(*end_cb_called); - *end_cb_called = true; - }, - &end_cb_called); - parser.Parse(buffer.data(), sizeof(packet)); - EXPECT_TRUE(end_cb_called); -} diff --git a/test/fuzzers/fuzz_ClientHelloParser.cc b/test/fuzzers/fuzz_ClientHelloParser.cc deleted file mode 100644 index 87d7ae5e303e..000000000000 --- a/test/fuzzers/fuzz_ClientHelloParser.cc +++ /dev/null @@ -1,16 +0,0 @@ -/* - * A fuzzer focused on node::crypto::ClientHelloParser. - */ - -#include -#include "crypto/crypto_clienthello-inl.h" - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - node::crypto::ClientHelloParser parser; - bool end_cb_called = false; - parser.Start([](void* arg, auto hello) { }, - [](void* arg) { }, - &end_cb_called); - parser.Parse(data, size); - return 0; -} diff --git a/test/parallel/test-tls-client-hello-fragmented.js b/test/parallel/test-tls-client-hello-fragmented.js new file mode 100644 index 000000000000..690482e73dc5 --- /dev/null +++ b/test/parallel/test-tls-client-hello-fragmented.js @@ -0,0 +1,75 @@ +'use strict'; + +// A ClientHello split across several TLS records must still emit +// 'resumeSession'. + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const fixtures = require('../common/fixtures'); +const net = require('net'); +const tls = require('tls'); + +const options = { + key: fixtures.readKey('rsa_private.pem'), + cert: fixtures.readKey('rsa_cert.crt'), +}; + +// Capture a real ClientHello record so the replayed bytes are well-formed. +function captureClientHello(callback) { + let hello = Buffer.alloc(0); + const collector = net.createServer((socket) => { + socket.on('data', (chunk) => { + hello = Buffer.concat([hello, chunk]); + if (hello.length < 5 || hello.length < 5 + hello.readUInt16BE(3)) return; + socket.destroy(); + collector.close(() => callback(hello)); + }); + }); + collector.listen(0, common.mustCall(() => { + tls.connect({ port: collector.address().port, rejectUnauthorized: false }) + .on('error', () => {}); + })); +} + +captureClientHello(common.mustCall((hello) => { + assert.strictEqual(hello[0], 22); + + // Split partway through the fixed-size header, before the session ID. + const body = hello.subarray(5); + const split = 20; + assert.ok(body.length > split); + + const record = (payload) => Buffer.concat([ + Buffer.from([22, hello[1], hello[2], + payload.length >> 8, payload.length & 0xff]), + payload, + ]); + + const server = tls.createServer(options); + server.on('tlsClientError', () => {}); // The replay never completes. + + server.listen(0, common.mustCall(() => { + const client = net.connect(server.address().port, common.mustCall(() => { + client.write(record(body.subarray(0, split))); + setTimeout(() => client.write(record(body.subarray(split))), 10); + })); + client.on('error', () => {}); + + // Fail fast: a missing event stalls the handshake rather than erroring. + const guard = setTimeout(() => { + throw new Error('resumeSession was not emitted'); + }, common.platformTimeout(10000)); + + server.on('resumeSession', common.mustCall((id, callback) => { + clearTimeout(guard); + assert.ok(id.length > 0); + callback(null, null); + client.destroy(); + server.close(); + })); + })); +})); diff --git a/test/parallel/test-tls-clienthello-sync-write.js b/test/parallel/test-tls-clienthello-sync-write.js new file mode 100644 index 000000000000..4b7fcc541f0c --- /dev/null +++ b/test/parallel/test-tls-clienthello-sync-write.js @@ -0,0 +1,53 @@ +'use strict'; + +// Writing to a server TLSSocket synchronously from inside a 'resumeSession' +// handler, while the handshake is still waiting on the ClientHello, must not +// break the connection; the data must be delivered once the handshake ends. + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { EventEmitter } = require('events'); +const fixtures = require('../common/fixtures'); +const net = require('net'); +const tls = require('tls'); + +const secureContext = tls.createSecureContext({ + key: fixtures.readKey('rsa_private.pem'), + cert: fixtures.readKey('rsa_cert.crt'), +}); + +const fakeServer = new EventEmitter(); +fakeServer.getTicketKeys = () => null; + +let serverSocket; +fakeServer.on('resumeSession', common.mustCall((id, callback) => { + serverSocket.write('from-mid-handshake'); + callback(null, null); +})); + +const server = net.createServer(common.mustCall((raw) => { + serverSocket = new tls.TLSSocket(raw, { + isServer: true, + secureContext, + server: fakeServer, + }); + serverSocket.on('error', common.mustNotCall()); +})); + +server.listen(0, common.mustCall(() => { + const client = tls.connect({ + port: server.address().port, + rejectUnauthorized: false, + }, common.mustCall(() => { + client.on('data', common.mustCall((data) => { + assert.strictEqual(data.toString(), 'from-mid-handshake'); + client.end(); + server.close(); + })); + })); + client.on('error', common.mustNotCall()); +})); From d18457bad3950ff545fe6f2982e9d5e2c5de31e6 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Mon, 3 Aug 2026 15:37:03 +0200 Subject: [PATCH 08/24] tls: don't trigger SNICallback or OCSPRequest from the TLS lib stack Both events (backed by oncertcb) could potentially write to the socket synchronously, re-entering SSL mid-handshake and breaking the connection, so we defer them just like the new 'resumeSession' behaviour. Also fixes a small bug in the error path of EmitClientHello, which now bails out more aggressively instead of resuming handshakes in a V8 teardown scenario. Co-authored-by: Filip Skokan Signed-off-by: Tim Perry PR-URL: https://github.com/nodejs/node/pull/64827 Reviewed-By: Matteo Collina Reviewed-By: Filip Skokan --- src/crypto/crypto_tls.cc | 68 +++++++++++++-------- src/crypto/crypto_tls.h | 4 ++ test/parallel/test-tls-certcb-sync-write.js | 49 +++++++++++++++ 3 files changed, 96 insertions(+), 25 deletions(-) create mode 100644 test/parallel/test-tls-certcb-sync-write.js diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc index 9981704fb34a..8ef74aee2d0e 100644 --- a/src/crypto/crypto_tls.cc +++ b/src/crypto/crypto_tls.cc @@ -218,32 +218,18 @@ int SSLCertCallback(SSL* s, void* arg) { // handshake will continue after certcb is done. return -1; - Environment* env = w->env(); - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); w->set_cert_cb_running(); - Local info = Object::New(env->isolate()); + // The view points into SSL-owned memory, so copy it before deferring. + std::string servername; + if (auto name = SSLPointer::GetServerName(s)) servername = *name; - auto servername = SSLPointer::GetServerName(s); - Local servername_str = - !servername.has_value() - ? String::Empty(env->isolate()) - : OneByteString(env->isolate(), servername.value()); - - Local ocsp = Boolean::New( - env->isolate(), SSL_get_tlsext_status_type(s) == TLSEXT_STATUSTYPE_ocsp); + w->ScheduleCertCb(std::move(servername), + SSL_get_tlsext_status_type(s) == TLSEXT_STATUSTYPE_ocsp); - if (info->Set(env->context(), env->servername_string(), servername_str) - .IsNothing() || - info->Set(env->context(), env->ocsp_request_string(), ocsp).IsNothing()) { - return 1; - } - - Local argv[] = { info }; - w->MakeCallback(env->oncertcb_string(), arraysize(argv), argv); - - return w->is_cert_cb_running() ? -1 : 1; + // Suspend handshake with SSL_ERROR_WANT_X509_LOOKUP, and handshake will + // continue after certcb is done. + return -1; } int SelectALPNCallback( @@ -519,9 +505,7 @@ void TLSWrap::EmitClientHello(const std::vector& session_id, env->tls_ticket_string(), Boolean::New(env->isolate(), has_ticket)) .IsNothing()) { - // Continue the handshake unresumed rather than leaving it suspended. - hello_answered_ = true; - Cycle(); + // An exception is pending, so don't re-enter SSL or JS to resume. return; } @@ -529,6 +513,40 @@ void TLSWrap::EmitClientHello(const std::vector& session_id, MakeCallback(env->onclienthello_string(), arraysize(argv), argv); } +// As with the ClientHello, JS must not run on the library's stack: 'oncertcb' +// handlers synchronously call back into the handle to resume the handshake. +void TLSWrap::ScheduleCertCb(std::string servername, bool ocsp) { + Debug(this, "Scheduling oncertcb"); + BaseObjectPtr strong_ref{this}; + env()->SetImmediate( + [this, strong_ref, servername = std::move(servername), ocsp]( + Environment* env) { + if (ssl_) EmitCertCb(servername, ocsp); + }); +} + +void TLSWrap::EmitCertCb(const std::string& servername, bool ocsp) { + Debug(this, "Emitting oncertcb"); + Environment* env = this->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local info = Object::New(env->isolate()); + if (info->Set(env->context(), + env->servername_string(), + OneByteString(env->isolate(), servername)) + .IsNothing() || + info->Set(env->context(), + env->ocsp_request_string(), + Boolean::New(env->isolate(), ocsp)) + .IsNothing()) { + return; + } + + Local argv[] = {info}; + MakeCallback(env->oncertcb_string(), arraysize(argv), argv); +} + void TLSWrap::InitSSL() { // Initialize SSL – OpenSSL takes ownership of these. enc_in_ = NodeBIO::New(env()).release(); diff --git a/src/crypto/crypto_tls.h b/src/crypto/crypto_tls.h index 61f773b9c609..a5ded3392915 100644 --- a/src/crypto/crypto_tls.h +++ b/src/crypto/crypto_tls.h @@ -115,6 +115,9 @@ class TLSWrap : public AsyncWrap, size_t session_id_len, bool has_ticket); + // Schedules 'oncertcb'. The handshake stays suspended until certCbDone(). + void ScheduleCertCb(std::string servername, bool ocsp); + // Implement MemoryRetainer: void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(TLSWrap) @@ -149,6 +152,7 @@ class TLSWrap : public AsyncWrap, void WaitForCertCb(CertCb cb, void* arg); void EmitClientHello(const std::vector& session_id, bool has_ticket); + void EmitCertCb(const std::string& servername, bool ocsp); TLSWrap(Environment* env, v8::Local obj, diff --git a/test/parallel/test-tls-certcb-sync-write.js b/test/parallel/test-tls-certcb-sync-write.js new file mode 100644 index 000000000000..caf591d669f4 --- /dev/null +++ b/test/parallel/test-tls-certcb-sync-write.js @@ -0,0 +1,49 @@ +'use strict'; + +// Writing to a server TLSSocket synchronously from inside an SNICallback, +// while the handshake is still waiting on the certificate callback, must not +// break the connection; the data must be delivered once the handshake ends. + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const fixtures = require('../common/fixtures'); +const net = require('net'); +const tls = require('tls'); + +const secureContext = tls.createSecureContext({ + key: fixtures.readKey('rsa_private.pem'), + cert: fixtures.readKey('rsa_cert.crt'), +}); + +let serverSocket; +const server = net.createServer(common.mustCall((raw) => { + serverSocket = new tls.TLSSocket(raw, { + isServer: true, + secureContext, + SNICallback: common.mustCall((servername, callback) => { + assert.strictEqual(servername, 'localhost'); + serverSocket.write('from-mid-handshake'); + callback(null, null); + }), + }); + serverSocket.on('error', common.mustNotCall()); +})); + +server.listen(0, common.mustCall(() => { + const client = tls.connect({ + port: server.address().port, + servername: 'localhost', + rejectUnauthorized: false, + }, common.mustCall(() => { + client.on('data', common.mustCall((data) => { + assert.strictEqual(data.toString(), 'from-mid-handshake'); + client.end(); + server.close(); + })); + })); + client.on('error', common.mustNotCall()); +})); From 793a6ec0dea6757316f57e0b792fef173c544086 Mon Sep 17 00:00:00 2001 From: Archkon <180910180+Archkon@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:33:13 +0800 Subject: [PATCH 09/24] src: use UTF-8 for task runner filesystem paths Use ConvertPathToUTF8() instead of path::string() when passing filesystem paths to Node and libuv interfaces. This prevents paths containing characters outside the active Windows code page from being corrupted or rejected. Signed-off-by: Archkon <180910180+Archkon@users.noreply.github.com> PR-URL: https://github.com/nodejs/node/pull/64868 Reviewed-By: Yagiz Nizipli Reviewed-By: Stefan Stojanovic --- src/node_task_runner.cc | 25 ++++++++++---------- test/parallel/test-node-run.js | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/node_task_runner.cc b/src/node_task_runner.cc index 22c02e83e12e..2b3e005abf34 100644 --- a/src/node_task_runner.cc +++ b/src/node_task_runner.cc @@ -123,7 +123,7 @@ void ProcessRunner::SetEnvironmentVariables() { // Add NODE_RUN_PACKAGE_JSON_PATH environment variable to the environment to // indicate which package.json is being processed. env_vars_.push_back("NODE_RUN_PACKAGE_JSON_PATH=" + - package_json_path_.string()); + ConvertPathToUTF8(package_json_path_)); env_ = std::unique_ptr(new char*[env_vars_.size() + 1]); options_.env = env_.get(); @@ -206,7 +206,7 @@ void ProcessRunner::OnExit(int64_t exit_status, int term_signal) { void ProcessRunner::Run() { // keeps the string alive until destructor - cwd_ = package_json_path_.parent_path().string(); + cwd_ = ConvertPathToUTF8(package_json_path_.parent_path()); options_.cwd = cwd_.c_str(); if (int r = uv_spawn(loop_, &process_, &options_)) { fprintf(stderr, "Error: %s\n", uv_strerror(r)); @@ -228,14 +228,14 @@ FindPackageJson(const std::filesystem::path& cwd) { // Append "path/node_modules/.bin" to the env var, if it is a directory. auto node_modules_bin = directory_path / "node_modules" / ".bin"; if (std::filesystem::is_directory(node_modules_bin)) { - path_env_var += node_modules_bin.string() + env_var_separator; + path_env_var += ConvertPathToUTF8(node_modules_bin) + env_var_separator; } if (raw_content.empty()) { package_json_path = directory_path / "package.json"; // This is required for Windows because std::filesystem::path::c_str() // returns wchar_t* on Windows, and char* on other platforms. - std::string contents = package_json_path.string(); + std::string contents = ConvertPathToUTF8(package_json_path); USE(ReadFileSync(&raw_content, contents.c_str()) > 0); } } @@ -258,7 +258,7 @@ void RunTask(const std::shared_ptr& result, if (!package_json.has_value()) { fprintf(stderr, "Can't find package.json for directory %s\n", - cwd.string().c_str()); + ConvertPathToUTF8(cwd).c_str()); result->exit_code_ = ExitCode::kGenericUserError; return; } @@ -274,7 +274,7 @@ void RunTask(const std::shared_ptr& result, simdjson::ondemand::object main_object; if (json_parser.iterate(raw_json).get(document)) { - fprintf(stderr, "Can't parse %s\n", path.string().c_str()); + fprintf(stderr, "Can't parse %s\n", ConvertPathToUTF8(path).c_str()); result->exit_code_ = ExitCode::kGenericUserError; return; } @@ -283,9 +283,9 @@ void RunTask(const std::shared_ptr& result, if (root_error == simdjson::error_code::INCORRECT_TYPE) { fprintf(stderr, "Root value unexpected not an object for %s\n\n", - path.string().c_str()); + ConvertPathToUTF8(path).c_str()); } else { - fprintf(stderr, "Can't parse %s\n", path.string().c_str()); + fprintf(stderr, "Can't parse %s\n", ConvertPathToUTF8(path).c_str()); } result->exit_code_ = ExitCode::kGenericUserError; return; @@ -294,8 +294,9 @@ void RunTask(const std::shared_ptr& result, // If package_json object doesn't have "scripts" field, throw an error. simdjson::ondemand::object scripts_object; if (main_object["scripts"].get_object().get(scripts_object)) { - fprintf( - stderr, "Can't find \"scripts\" field in %s\n", path.string().c_str()); + fprintf(stderr, + "Can't find \"scripts\" field in %s\n", + ConvertPathToUTF8(path).c_str()); result->exit_code_ = ExitCode::kGenericUserError; return; } @@ -309,13 +310,13 @@ void RunTask(const std::shared_ptr& result, "Script \"%.*s\" is unexpectedly not a string for %s\n\n", static_cast(command_id.size()), command_id.data(), - path.string().c_str()); + ConvertPathToUTF8(path).c_str()); } else { fprintf(stderr, "Missing script: \"%.*s\" for %s\n\n", static_cast(command_id.size()), command_id.data(), - path.string().c_str()); + ConvertPathToUTF8(path).c_str()); fprintf(stderr, "Available scripts are:\n"); // Reset the object to iterate over it again diff --git a/test/parallel/test-node-run.js b/test/parallel/test-node-run.js index e24117f6b165..7c1f6609f6f1 100644 --- a/test/parallel/test-node-run.js +++ b/test/parallel/test-node-run.js @@ -5,8 +5,11 @@ common.requireNoPackageJSONAbove(); const { it, describe } = require('node:test'); const assert = require('node:assert'); +const fs = require('node:fs'); +const path = require('node:path'); const fixtures = require('../common/fixtures'); +const tmpdir = require('../common/tmpdir'); const envSuffix = common.isWindows ? '-windows' : ''; describe('node --run [command]', () => { @@ -201,6 +204,45 @@ describe('node --run [command]', () => { assert.strictEqual(child.code, 0); }); + it('handles package paths outside the active Windows code page', + { skip: !common.isWindows }, async () => { + tmpdir.refresh(); + + const projectDir = path.join(tmpdir.path, 'node-run-\u{20BB7}'); + const packageJsonPath = path.join(projectDir, 'package.json'); + const nodeModulesBin = path.join(projectDir, 'node_modules', '.bin'); + const checkScript = path.join(projectDir, 'check.js'); + + fs.mkdirSync(nodeModulesBin, { recursive: true }); + fs.writeFileSync(packageJsonPath, JSON.stringify({ + scripts: { + unicode: `"${process.execPath}" check.js`, + }, + })); + fs.writeFileSync(checkScript, ` + 'use strict'; + console.log(JSON.stringify({ + cwd: process.cwd(), + packageJsonPath: process.env.NODE_RUN_PACKAGE_JSON_PATH, + path: process.env.PATH, + })); + `); + + const child = await common.spawnPromisified( + process.execPath, + [ '--run', 'unicode'], + { cwd: projectDir }, + ); + + assert.strictEqual(child.stderr, ''); + assert.strictEqual(child.code, 0); + + const output = JSON.parse(child.stdout); + assert.strictEqual(output.cwd, projectDir); + assert.strictEqual(output.packageJsonPath, packageJsonPath); + assert.strictEqual(output.path.split(path.delimiter)[0], nodeModulesBin); + }); + it('returns error on unparsable file', async () => { const child = await common.spawnPromisified( process.execPath, From 15495a069fc8eaec8c70ea88781ae241a87fd25d Mon Sep 17 00:00:00 2001 From: Kamal Rawal Date: Tue, 4 Aug 2026 18:03:25 +0530 Subject: [PATCH 10/24] doc: fix grammar and punctuation in dgram documentation Signed-off-by: Rawal27 PR-URL: https://github.com/nodejs/node/pull/64957 Reviewed-By: Mike McCready <66998419+MikeMcC399@users.noreply.github.com> Reviewed-By: Tierney Cyren Reviewed-By: Rich Trott --- doc/api/dgram.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/api/dgram.md b/doc/api/dgram.md index 28fb5a2bed14..3ccc5566bc23 100644 --- a/doc/api/dgram.md +++ b/doc/api/dgram.md @@ -257,7 +257,7 @@ useful. A bound datagram socket keeps the Node.js process running to receive datagram messages. -If binding fails, an `'error'` event is generated. In rare case (e.g. +If binding fails, an `'error'` event is generated. In rare cases (e.g., attempting to bind with a closed socket), an [`Error`][] may be thrown. Example of a UDP server listening on port 41234: @@ -350,7 +350,7 @@ is called. A bound datagram socket keeps the Node.js process running to receive datagram messages. -If binding fails, an `'error'` event is generated. In rare case (e.g. +If binding fails, an `'error'` event is generated. In rare cases (e.g., attempting to bind with a closed socket), an [`Error`][] may be thrown. An example socket listening on an exclusive port is shown below. @@ -596,7 +596,7 @@ to exclude the socket from the reference counting that keeps the Node.js process active. The `socket.ref()` method adds the socket back to the reference counting and restores the default behavior. -Calling `socket.ref()` multiples times will have no additional effect. +Calling `socket.ref()` multiple times will have no additional effect. The `socket.ref()` method returns a reference to the socket so calls can be chained. @@ -745,7 +745,7 @@ client.send([buf1, buf2], 41234, (err) => { }); ``` -Sending multiple buffers might be faster or slower depending on the +Sending multiple buffers might be faster or slower, depending on the application and operating system. Run benchmarks to determine the optimal strategy on a case-by-case basis. Generally speaking, however, sending multiple buffers is faster. @@ -887,7 +887,7 @@ socket.bind(1234, () => { A call on a socket that is not ready to send or no longer open may throw a _Not running_ [`Error`][]. -If `multicastInterface` can not be parsed into an IP then an _EINVAL_ +If `multicastInterface` cannot be parsed into an IP then an _EINVAL_ [`System Error`][] is thrown. On IPv4, if `multicastInterface` is a valid address but does not match any From 31cde9fee9a07962ce885fdc24f864d94fe48aae Mon Sep 17 00:00:00 2001 From: Yilong Li Date: Tue, 4 Aug 2026 21:21:11 +0800 Subject: [PATCH 11/24] ffi: reuse libffi call plans MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Precompute a libffi call plan for each fixed signature on x86-64 System V and reuse it from the generic and SharedBuffer invokers. This avoids repeating argument-placement work for every call. Continue to use ffi_call() with libffi older than 3.7, on other ABIs, and when plan allocation fails. Signed-off-by: umuoy1 PR-URL: https://github.com/nodejs/node/pull/64958 Fixes: https://github.com/nodejs/node/issues/64562 Refs: https://github.com/libffi/libffi/commit/3cc6beb7d404e12d3458461d76557b79795816ff Reviewed-By: Matteo Collina Reviewed-By: GĂ¼rgĂ¼n DayıoÄŸlu Reviewed-By: Paolo Insogna Reviewed-By: Trivikram Kamat --- benchmark/ffi/invoke-function.js | 79 ++++++++++++++++++++++++++++++++ src/ffi/fast.cc | 24 ++++++++-- src/node_ffi.cc | 37 +++++++++++---- src/node_ffi.h | 29 ++++++++++-- 4 files changed, 152 insertions(+), 17 deletions(-) create mode 100644 benchmark/ffi/invoke-function.js diff --git a/benchmark/ffi/invoke-function.js b/benchmark/ffi/invoke-function.js new file mode 100644 index 000000000000..ae8d5b2ef795 --- /dev/null +++ b/benchmark/ffi/invoke-function.js @@ -0,0 +1,79 @@ +'use strict'; + +const assert = require('node:assert'); +const common = require('../common.js'); +const { libraryPath, ensureFixtureLibrary } = require('./common.js'); + +// Measure the invocation (call) path for signatures that bypass V8 Fast API +// and use libffi through FFIFunction::Invoke(). On x86-64 System V with +// libffi >= 3.7, Invoke() reuses a precomputed call plan that avoids repeating +// argument-placement work on every call. This benchmark quantifies the +// per-call benefit. +// +// Signatures chosen to bypass both V8 Fast API and keep native work minimal: +// - call_int_callback (null): 'function' type forces the generic path; null +// pointer triggers the early return in C so native computation is negligible. +// From libffi's perspective this is a register-only plan (2 pointer-sized +// args both fit in GP registers on x86-64 System V). +// - sum_8_i32: 8 GP args exceed the x86-64 Fast API register cap (6), forcing +// the generic path. From libffi's perspective 6 args go in registers and 2 +// spill to the stack, exercising a stack-spilled plan. + +const bench = common.createBenchmark(main, { + n: [1e7], + symbol: ['call_int_callback', 'sum_8_i32'], +}, { + flags: ['--experimental-ffi', '--no-warnings'], +}); + +ensureFixtureLibrary(); + +function main({ n, symbol }) { + const ffi = require('node:ffi'); + + if (symbol === 'call_int_callback') { + // 'function' type bypasses Fast API (IsFastCallEligible rejects it). + // Pass 0n (null function pointer) so the native function returns -1 + // immediately without invoking any callback, keeping per-call overhead + // dominated by the FFI call machinery itself. + const { lib, functions } = ffi.dlopen(libraryPath, { + call_int_callback: { return: 'i32', arguments: ['function', 'i32'] }, + }); + + try { + // Verify the null-pointer early return. + assert.strictEqual(functions.call_int_callback(0n, 7), -1); + + bench.start(); + for (let i = 0; i < n; ++i) + functions.call_int_callback(0n, 21); + bench.end(n); + } finally { + lib.close(); + } + } else { + // 8 integer args exceed the x86-64 SysV GP register cap (6), which makes + // CreateFastFFIMetadata reject the signature. Calls go through the + // SharedBuffer or generic invoker into FFIFunction::Invoke(). + const { lib, functions } = ffi.dlopen(libraryPath, { + sum_8_i32: { + return: 'i32', + arguments: [ + 'i32', 'i32', 'i32', 'i32', + 'i32', 'i32', 'i32', 'i32', + ], + }, + }); + + const fn = functions.sum_8_i32; + + assert.strictEqual(fn(1, 2, 3, 4, 5, 6, 7, 8), 36); + + bench.start(); + for (let i = 0; i < n; ++i) + fn(1, 2, 3, 4, 5, 6, 7, 14); + bench.end(n); + + lib.close(); + } +} diff --git a/src/ffi/fast.cc b/src/ffi/fast.cc index ca9a5715724b..80ee49e08b28 100644 --- a/src/ffi/fast.cc +++ b/src/ffi/fast.cc @@ -192,13 +192,31 @@ bool SignatureNeedsFastBufferInvoke(const FFIFunction& fn) { IsBufferTypeName(fn.arg_type_names[0])); } +namespace { + +std::shared_ptr CloneForFastMetadata( + const std::shared_ptr& fn) { + // Fast metadata only needs the native target and signature. In particular, + // its temporary clone must not borrow the original function's cif or plan. + auto clone = std::make_shared(); + clone->closed = fn->closed; + clone->ptr = fn->ptr; + clone->args = fn->args; + clone->return_type = fn->return_type; + clone->arg_type_names = fn->arg_type_names; + clone->return_type_name = fn->return_type_name; + return clone; +} + +} // namespace + std::shared_ptr CloneWithRawPointerArgNames( const std::shared_ptr& fn) { // The primary Fast API entrypoint receives pointer-compatible values as // BigInts after the JS wrapper has converted strings, nullish values, and // memory-backed objects. A secondary entrypoint handles the monomorphic // memory-backed case without extracting the pointer in JS. - auto clone = std::make_shared(*fn); + auto clone = CloneForFastMetadata(fn); for (std::string& name : clone->arg_type_names) { if (IsBufferTypeName(name)) { name = "pointer"; @@ -209,10 +227,10 @@ std::shared_ptr CloneWithRawPointerArgNames( std::shared_ptr CloneWithFastBufferArgNames( const std::shared_ptr& fn) { - // Reuse the same native target and libffi metadata, but describe the JS + // Reuse the same native target and signature metadata, but describe the JS // argument as `buffer` so CreateFastFFIMetadata() emits a trampoline that // receives a V8 value and calls node_ffi_fast_buffer_data(). - auto clone = std::make_shared(*fn); + auto clone = CloneForFastMetadata(fn); for (std::string& name : clone->arg_type_names) { if (IsPointerTypeName(name)) { name = "buffer"; diff --git a/src/node_ffi.cc b/src/node_ffi.cc index 6cce5e38e8e3..26a8f5a610cb 100644 --- a/src/node_ffi.cc +++ b/src/node_ffi.cc @@ -42,6 +42,17 @@ using v8::Value; namespace ffi { +void FFIFunction::Invoke(void* result, void** values) { +#if defined(NODE_FFI_HAS_FAST_CALL_PLAN) + if (call_plan != nullptr) { + ffi_call_plan_invoke(call_plan.get(), FFI_FN(ptr), result, values); + return; + } +#endif + + ffi_call(&cif, FFI_FN(ptr), result, values); +} + void FFIFunctionInfo::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("sb_backing", sb_backing); } @@ -146,14 +157,12 @@ Maybe DynamicLibrary::PrepareFunction( should_cache_symbol = symbols_.find(name) == symbols_.end(); - fn = std::make_shared( - FFIFunction{.closed = false, - .ptr = ptr, - .cif = {}, - .args = args, - .return_type = return_type, - .arg_type_names = std::move(arg_type_names), - .return_type_name = std::move(return_type_name)}); + fn = std::make_shared(); + fn->ptr = ptr; + fn->args = std::move(args); + fn->return_type = return_type; + fn->arg_type_names = std::move(arg_type_names); + fn->return_type_name = std::move(return_type_name); ffi_status status = ffi_prep_cif(&fn->cif, FFI_DEFAULT_ABI, @@ -178,6 +187,14 @@ Maybe DynamicLibrary::PrepareFunction( return {}; } +#if defined(NODE_FFI_HAS_FAST_CALL_PLAN) + // Allocation failure is non-fatal. Invoke() falls back to ffi_call(). + ffi_call_plan* call_plan = ffi_call_plan_alloc(&fn->cif); + if (call_plan != nullptr) { + fn->call_plan.reset(call_plan); + } +#endif + should_cache_function = true; } else { fn = existing->second; @@ -552,7 +569,7 @@ void DynamicLibrary::InvokeFunction(const FunctionCallbackInfo& args) { result = Malloc(GetFFIReturnValueStorageSize(fn->return_type)); } - ffi_call(&fn->cif, FFI_FN(fn->ptr), result, ffi_args.data()); + fn->Invoke(result, ffi_args.data()); // Return result back to Javascript ToJSReturnValue(env, args, fn->return_type, result); @@ -611,7 +628,7 @@ void DynamicLibrary::InvokeFunctionSB(const FunctionCallbackInfo& args) { alignas(8) uint8_t result_storage[kSBResultStorageSize] = {0}; void* result = (fn->return_type != &ffi_type_void) ? result_storage : nullptr; - ffi_call(&fn->cif, FFI_FN(fn->ptr), result, ffi_args.data()); + fn->Invoke(result, ffi_args.data()); if (result != nullptr) { WriteFFIReturnToBuffer(fn->return_type, result, buffer, 0); diff --git a/src/node_ffi.h b/src/node_ffi.h index a55cb74fc619..07bd0163db75 100644 --- a/src/node_ffi.h +++ b/src/node_ffi.h @@ -14,20 +14,41 @@ #include #include +// libffi only accelerates reusable call plans on x86-64 System V. Other +// targets implement the API by calling ffi_call(), which adds no benefit. +#if defined(FFI_VERSION_NUMBER) && FFI_VERSION_NUMBER >= 30700 && \ + defined(__x86_64__) && !defined(__ILP32__) && !defined(X86_WIN64) && \ + !defined(_WIN32) +#define NODE_FFI_HAS_FAST_CALL_PLAN 1 +#endif + namespace node::ffi { class DynamicLibrary; struct FFIFunction; struct FFIFunction { - bool closed; + FFIFunction() = default; + FFIFunction(const FFIFunction&) = delete; + FFIFunction& operator=(const FFIFunction&) = delete; + FFIFunction(FFIFunction&&) = delete; + FFIFunction& operator=(FFIFunction&&) = delete; - void* ptr; - ffi_cif cif; + bool closed = false; + + void* ptr = nullptr; + ffi_cif cif = {}; std::vector args; - ffi_type* return_type; + ffi_type* return_type = nullptr; std::vector arg_type_names; std::string return_type_name; +#if defined(NODE_FFI_HAS_FAST_CALL_PLAN) + // The plan borrows cif, so it must remain uniquely owned by this instance. + std::unique_ptr call_plan{ + nullptr, ffi_call_plan_free}; +#endif + + void Invoke(void* result, void** values); }; class FFIFunctionInfo final : public BaseObject { From 1576cb83b499e91cd0be24b759696490518f5672 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 4 Aug 2026 15:01:33 +0100 Subject: [PATCH 12/24] http2: increase default window sizes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Increase the default HTTP/2 stream window from 64KB (65535) to 4MB (4194304) and the default local connection window to 32MB (33554432). The default 64KB window limits throughput on high-latency connections to window_size / RTT. With a 250ms RTT, throughput is limited to 256KB/s. The new defaults improve throughput to 16MB/s (128Mbps) for the stream window and 128MB/s (1Gbps) for the connection window. Fixes: https://github.com/nodejs/node/issues/38426 Signed-off-by: Matteo Collina PR-URL: https://github.com/nodejs/node/pull/64623 Reviewed-By: Rafael Gonzaga Reviewed-By: Tim Perry Reviewed-By: James M Snell Reviewed-By: GĂ¼rgĂ¼n DayıoÄŸlu Reviewed-By: Ethan Arrowood Reviewed-By: Aviv Keller Reviewed-By: Robert Nagy Reviewed-By: Trivikram Kamat --- doc/api/http2.md | 2 +- lib/internal/http2/core.js | 11 ++++++- src/node_http2.cc | 11 +++++++ src/node_http2.h | 6 +++- src/stream_base.cc | 2 ++ src/stream_pipe.cc | 15 +++++++--- test/parallel/test-http2-binding.js | 4 +-- .../test-http2-client-setLocalWindowSize.js | 29 +++++++++---------- test/parallel/test-http2-getpackedsettings.js | 2 +- .../test-http2-pack-end-stream-flag.js | 6 ++-- test/parallel/test-http2-padding-aligned.js | 18 +++++++----- test/parallel/test-http2-perf_hooks.js | 6 +++- .../test-http2-server-setLocalWindowSize.js | 13 +++++---- .../test-http2-settings-unsolicited-ack.js | 7 +++-- test/parallel/test-http2-window-size.js | 2 +- .../test-http2-window-update-overflow.js | 4 +-- .../test-http2-max-session-memory.js | 2 +- .../test-http2-timeout-large-write-file.js | 11 ++----- .../test-http2-timeout-large-write.js | 11 ++----- 19 files changed, 96 insertions(+), 66 deletions(-) diff --git a/doc/api/http2.md b/doc/api/http2.md index cea1e6ef5a29..d53548f76730 100644 --- a/doc/api/http2.md +++ b/doc/api/http2.md @@ -3656,7 +3656,7 @@ properties. permitted on the `Http2Session` instances. **Default:** `true`. * `initialWindowSize` {number} Specifies the _sender's_ initial window size in bytes for stream-level flow control. The minimum allowed value is 0. The - maximum allowed value is 232-1. **Default:** `65535`. + maximum allowed value is 232-1. **Default:** `4194304`. * `maxFrameSize` {number} Specifies the size in bytes of the largest frame payload. The minimum allowed value is 16,384. The maximum allowed value is 224-1. **Default:** `16384`. diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 4a0ba7330e03..200471dca0fd 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -1205,7 +1205,16 @@ function setupHandle(socket, type, options) { } const settings = typeof options.settings === 'object' ? - options.settings : {}; + { ...options.settings } : {}; + + // Increase the default initial window size to improve throughput + // on high-latency connections. The HTTP/2 default of 65535 (64KB) + // limits throughput to window_size / RTT. By increasing to 4MB, + // throughput is significantly improved. + // See https://github.com/nodejs/node/issues/38426 + if (settings.initialWindowSize === undefined) { + settings.initialWindowSize = constants.DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE; + } this.settings(settings); diff --git a/src/node_http2.cc b/src/node_http2.cc index 4ba45649e787..04b2acca148d 100644 --- a/src/node_http2.cc +++ b/src/node_http2.cc @@ -612,6 +612,17 @@ Http2Session::Http2Session(Http2State* http2_state, &alloc_info), 0); session_.reset(session); + // Increase the default local connection window to improve throughput + // on high-latency connections. The default 64KB window limits throughput + // to window_size / RTT. With a 32MB connection window, throughput is + // significantly improved. See https://github.com/nodejs/node/issues/38426 + CHECK_EQ(nghttp2_session_set_local_window_size( + session, + NGHTTP2_FLAG_NONE, + 0, + DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE), + 0); + outgoing_storage_.reserve(1024); outgoing_buffers_.reserve(32); diff --git a/src/node_http2.h b/src/node_http2.h index ddfcb29166f4..b81862b27bce 100644 --- a/src/node_http2.h +++ b/src/node_http2.h @@ -43,7 +43,7 @@ constexpr uint64_t kDefaultMaxSessionMemory = 10000000; constexpr uint32_t DEFAULT_SETTINGS_HEADER_TABLE_SIZE = 4096; constexpr uint32_t DEFAULT_SETTINGS_ENABLE_PUSH = 1; constexpr uint32_t DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS = 0xffffffffu; -constexpr uint32_t DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE = 65535; +constexpr uint32_t DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE = 4194304; constexpr uint32_t DEFAULT_SETTINGS_MAX_FRAME_SIZE = 16384; constexpr uint32_t DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE = 65535; constexpr uint32_t DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0; @@ -51,6 +51,10 @@ constexpr uint32_t MAX_MAX_FRAME_SIZE = 16777215; constexpr uint32_t MIN_MAX_FRAME_SIZE = DEFAULT_SETTINGS_MAX_FRAME_SIZE; constexpr uint32_t MAX_INITIAL_WINDOW_SIZE = 2147483647; +// Default local connection window size (32MB) to improve throughput +// on high-latency connections. See https://github.com/nodejs/node/issues/38426 +constexpr uint32_t DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE = 33554432; + // Stream is not going to have any DATA frames constexpr int STREAM_OPTION_EMPTY_PAYLOAD = 0x1; diff --git a/src/stream_base.cc b/src/stream_base.cc index 370b8f682ead..3efeaa6df1eb 100644 --- a/src/stream_base.cc +++ b/src/stream_base.cc @@ -752,6 +752,8 @@ void CustomBufferJSListener::OnStreamRead(ssize_t nread, const uv_buf_t& buf) { void ReportWritesToJSStreamListener::OnStreamAfterReqFinished( StreamReq* req_wrap, int status) { StreamBase* stream = static_cast(stream_); + if (stream == nullptr) return; + if (req_wrap == nullptr) return; Environment* env = stream->stream_env(); if (!env->can_call_into_js()) return; AsyncWrap* async_wrap = req_wrap->GetAsyncWrap(); diff --git a/src/stream_pipe.cc b/src/stream_pipe.cc index 512c3f16e441..e626e103daf8 100644 --- a/src/stream_pipe.cc +++ b/src/stream_pipe.cc @@ -59,7 +59,7 @@ void StreamPipe::Unpipe(bool is_in_deletion) { is_closed_ = true; is_reading_ = false; source()->RemoveStreamListener(&readable_listener_); - if (pending_writes_ == 0) + if (pending_writes_ == 0 || sink_destroyed_) sink()->RemoveStreamListener(&writable_listener_); if (is_in_deletion) return; @@ -159,13 +159,18 @@ void StreamPipe::WritableListener::OnStreamAfterWrite(WriteWrap* w, StreamPipe* pipe = ContainerOf(&StreamPipe::writable_listener_, this); pipe->pending_writes_--; if (pipe->is_closed_) { - if (pipe->pending_writes_ == 0) { + // If the sink has been destroyed, pending_writes_ may have been + // reset and we should check <= 0 instead of == 0. Also guard + // against the listener having already been removed. + bool writes_done = pipe->sink_destroyed_ ? pipe->pending_writes_ <= 0 + : pipe->pending_writes_ == 0; + if (writes_done) { Environment* env = pipe->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); if (pipe->MakeCallback(env->oncomplete_string(), 0, nullptr).IsEmpty()) return; - stream()->RemoveStreamListener(this); + if (stream() != nullptr) stream()->RemoveStreamListener(this); } return; } @@ -213,7 +218,9 @@ void StreamPipe::WritableListener::OnStreamDestroy() { StreamPipe* pipe = ContainerOf(&StreamPipe::writable_listener_, this); pipe->sink_destroyed_ = true; pipe->is_eof_ = true; - pipe->pending_writes_ = 0; + // Don't reset pending_writes_ here; let OnStreamAfterWrite track + // completion naturally. Unpipe() will remove this listener from the + // sink regardless of pending_writes_ since sink_destroyed_ is set. pipe->Unpipe(); } diff --git a/test/parallel/test-http2-binding.js b/test/parallel/test-http2-binding.js index 7a91b2ba7406..bc3058d80c14 100644 --- a/test/parallel/test-http2-binding.js +++ b/test/parallel/test-http2-binding.js @@ -17,7 +17,7 @@ const settings = http2.getDefaultSettings(); assert.strictEqual(settings.headerTableSize, 4096); assert.strictEqual(settings.enablePush, true); assert.strictEqual(settings.maxConcurrentStreams, 4294967295); -assert.strictEqual(settings.initialWindowSize, 65535); +assert.strictEqual(settings.initialWindowSize, 4194304); assert.strictEqual(settings.maxFrameSize, 16384); assert.strictEqual(binding.nghttp2ErrorString(-517), @@ -239,7 +239,7 @@ const defaultSettings = { DEFAULT_SETTINGS_HEADER_TABLE_SIZE: 4096, DEFAULT_SETTINGS_ENABLE_PUSH: 1, DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS: 4294967295, - DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE: 65535, + DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE: 4194304, DEFAULT_SETTINGS_MAX_FRAME_SIZE: 16384, DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE: 65535, DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL: 0 diff --git a/test/parallel/test-http2-client-setLocalWindowSize.js b/test/parallel/test-http2-client-setLocalWindowSize.js index 8e3b57ed0c1a..700aee07e865 100644 --- a/test/parallel/test-http2-client-setLocalWindowSize.js +++ b/test/parallel/test-http2-client-setLocalWindowSize.js @@ -73,15 +73,16 @@ const http2 = require('http2'); client.on('connect', common.mustCall(() => { const windowSize = 2 ** 20; - const defaultSetting = http2.getDefaultSettings(); client.setLocalWindowSize(windowSize); assert.strictEqual(client.state.effectiveLocalWindowSize, windowSize); - assert.strictEqual(client.state.localWindowSize, windowSize); - assert.strictEqual( - client.state.remoteWindowSize, - defaultSetting.initialWindowSize - ); + // localWindowSize returns the available connection window. + // When decreasing from the default 33554432 to 1048576, + // the available window stays at 33554432. + assert.strictEqual(client.state.localWindowSize, 33554432); + // remoteWindowSize is the connection-level send window, + // which remains at the HTTP/2 default of 65535. + assert.strictEqual(client.state.remoteWindowSize, 65535); server.close(); client.close(); @@ -101,18 +102,16 @@ const http2 = require('http2'); client.on('connect', common.mustCall(() => { const windowSize = 20; - const defaultSetting = http2.getDefaultSettings(); client.setLocalWindowSize(windowSize); assert.strictEqual(client.state.effectiveLocalWindowSize, windowSize); - assert.strictEqual( - client.state.localWindowSize, - defaultSetting.initialWindowSize - ); - assert.strictEqual( - client.state.remoteWindowSize, - defaultSetting.initialWindowSize - ); + // localWindowSize returns the available connection window. + // When decreasing from the default 33554432 to 20, + // the available window stays at 33554432. + assert.strictEqual(client.state.localWindowSize, 33554432); + // remoteWindowSize is the connection-level send window, + // which remains at the HTTP/2 default of 65535. + assert.strictEqual(client.state.remoteWindowSize, 65535); server.close(); client.close(); diff --git a/test/parallel/test-http2-getpackedsettings.js b/test/parallel/test-http2-getpackedsettings.js index d238eb8c7d1b..a7a1701cccca 100644 --- a/test/parallel/test-http2-getpackedsettings.js +++ b/test/parallel/test-http2-getpackedsettings.js @@ -9,7 +9,7 @@ const http2 = require('http2'); const check = Buffer.from([0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x04, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x04, 0x00, 0x40, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x40, 0x00, 0x00, 0x06, 0x00, 0x00, 0xff, 0xff, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00]); diff --git a/test/parallel/test-http2-pack-end-stream-flag.js b/test/parallel/test-http2-pack-end-stream-flag.js index 9c0b3246f35e..a1f496c098fe 100644 --- a/test/parallel/test-http2-pack-end-stream-flag.js +++ b/test/parallel/test-http2-pack-end-stream-flag.js @@ -52,8 +52,10 @@ function testRequest(path, targetFrameCount, callback) { }); } -// SETTINGS => SETTINGS => HEADERS => DATA -const MIN_FRAME_COUNT = 4; +// SETTINGS => WINDOW_UPDATE => SETTINGS ACK => HEADERS => DATA +// The WINDOW_UPDATE frame is sent because the default local connection +// window is now increased to 32MB (see https://github.com/nodejs/node/issues/38426) +const MIN_FRAME_COUNT = 5; server.listen(0, () => { testRequest('/singleEnd', MIN_FRAME_COUNT, () => { diff --git a/test/parallel/test-http2-padding-aligned.js b/test/parallel/test-http2-padding-aligned.js index 022998543618..940a121c5e35 100644 --- a/test/parallel/test-http2-padding-aligned.js +++ b/test/parallel/test-http2-padding-aligned.js @@ -25,18 +25,20 @@ const { duplexPair } = require('stream'); // The lengths of the expected writes... note that this is highly // sensitive to how the internals are implemented. - const serverLengths = [24, 9, 9, 32]; - const clientLengths = [9, 9, 48, 9, 1, 21, 1]; + const serverLengths = [24, 15, 9, 13, 32]; + const clientLengths = [15, 13, 9, 48, 9, 1, 21, 1]; - // Adjust for the 24-byte preamble and two 9-byte settings frames, and - // the result must be equally divisible by 8 + // Adjust for the 24-byte preamble, 15-byte settings frame (with + // initialWindowSize), 13-byte window update frame, and 9-byte settings + // ack, and the result must be equally divisible by 8 assert.strictEqual( - (serverLengths.reduce((i, n) => i + n) - 24 - 9 - 9) % 8, 0); + (serverLengths.reduce((i, n) => i + n) - 24 - 15 - 13 - 9) % 8, 0); - // Adjust for two 9-byte settings frames, and the result must be equally - // divisible by 8 + // Adjust for the 15-byte settings frame (with initialWindowSize), + // 13-byte window update frame, and 9-byte settings ack, and the result + // must be equally divisible by 8 assert.strictEqual( - (clientLengths.reduce((i, n) => i + n) - 9 - 9) % 8, 0); + (clientLengths.reduce((i, n) => i + n) - 15 - 13 - 9) % 8, 0); serverSide.on('data', common.mustCall((chunk) => { assert.strictEqual(chunk.length, serverLengths.shift()); diff --git a/test/parallel/test-http2-perf_hooks.js b/test/parallel/test-http2-perf_hooks.js index 1e72801147a7..6ab55b0dc6d8 100644 --- a/test/parallel/test-http2-perf_hooks.js +++ b/test/parallel/test-http2-perf_hooks.js @@ -31,7 +31,11 @@ const obs = new PerformanceObserver(common.mustCallAtLeast((items) => { break; case 'client': assert.strictEqual(entry.detail.streamCount, 1); - assert.strictEqual(entry.detail.framesReceived, 7); + // With the default window size increase (#38426), the server now + // sends a WINDOW_UPDATE frame during session setup, increasing + // the frames received by the client from 7 to 8. + assert.ok(entry.detail.framesReceived >= 7, + `expected >=7 frames but got ${entry.detail.framesReceived}`); break; default: assert.fail('invalid Http2Session type'); diff --git a/test/parallel/test-http2-server-setLocalWindowSize.js b/test/parallel/test-http2-server-setLocalWindowSize.js index 8fcb9b9d0d81..94cea676daf2 100644 --- a/test/parallel/test-http2-server-setLocalWindowSize.js +++ b/test/parallel/test-http2-server-setLocalWindowSize.js @@ -14,15 +14,16 @@ server.on('stream', common.mustCall((stream) => { })); server.on('session', common.mustCall((session) => { const windowSize = 2 ** 20; - const defaultSetting = http2.getDefaultSettings(); session.setLocalWindowSize(windowSize); assert.strictEqual(session.state.effectiveLocalWindowSize, windowSize); - assert.strictEqual(session.state.localWindowSize, windowSize); - assert.strictEqual( - session.state.remoteWindowSize, - defaultSetting.initialWindowSize - ); + // localWindowSize returns the available connection window. + // When decreasing from the default 33554432 to 1048576, + // the available window stays at 33554432. + assert.strictEqual(session.state.localWindowSize, 33554432); + // remoteWindowSize is the connection-level send window, + // which remains at the HTTP/2 default of 65535. + assert.strictEqual(session.state.remoteWindowSize, 65535); })); server.listen(0, common.mustCall(() => { diff --git a/test/parallel/test-http2-settings-unsolicited-ack.js b/test/parallel/test-http2-settings-unsolicited-ack.js index 7c87fd478fd8..8b0ef131cd1b 100644 --- a/test/parallel/test-http2-settings-unsolicited-ack.js +++ b/test/parallel/test-http2-settings-unsolicited-ack.js @@ -35,8 +35,11 @@ server.listen(0, common.mustCall(() => { // servers are received, so that the first ack is actually expected. client.once('data', common.mustCall((chunk) => { // The very first chunk of data we get from the server should - // be a settings frame. - assert.deepStrictEqual(chunk.slice(0, 9), kSettings.data); + // be a settings frame. The server now sends an initialWindowSize + // setting by default (6 bytes payload). + assert.deepStrictEqual(chunk.slice(0, 9), Buffer.from([ + 0, 0, 6, 4, 0, 0, 0, 0, 0, + ])); // The first ack is expected. client.write(kSettingsAck.data, () => countdown.dec()); // The second one is not and will be ignored. diff --git a/test/parallel/test-http2-window-size.js b/test/parallel/test-http2-window-size.js index d0ae48361ef4..e21b6683762f 100644 --- a/test/parallel/test-http2-window-size.js +++ b/test/parallel/test-http2-window-size.js @@ -89,7 +89,7 @@ const initialWindowSizeList = [ (1 << 8) - 1, 1 << 8, 1 << 17, - undefined, // Use default window size which is (1 << 16) - 1 + undefined, // Use default window size which is now 4194304 (4MB) ]; // Call `run` on each element in the cartesian product of buffersList and diff --git a/test/parallel/test-http2-window-update-overflow.js b/test/parallel/test-http2-window-update-overflow.js index 41488af9b08f..326bc6572fb9 100644 --- a/test/parallel/test-http2-window-update-overflow.js +++ b/test/parallel/test-http2-window-update-overflow.js @@ -64,8 +64,8 @@ server.listen(0, common.mustCall(() => { conn.write(ack); // WINDOW_UPDATE on stream 0 (connection level) with increment 2^31-1. - // Default connection window is 65535, so the new total would be - // 65535 + 2147483647 = 2147549182 > 2^31-1, triggering + // Default connection window is now 33554432, so the new total would be + // 33554432 + 2147483647 = 2181038079 > 2^31-1, triggering // NGHTTP2_ERR_FLOW_CONTROL inside nghttp2. const windowUpdate = Buffer.alloc(13); windowUpdate.writeUIntBE(4, 0, 3); // length = 4 diff --git a/test/sequential/test-http2-max-session-memory.js b/test/sequential/test-http2-max-session-memory.js index 51560b31299d..8103d1685930 100644 --- a/test/sequential/test-http2-max-session-memory.js +++ b/test/sequential/test-http2-max-session-memory.js @@ -8,7 +8,7 @@ const http2 = require('http2'); // Test that maxSessionMemory Caps work -const largeBuffer = Buffer.alloc(2e6); +const largeBuffer = Buffer.alloc(8e6); const server = http2.createServer({ maxSessionMemory: 1 }); diff --git a/test/sequential/test-http2-timeout-large-write-file.js b/test/sequential/test-http2-timeout-large-write-file.js index c0675ced2c13..74ba4b77a79a 100644 --- a/test/sequential/test-http2-timeout-large-write-file.js +++ b/test/sequential/test-http2-timeout-large-write-file.js @@ -2,7 +2,6 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -const assert = require('assert'); const fixtures = require('../common/fixtures'); const fs = require('fs'); const http2 = require('http2'); @@ -22,12 +21,10 @@ tmpdir.refresh(); // that the backing stream is still active and writing // 4) Our timer fires, we resume the socket and start at 1) -const writeSize = 3000000; +const writeSize = 33554432; const minReadSize = 500000; const serverTimeout = common.platformTimeout(500); let offsetTimeout = common.platformTimeout(100); -let didReceiveData = false; - const content = Buffer.alloc(writeSize, 0x44); const filepath = tmpdir.resolve('http2-large-write.tmp'); fs.writeFileSync(filepath, content, 'binary'); @@ -47,9 +44,7 @@ server.on('stream', common.mustCall((stream) => { stream.end(); })); server.setTimeout(serverTimeout); -server.on('timeout', common.mustCallAtLeast(() => { - assert.ok(!didReceiveData, 'Should not timeout'); -}, 0)); +server.on('timeout', common.mustCallAtLeast(0)); server.listen(0, common.mustCall(() => { const client = http2.connect(`https://localhost:${server.address().port}`, @@ -63,13 +58,11 @@ server.listen(0, common.mustCall(() => { let firstReceivedAt; req.on('data', common.mustCallAtLeast((buf) => { if (receivedBufferLength === 0) { - didReceiveData = false; firstReceivedAt = Date.now(); } receivedBufferLength += buf.length; if (receivedBufferLength >= minReadSize && receivedBufferLength < writeSize) { - didReceiveData = true; receivedBufferLength = 0; req.pause(); setTimeout( diff --git a/test/sequential/test-http2-timeout-large-write.js b/test/sequential/test-http2-timeout-large-write.js index ea5fd1973b71..65000958184d 100644 --- a/test/sequential/test-http2-timeout-large-write.js +++ b/test/sequential/test-http2-timeout-large-write.js @@ -2,7 +2,6 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -const assert = require('assert'); const fixtures = require('../common/fixtures'); const http2 = require('http2'); @@ -18,19 +17,15 @@ const http2 = require('http2'); // that the backing stream is still active and writing // 4) Our timer fires, we resume the socket and start at 1) -const writeSize = 3000000; +const writeSize = 33554432; const minReadSize = 500000; const serverTimeout = common.platformTimeout(500); let offsetTimeout = common.platformTimeout(100); -let didReceiveData = false; - const server = http2.createSecureServer({ key: fixtures.readKey('agent1-key.pem'), cert: fixtures.readKey('agent1-cert.pem'), }); -const onTimeout = common.mustCallAtLeast(() => { - assert.ok(!didReceiveData, 'Should not timeout'); -}, 0); +const onTimeout = common.mustCallAtLeast(0); server.on('stream', common.mustCall((stream) => { const content = Buffer.alloc(writeSize, 0x44); @@ -60,13 +55,11 @@ server.listen(0, common.mustCall(() => { let firstReceivedAt; req.on('data', common.mustCallAtLeast((buf) => { if (receivedBufferLength === 0) { - didReceiveData = false; firstReceivedAt = Date.now(); } receivedBufferLength += buf.length; if (receivedBufferLength >= minReadSize && receivedBufferLength < writeSize) { - didReceiveData = true; receivedBufferLength = 0; req.pause(); setTimeout( From 500d0482df873ef57970e171ffd221aa53d05db3 Mon Sep 17 00:00:00 2001 From: Paul Bouchon Date: Tue, 4 Aug 2026 11:03:13 -0400 Subject: [PATCH 13/24] test: prefer in-memory databases in sqlite tests Several SQLite tests created temporary file databases through a `nextDb()` helper even though they only exercise SQL behavior and never rely on filesystem persistence. Switch those to `:memory:`, which is faster and drops the temporary-file bookkeeping. Tests that depend on an on-disk or shared database keep using files: the constructor, open() and backup() cases, the timeout and cross-worker suites, and the WAL journal-mode PRAGMA. Refs: https://github.com/nodejs/node/issues/64665 Signed-off-by: Paul Bouchon PR-URL: https://github.com/nodejs/node/pull/64701 Reviewed-By: Matteo Collina Reviewed-By: Edy Silva Reviewed-By: Trivikram Kamat Reviewed-By: Colin Ihrig --- test/parallel/test-sqlite-data-types.js | 19 ++-- test/parallel/test-sqlite-named-parameters.js | 17 +--- test/parallel/test-sqlite-statement-sync.js | 87 +++++++++---------- test/parallel/test-sqlite-transactions.js | 13 +-- .../test-sqlite-typed-array-and-data-view.js | 11 +-- test/parallel/test-sqlite.js | 5 +- 6 files changed, 54 insertions(+), 98 deletions(-) diff --git a/test/parallel/test-sqlite-data-types.js b/test/parallel/test-sqlite-data-types.js index 26af15a777d2..5e51f17733c5 100644 --- a/test/parallel/test-sqlite-data-types.js +++ b/test/parallel/test-sqlite-data-types.js @@ -1,22 +1,13 @@ 'use strict'; const { skipIfSQLiteMissing } = require('../common'); skipIfSQLiteMissing(); -const tmpdir = require('../common/tmpdir'); -const { join } = require('node:path'); const { DatabaseSync } = require('node:sqlite'); const { suite, test } = require('node:test'); -let cnt = 0; - -tmpdir.refresh(); - -function nextDb() { - return join(tmpdir.path, `database-${cnt++}.db`); -} suite('data binding and mapping', () => { test('supported data types', (t) => { const u8a = new TextEncoder().encode('a☃b☃c'); - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE types( @@ -83,7 +74,7 @@ suite('data binding and mapping', () => { }); test('large strings are bound correctly', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, text TEXT) STRICT;' @@ -118,7 +109,7 @@ suite('data binding and mapping', () => { }); test('unsupported data types', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -153,7 +144,7 @@ suite('data binding and mapping', () => { test('throws when binding a BigInt that is too large', (t) => { const max = 9223372036854775807n; // Largest 64-bit signed integer value. - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -173,7 +164,7 @@ suite('data binding and mapping', () => { }); test('statements are unbound on each call', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' diff --git a/test/parallel/test-sqlite-named-parameters.js b/test/parallel/test-sqlite-named-parameters.js index db8f46e6b6ce..2fd6fb0da1c3 100644 --- a/test/parallel/test-sqlite-named-parameters.js +++ b/test/parallel/test-sqlite-named-parameters.js @@ -1,21 +1,12 @@ 'use strict'; const { skipIfSQLiteMissing } = require('../common'); skipIfSQLiteMissing(); -const tmpdir = require('../common/tmpdir'); -const { join } = require('node:path'); const { DatabaseSync } = require('node:sqlite'); const { suite, test } = require('node:test'); -let cnt = 0; - -tmpdir.refresh(); - -function nextDb() { - return join(tmpdir.path, `database-${cnt++}.db`); -} suite('named parameters', () => { test('throws on unknown named parameters', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -32,7 +23,7 @@ suite('named parameters', () => { }); test('bare named parameters are supported', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -47,7 +38,7 @@ suite('named parameters', () => { }); test('duplicate bare named parameters are supported', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -62,7 +53,7 @@ suite('named parameters', () => { }); test('bare named parameters throw on ambiguous names', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' diff --git a/test/parallel/test-sqlite-statement-sync.js b/test/parallel/test-sqlite-statement-sync.js index b3a1dc434537..c353c8035c5f 100644 --- a/test/parallel/test-sqlite-statement-sync.js +++ b/test/parallel/test-sqlite-statement-sync.js @@ -2,17 +2,8 @@ 'use strict'; const { skipIfSQLiteMissing } = require('../common'); skipIfSQLiteMissing(); -const tmpdir = require('../common/tmpdir'); -const { join } = require('node:path'); const { DatabaseSync, StatementSync } = require('node:sqlite'); const { suite, test } = require('node:test'); -let cnt = 0; - -tmpdir.refresh(); - -function nextDb() { - return join(tmpdir.path, `database-${cnt++}.db`); -} suite('StatementSync() constructor', () => { test('StatementSync cannot be constructed directly', (t) => { @@ -27,7 +18,7 @@ suite('StatementSync() constructor', () => { suite('StatementSync.prototype.get()', () => { test('executes a query and returns undefined on no results', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); let stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); t.assert.strictEqual(stmt.get(), undefined); @@ -36,7 +27,7 @@ suite('StatementSync.prototype.get()', () => { }); test('executes a query and returns the first result', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); let stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); t.assert.strictEqual(stmt.get(), undefined); @@ -48,7 +39,7 @@ suite('StatementSync.prototype.get()', () => { }); test('executes a query that returns special columns', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const stmt = db.prepare('SELECT 1 as __proto__, 2 as constructor, 3 as toString'); t.assert.deepStrictEqual(stmt.get(), { __proto__: null, ['__proto__']: 1, constructor: 2, toString: 3 }); @@ -80,14 +71,14 @@ suite('StatementSync.prototype.get()', () => { suite('StatementSync.prototype.all()', () => { test('executes a query and returns an empty array on no results', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); t.assert.deepStrictEqual(stmt.all(), []); }); test('executes a query and returns all results', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); let stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); t.assert.deepStrictEqual(stmt.run(), { changes: 0, lastInsertRowid: 0 }); @@ -133,7 +124,7 @@ suite('StatementSync.prototype.all()', () => { suite('StatementSync.prototype.iterate()', () => { test('executes a query and returns an empty iterator on no results', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); const iter = stmt.iterate(); @@ -143,7 +134,7 @@ suite('StatementSync.prototype.iterate()', () => { }); test('executes a query and returns all results', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); let stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); t.assert.deepStrictEqual(stmt.run(), { changes: 0, lastInsertRowid: 0 }); @@ -299,7 +290,7 @@ suite('StatementSync.prototype.iterate()', () => { suite('StatementSync.prototype.run()', () => { test('executes a query and returns change metadata', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE storage(key TEXT, val TEXT); @@ -311,7 +302,7 @@ suite('StatementSync.prototype.run()', () => { }); test('SQLite throws when trying to bind too many parameters', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -329,7 +320,7 @@ suite('StatementSync.prototype.run()', () => { }); test('SQLite defaults to NULL for unbound parameters', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER NOT NULL) STRICT;' @@ -366,7 +357,7 @@ suite('StatementSync.prototype.run()', () => { }); test('SQLite defaults unbound ?NNN parameters', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER NOT NULL) STRICT;' @@ -385,7 +376,7 @@ suite('StatementSync.prototype.run()', () => { }); test('binds ?NNN params by position', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER NOT NULL) STRICT;' @@ -398,7 +389,7 @@ suite('StatementSync.prototype.run()', () => { suite('StatementSync.prototype.sourceSQL', () => { test('equals input SQL', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -412,7 +403,7 @@ suite('StatementSync.prototype.sourceSQL', () => { suite('StatementSync.prototype.expandedSQL', () => { test('equals expanded SQL', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -431,7 +422,7 @@ suite('StatementSync.prototype.expandedSQL', () => { suite('StatementSync.prototype.setReadBigInts()', () => { test('BigInts support can be toggled', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT; @@ -464,7 +455,7 @@ suite('StatementSync.prototype.setReadBigInts()', () => { }); test('throws when input is not a boolean', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -480,7 +471,7 @@ suite('StatementSync.prototype.setReadBigInts()', () => { }); test('BigInt is required for reading large integers', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const bad = db.prepare(`SELECT ${Number.MAX_SAFE_INTEGER} + 1`); t.assert.throws(() => { @@ -500,7 +491,7 @@ suite('StatementSync.prototype.setReadBigInts()', () => { suite('StatementSync.prototype.setReturnArrays()', () => { test('throws when input is not a boolean', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -518,7 +509,7 @@ suite('StatementSync.prototype.setReturnArrays()', () => { suite('StatementSync.prototype.get() with array output', () => { test('returns array row when setReturnArrays is true', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT; @@ -538,7 +529,7 @@ suite('StatementSync.prototype.get() with array output', () => { test('returns array rows with BigInts when both flags are set', (t) => { const expected = [1n, 9007199254740992n]; - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE big_data(id INTEGER, big_num INTEGER); @@ -557,7 +548,7 @@ suite('StatementSync.prototype.get() with array output', () => { suite('StatementSync.prototype.all() with array output', () => { test('returns array rows when setReturnArrays is true', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT; @@ -598,7 +589,7 @@ suite('StatementSync.prototype.all() with array output', () => { 9, 'text3', ]; - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE wide_table( @@ -623,7 +614,7 @@ suite('StatementSync.prototype.all() with array output', () => { suite('StatementSync.prototype.iterate() with array output', () => { test('iterates array rows when setReturnArrays is true', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT; @@ -692,7 +683,7 @@ suite('StatementSync.prototype.iterate() with array output', () => { suite('StatementSync.prototype.setAllowBareNamedParameters()', () => { test('bare named parameter support can be toggled', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -718,7 +709,7 @@ suite('StatementSync.prototype.setAllowBareNamedParameters()', () => { }); test('throws when input is not a boolean', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -736,7 +727,7 @@ suite('StatementSync.prototype.setAllowBareNamedParameters()', () => { suite('options.readBigInts', () => { test('BigInts are returned when input is true', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT; @@ -749,7 +740,7 @@ suite('options.readBigInts', () => { }); test('numbers are returned when input is false', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT; @@ -762,7 +753,7 @@ suite('options.readBigInts', () => { }); test('throws when input is not a boolean', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -777,7 +768,7 @@ suite('options.readBigInts', () => { }); test('setReadBigInts can override prepare option', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT; @@ -794,7 +785,7 @@ suite('options.readBigInts', () => { suite('options.returnArrays', () => { test('arrays are returned when input is true', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT; @@ -810,7 +801,7 @@ suite('options.returnArrays', () => { }); test('objects are returned when input is false', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT; @@ -826,7 +817,7 @@ suite('options.returnArrays', () => { }); test('throws when input is not a boolean', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT;' @@ -841,7 +832,7 @@ suite('options.returnArrays', () => { }); test('setReturnArrays can override prepare option', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT; @@ -859,7 +850,7 @@ suite('options.returnArrays', () => { }); test('all() returns arrays when input is true', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT; @@ -879,7 +870,7 @@ suite('options.returnArrays', () => { }); test('iterate() returns arrays when input is true', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT; @@ -901,7 +892,7 @@ suite('options.returnArrays', () => { suite('options.allowBareNamedParameters', () => { test('bare named parameters are allowed when input is true', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -918,7 +909,7 @@ suite('options.allowBareNamedParameters', () => { }); test('bare named parameters throw when input is false', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -937,7 +928,7 @@ suite('options.allowBareNamedParameters', () => { }); test('throws when input is not a boolean', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -955,7 +946,7 @@ suite('options.allowBareNamedParameters', () => { }); test('setAllowBareNamedParameters can override prepare option', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' diff --git a/test/parallel/test-sqlite-transactions.js b/test/parallel/test-sqlite-transactions.js index 50b47829aca0..963e7126a101 100644 --- a/test/parallel/test-sqlite-transactions.js +++ b/test/parallel/test-sqlite-transactions.js @@ -1,21 +1,12 @@ 'use strict'; const { skipIfSQLiteMissing } = require('../common'); skipIfSQLiteMissing(); -const tmpdir = require('../common/tmpdir'); -const { join } = require('node:path'); const { DatabaseSync } = require('node:sqlite'); const { suite, test } = require('node:test'); -let cnt = 0; - -tmpdir.refresh(); - -function nextDb() { - return join(tmpdir.path, `database-${cnt++}.db`); -} suite('manual transactions', () => { test('a transaction is committed', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE data( @@ -42,7 +33,7 @@ suite('manual transactions', () => { }); test('a transaction is rolled back', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE data( diff --git a/test/parallel/test-sqlite-typed-array-and-data-view.js b/test/parallel/test-sqlite-typed-array-and-data-view.js index 71d7b181a3d7..2d5269be09b7 100644 --- a/test/parallel/test-sqlite-typed-array-and-data-view.js +++ b/test/parallel/test-sqlite-typed-array-and-data-view.js @@ -1,17 +1,8 @@ 'use strict'; const { skipIfSQLiteMissing } = require('../common'); skipIfSQLiteMissing(); -const tmpdir = require('../common/tmpdir'); -const { join } = require('node:path'); const { DatabaseSync } = require('node:sqlite'); const { suite, test } = require('node:test'); -let cnt = 0; - -tmpdir.refresh(); - -function nextDb() { - return join(tmpdir.path, `database-${cnt++}.db`); -} const arrayBuffer = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]).buffer; const TypedArrays = [ @@ -32,7 +23,7 @@ const TypedArrays = [ suite('StatementSync with TypedArray/DataView', () => { for (const [displayName, TypedArray] of TypedArrays) { test(displayName, (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); db.exec('CREATE TABLE test (data BLOB)'); // insert diff --git a/test/parallel/test-sqlite.js b/test/parallel/test-sqlite.js index ebbcd27d1345..b7b65f1258f0 100644 --- a/test/parallel/test-sqlite.js +++ b/test/parallel/test-sqlite.js @@ -36,7 +36,7 @@ suite('accessing the node:sqlite module', () => { }); test('ERR_SQLITE_ERROR is thrown for errors originating from SQLite', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec(` CREATE TABLE test( @@ -86,6 +86,7 @@ test('sqlite constants are defined', (t) => { }); test('PRAGMAs are supported', (t) => { + // WAL journal mode requires an on-disk database. const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); t.assert.deepStrictEqual( @@ -218,7 +219,7 @@ suite('SQL APIs enabled at build time', () => { }); test('dbstat is enabled', (t) => { - const db = new DatabaseSync(nextDb()); + const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); db.exec(` CREATE TABLE t1 (key INTEGER PRIMARY KEY); From 427d2e12030cff10d15bb19a5669840ed7f0fb43 Mon Sep 17 00:00:00 2001 From: Ryuhei Shima <65934663+islandryu@users.noreply.github.com> Date: Wed, 5 Aug 2026 00:03:32 +0900 Subject: [PATCH 14/24] build: add host toolset to perfetto_sdk Signed-off-by: islandryu PR-URL: https://github.com/nodejs/node/pull/64751 Reviewed-By: Aviv Keller --- deps/perfetto/perfetto.gyp | 1 + 1 file changed, 1 insertion(+) diff --git a/deps/perfetto/perfetto.gyp b/deps/perfetto/perfetto.gyp index 3836f3424cbd..083d0b386dd2 100644 --- a/deps/perfetto/perfetto.gyp +++ b/deps/perfetto/perfetto.gyp @@ -9,6 +9,7 @@ { 'target_name': 'perfetto_sdk', 'type': 'static_library', + 'toolsets': ['host', 'target'], 'include_dirs': [ 'sdk' ], 'direct_dependent_settings': { # Use like `#include "perfetto.h"` From 7cb4e7fc460b5f48d4dfc7e9120627f2563f95c8 Mon Sep 17 00:00:00 2001 From: Junsoo Ha <35479251+ganjanggejang@users.noreply.github.com> Date: Wed, 5 Aug 2026 00:58:10 +0900 Subject: [PATCH 15/24] ffi: support SharedArrayBuffer in getRawPointer Signed-off-by: Junsoo Ha PR-URL: https://github.com/nodejs/node/pull/64864 Reviewed-By: Paolo Insogna Reviewed-By: Trivikram Kamat --- doc/api/ffi.md | 2 +- src/ffi/data.cc | 10 ++++++---- test/ffi/test-ffi-memory.js | 7 +++++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/doc/api/ffi.md b/doc/api/ffi.md index 9df2a9789d98..09b78d64fc68 100644 --- a/doc/api/ffi.md +++ b/doc/api/ffi.md @@ -714,7 +714,7 @@ available storage. This function does not allocate memory on its own. added: v26.1.0 --> -* `source` {Buffer|ArrayBuffer|ArrayBufferView} +* `source` {Buffer|ArrayBuffer|SharedArrayBuffer|ArrayBufferView} * Returns: {bigint} Returns the raw memory address of JavaScript-managed byte storage. diff --git a/src/ffi/data.cc b/src/ffi/data.cc index 726edc5cd2dc..73b575395c8c 100644 --- a/src/ffi/data.cc +++ b/src/ffi/data.cc @@ -739,7 +739,8 @@ void GetRawPointer(const FunctionCallbackInfo& args) { if (args.Length() < 1) { THROW_ERR_INVALID_ARG_TYPE( env, - "The first argument must be a Buffer, ArrayBuffer, or ArrayBufferView"); + "The first argument must be a Buffer, ArrayBuffer, SharedArrayBuffer, " + "or ArrayBufferView"); return; } @@ -758,9 +759,10 @@ void GetRawPointer(const FunctionCallbackInfo& args) { store = args[0].As()->Buffer()->GetBackingStore(); offset = args[0].As()->ByteOffset(); } else { - THROW_ERR_INVALID_ARG_TYPE(env, - "The first argument must be a Buffer, " - "ArrayBuffer, or ArrayBufferView"); + THROW_ERR_INVALID_ARG_TYPE( + env, + "The first argument must be a Buffer, " + "ArrayBuffer, SharedArrayBuffer, or ArrayBufferView"); return; } diff --git a/test/ffi/test-ffi-memory.js b/test/ffi/test-ffi-memory.js index adc5a8539491..f17f56c410f8 100644 --- a/test/ffi/test-ffi-memory.js +++ b/test/ffi/test-ffi-memory.js @@ -125,18 +125,25 @@ test('ffi getRawPointer returns raw addresses for byte sources', () => { const buffer = Buffer.from([1, 2, 3]); const arrayBuffer = new Uint8Array([4, 5, 6, 7]).buffer; const view = new Uint8Array(arrayBuffer, 2); + const sharedArrayBuffer = new SharedArrayBuffer(4); + const sharedView = new Uint8Array(sharedArrayBuffer, 2); const bufferPointer = ffi.getRawPointer(buffer); const arrayBufferPointer = ffi.getRawPointer(arrayBuffer); const viewPointer = ffi.getRawPointer(view); + const sharedArrayBufferPointer = ffi.getRawPointer(sharedArrayBuffer); + const sharedViewPointer = ffi.getRawPointer(sharedView); assert.strictEqual(typeof bufferPointer, 'bigint'); assert.strictEqual(typeof arrayBufferPointer, 'bigint'); assert.strictEqual(typeof viewPointer, 'bigint'); + assert.strictEqual(typeof sharedArrayBufferPointer, 'bigint'); + assert.strictEqual(typeof sharedViewPointer, 'bigint'); assert.strictEqual(bufferPointer, symbols.pointer_to_usize(buffer)); assert.strictEqual(arrayBufferPointer, symbols.pointer_to_usize(arrayBuffer)); assert.strictEqual(viewPointer, arrayBufferPointer + 2n); + assert.strictEqual(sharedViewPointer, sharedArrayBufferPointer + 2n); }); test('ffi exportString and exportBuffer copy data into native memory', () => { From 2010da59f939845c8a92b729cd62f5cf34d7ea85 Mon Sep 17 00:00:00 2001 From: agape1225 <49804691+agape1225@users.noreply.github.com> Date: Wed, 5 Aug 2026 00:58:27 +0900 Subject: [PATCH 16/24] doc: use ffi.suffix in permission example The `--allow-ffi` example in cli.md hard-coded the Linux-only `.so` extension. node:ffi already exposes `suffix` for exactly this case, and doc/api/ffi.md's own examples use it. Do the same here. Assisted-by: Claude Sonnet 5 Signed-off-by: agape1225 <49804691+agape1225@users.noreply.github.com> PR-URL: https://github.com/nodejs/node/pull/64914 Reviewed-By: James M Snell Reviewed-By: Trivikram Kamat --- doc/api/cli.md | 4 ++-- doc/node.1 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index 8953971ac607..58b0d1151f75 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -208,8 +208,8 @@ starting Node.js. The [`node:ffi`][] module also requires the Example: ```js -const { DynamicLibrary } = require('node:ffi'); -const lib = new DynamicLibrary('mylib.so'); +const { DynamicLibrary, suffix } = require('node:ffi'); +const lib = new DynamicLibrary(`./mylib.${suffix}`); ``` ```console diff --git a/doc/node.1 b/doc/node.1 index ad5e0415a54e..6aede253510e 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -126,8 +126,8 @@ starting Node.js. The \fBnode:ffi\fR module also requires the \fB--experimental-ffi\fR flag and is only available in builds with FFI support. Example: .Bd -literal -const { DynamicLibrary } = require('node:ffi'); -const lib = new DynamicLibrary('mylib.so'); +const { DynamicLibrary, suffix } = require('node:ffi'); +const lib = new DynamicLibrary(`./mylib.${suffix}`); .Ed .Bd -literal $ node --permission --experimental-ffi index.js From bd2e6039212a2d4691104833425bd0df6ffbf2d3 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Tue, 4 Aug 2026 17:58:42 +0200 Subject: [PATCH 17/24] tools: remove `true` from branch name for auto-update automation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Antoine du Hamel PR-URL: https://github.com/nodejs/node/pull/64961 Reviewed-By: Filip Skokan Reviewed-By: Moshe Atlow Reviewed-By: Marco Ippolito Reviewed-By: James M Snell Reviewed-By: Aviv Keller Reviewed-By: Tierney Cyren Reviewed-By: RenĂ© --- .github/workflows/tools.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 0008b0478f55..4e44737c116b 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -336,7 +336,7 @@ jobs: # no-op if the base branch is already up-to-date. with: token: ${{ secrets.GH_USER_TOKEN }} - branch: actions/${{ github.ref_name == 'main' || format('{0}/', github.ref_name) }}tools-update-${{ matrix.id }} # Custom branch *just* for this Action. + branch: actions/${{ github.ref_name != 'main' && format('{0}/', github.ref_name) || '' }}tools-update-${{ matrix.id }} # Custom branch *just* for this Action. delete-branch: true commit-message: ${{ env.COMMIT_MSG }} labels: ${{ matrix.label }} From 339062296ec44dbff71e053d424d1dec538f021d Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Tue, 4 Aug 2026 18:34:32 +0200 Subject: [PATCH 18/24] tools: store "default" OpenSSL version in `openssl-matrix.nix` To help with automating keeping in sync with the bundled version. Signed-off-by: Antoine du Hamel PR-URL: https://github.com/nodejs/node/pull/64962 Reviewed-By: Filip Skokan Reviewed-By: Aviv Keller --- tools/dep_updaters/update-nixpkgs-pin.sh | 25 ++++++++++++++++++++++-- tools/nix/openssl-matrix.nix | 5 ++++- tools/nix/pkcs11.nix | 2 +- tools/nix/sharedLibDeps.nix | 2 +- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/tools/dep_updaters/update-nixpkgs-pin.sh b/tools/dep_updaters/update-nixpkgs-pin.sh index a025676e20a7..b6efae468478 100755 --- a/tools/dep_updaters/update-nixpkgs-pin.sh +++ b/tools/dep_updaters/update-nixpkgs-pin.sh @@ -42,6 +42,14 @@ COMPAT_VERSION_SHA1=$(grep 'rev =' "$NIXPKGS_COMPAT_PIN_FILE" | awk -F'"' '{ pri COMPAT_UPSTREAM_SHA1=$(git ls-remote "$NIXPKGS_REPO.git" nixpkgs-26.05-darwin | awk '{print $1}') update_pkgs_file "$NIXPKGS_COMPAT_PIN_FILE" "$COMPAT_VERSION_SHA1" "$COMPAT_UPSTREAM_SHA1" +# === Update openssl-matrix.nix === +# When bumping the pin, we want to update the openssl-matrix.nix file to keep the list in sync nixpkgs +# i.e. add newly added release lines, remove newly dropped release lines), and make sure the "openssl" +# attribute still refers to the same release line as the bundled version in deps/openssl/. + +OPENSSL_MAJOR=$(awk -F= '/^MAJOR=[0-9]+$/ { print $2; exit }' "$BASE_DIR/deps/openssl/openssl/VERSION.dat") +OPENSSL_MINOR=$(awk -F= '/^MINOR=[0-9]+$/ { print $2; exit }' "$BASE_DIR/deps/openssl/openssl/VERSION.dat") + nix-instantiate -I "nixpkgs=$NIXPKGS_PIN_FILE" --eval --strict --json -E " let pkgs = import {}; @@ -49,15 +57,24 @@ nix-instantiate -I "nixpkgs=$NIXPKGS_PIN_FILE" --eval --strict --json -E " (n: builtins.match \"openssl_[0-9]+(_[0-9]+)?\" n != null) (builtins.attrNames pkgs); extraMatrixAttrs = [ \"boringssl\" ]; + default = builtins.head (builtins.filter (n: + let + inherit (pkgs.lib) versions; + t = builtins.tryEval pkgs.\${n}; + v = if t.success then builtins.tryEval t.value.version else t; + majorVersion = pkgs.lib.optionalString v.success (versions.major v.value); + minorVersion = pkgs.lib.optionalString v.success (versions.minor v.value); + in + majorVersion == ''$OPENSSL_MAJOR'' && minorVersion == ''$OPENSSL_MINOR'') opensslAttrs); attrs = builtins.filter (n: let t = builtins.tryEval pkgs.\${n}; in - t.success && (builtins.tryEval t.value.version).success + n != default && t.success && (builtins.tryEval t.value.version).success ) (opensslAttrs ++ extraMatrixAttrs); in { - inherit attrs; + inherit attrs default; permittedInsecurePackages = builtins.map (attr: pkgs.\${attr}.name) ( builtins.filter (attr: (pkgs.\${attr}.meta.insecure)) attrs ); @@ -69,6 +86,10 @@ nix-instantiate -I "nixpkgs=$NIXPKGS_PIN_FILE" --eval --strict --json -E " }: { + # "default" OpenSSL release line, should be kept in sync with the bundled version: + openssl = pkgs.\(.default); + + # Other OpenSSL variants we want to test for: inherit (pkgs) \(.attrs | sort | join("\n ")) ; diff --git a/tools/nix/openssl-matrix.nix b/tools/nix/openssl-matrix.nix index 36978c5d4efc..8c62cae81196 100644 --- a/tools/nix/openssl-matrix.nix +++ b/tools/nix/openssl-matrix.nix @@ -5,11 +5,14 @@ }: { + # "default" OpenSSL release line, should be kept in sync with the bundled version: + openssl = pkgs.openssl_3_5; + + # Other OpenSSL variants we want to test for: inherit (pkgs) boringssl openssl_1_1 openssl_3 - openssl_3_5 openssl_3_6 openssl_4_0 ; diff --git a/tools/nix/pkcs11.nix b/tools/nix/pkcs11.nix index eb6165b7bd99..748d3d4de0ed 100644 --- a/tools/nix/pkcs11.nix +++ b/tools/nix/pkcs11.nix @@ -10,7 +10,7 @@ # pkcs11-provider is dlopen'd into the libcrypto Node.js itself links, so it # has to be built against that very OpenSSL. SoftHSM links OpenSSL too; # building it against the same one keeps a single libcrypto in the process. - openssl ? (import ./sharedLibDeps.nix { inherit pkgs; }).openssl, + openssl ? (import ./openssl-matrix.nix { inherit pkgs; }).openssl, pin ? "1234", }: diff --git a/tools/nix/sharedLibDeps.nix b/tools/nix/sharedLibDeps.nix index 11ad545587f8..788e9efedeb8 100644 --- a/tools/nix/sharedLibDeps.nix +++ b/tools/nix/sharedLibDeps.nix @@ -48,7 +48,7 @@ ffi = pkgs.libffiReal; }) // (pkgs.lib.optionalAttrs withSSL ({ - openssl = (import ./openssl-matrix.nix { inherit pkgs; }).openssl_3_5; + inherit (import ./openssl-matrix.nix { inherit pkgs; }) openssl; })) // (pkgs.lib.optionalAttrs withTemporal { inherit (pkgs) temporal_capi; From 2b350bb8e4219fd7f71da5efc003837647d96e09 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Tue, 4 Aug 2026 10:15:37 -0700 Subject: [PATCH 19/24] ffi: accept pointer BigInts in multi-argument fast calls convertPointerArg ended with an unconditional getRawPointer call for buffer and arraybuffer types, rejecting BigInt addresses that the single-argument fast path and ToFFIArgument both accept. Drop the fallback; hasPointerMemoryArg already converts memory-backed values, and null, undefined, and strings are handled earlier. Signed-off-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Assisted-by: claude:opus-5 PR-URL: https://github.com/nodejs/node/pull/64964 Fixes: https://github.com/nodejs/node/issues/64963 Reviewed-By: James M Snell Reviewed-By: Matteo Collina --- lib/internal/ffi/fast-api.js | 5 ++--- test/ffi/test-ffi-fast-buffer.js | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/internal/ffi/fast-api.js b/lib/internal/ffi/fast-api.js index 44e4c3a04e0f..486a119a2e07 100644 --- a/lib/internal/ffi/fast-api.js +++ b/lib/internal/ffi/fast-api.js @@ -169,9 +169,8 @@ function convertPointerArg(type, value, stringState, index) { if (hasPointerMemoryArg(type, value)) { return getRawPointer(value); } - if (needsRawPointerConversion(type)) { - return getRawPointer(value); - } + // Pointer-like values (e.g. BigInt addresses) are passed through, matching + // ToFFIArgument in src/ffi/types.cc and the single-argument fast path. return value; } diff --git a/test/ffi/test-ffi-fast-buffer.js b/test/ffi/test-ffi-fast-buffer.js index 97d8a3c9b852..e4399ee8ee47 100644 --- a/test/ffi/test-ffi-fast-buffer.js +++ b/test/ffi/test-ffi-fast-buffer.js @@ -133,3 +133,36 @@ test('optimized buffer signatures preserve pointer-like conversions', () => { lib.close(); } }); + +test('multi-argument buffer signatures accept pointer BigInts', () => { + const { lib, functions } = ffi.dlopen(libraryPath, { + sum_buffer: { arguments: ['buffer', 'u64'], return: 'u64' }, + fill_buffer: { arguments: ['arraybuffer', 'u64', 'u32'], return: 'void' }, + }); + + try { + const bytes = Buffer.from([1, 2, 3, 4]); + const pointer = ffi.getRawPointer(bytes); + const length = BigInt(bytes.length); + + // The two-argument wrapper must treat a raw address like the buffer it + // came from, matching both the single-argument fast path and the slow + // paths in src/ffi/types.cc. + assert.strictEqual(functions.sum_buffer(pointer, length), 10n); + assert.strictEqual(functions.sum_buffer(bytes, length), 10n); + assert.strictEqual(functions.sum_buffer(0n, length), 0n); + assert.strictEqual(functions.sum_buffer(null, length), 0n); + + // The three-argument wrapper must forward the address to real memory + // instead of rejecting it. + functions.fill_buffer(pointer, length, 7); + assert.deepStrictEqual(bytes, Buffer.from([7, 7, 7, 7])); + + // Still accepted once the call has been optimized. + for (let i = 0; i < 100_000; i++) { + assert.strictEqual(functions.sum_buffer(pointer, length), 28n); + } + } finally { + lib.close(); + } +}); From 8465148f4ad9f689ee12e8ee313cc112f1c52b6a Mon Sep 17 00:00:00 2001 From: Jihwan Date: Wed, 5 Aug 2026 03:32:44 +0900 Subject: [PATCH 20/24] test_runner: fix env option validation Signed-off-by: hanityx PR-URL: https://github.com/nodejs/node/pull/64865 Reviewed-By: Aviv Keller Reviewed-By: Moshe Atlow Reviewed-By: James M Snell Reviewed-By: Chemi Atlow --- lib/internal/test_runner/runner.js | 2 +- test/parallel/test-runner-run.mjs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 4bfce346a592..a5a53e44d29a 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -910,7 +910,7 @@ function run(options = kEmptyObject) { } if (env != null) { - validateObject(env); + validateObject(env, 'options.env'); if (isolation === 'none') { throw new ERR_INVALID_ARG_VALUE('options.env', env, 'is not supported with isolation=\'none\''); diff --git a/test/parallel/test-runner-run.mjs b/test/parallel/test-runner-run.mjs index b6eb6b6af518..c6b888432cf8 100644 --- a/test/parallel/test-runner-run.mjs +++ b/test/parallel/test-runner-run.mjs @@ -673,6 +673,14 @@ describe('require(\'node:test\').run', { concurrency: true }, () => { })); }); + it('should only allow object in options.env', () => { + [Symbol(), [], () => {}, 0, 1, 0n, 1n, '', '1', true, false] + .forEach((env) => assert.throws(() => run({ files: [], env }), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options\.env" property must be of type object\./ + })); + }); + it('should not allow files and globPatterns used together', () => { assert.throws(() => run({ files: ['a.js'], globPatterns: ['*.js'] }), { code: 'ERR_INVALID_ARG_VALUE' From 1fc74c8dbbc62e303c9920f0528cda8182e79c97 Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Tue, 4 Aug 2026 15:10:36 -0400 Subject: [PATCH 21/24] build: enable perfetto updater Signed-off-by: Chengzhong Wu PR-URL: https://github.com/nodejs/node/pull/64966 Reviewed-By: James M Snell Reviewed-By: Aviv Keller Reviewed-By: Marco Ippolito --- .github/workflows/tools.yml | 9 +++++++++ tools/dep_updaters/update-perfetto.sh | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 4e44737c116b..e80b65315a4a 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -35,6 +35,7 @@ on: - nghttp2 - nghttp3 - ngtcp2 + - perfetto - postject - root-certificates - simdjson @@ -237,6 +238,14 @@ jobs: cat temp-output tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true rm temp-output + - id: perfetto + subsystem: deps + label: dependencies + run: | + ./tools/dep_updaters/update-perfetto.sh > temp-output + cat temp-output + tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true + rm temp-output - id: postject subsystem: deps,test label: test diff --git a/tools/dep_updaters/update-perfetto.sh b/tools/dep_updaters/update-perfetto.sh index 516d7d6aa63b..493a8f8c57d1 100755 --- a/tools/dep_updaters/update-perfetto.sh +++ b/tools/dep_updaters/update-perfetto.sh @@ -57,7 +57,7 @@ echo "$NEW_VERSION" > perfetto/VERSION curl -sL -o "perfetto/LICENSE" "https://raw.githubusercontent.com/google/perfetto/refs/tags/$PERFETTO_REF/LICENSE" # Remove C API headers. Only keep C++ API headers. -rm perfetto/sdk/perfetto_c.h perfetto/sdk/perfetto_c.cc +rm -f perfetto/sdk/perfetto_c.h perfetto/sdk/perfetto_c.cc echo "Copying existing gyp files" cp "$DEPS_DIR/perfetto/perfetto.gyp" "$WORKSPACE/perfetto" From c8fa0b14665f3a8dfa530872b26ef5460311da2a Mon Sep 17 00:00:00 2001 From: npm CLI robot Date: Fri, 31 Jul 2026 20:49:56 +0000 Subject: [PATCH 22/24] deps: upgrade npm to 11.19.0 PR-URL: https://github.com/nodejs/node/pull/64883 Reviewed-By: Jordan Harband Reviewed-By: Aviv Keller Reviewed-By: Luigi Pinca Reviewed-By: Mike McCready <66998419+MikeMcC399@users.noreply.github.com> Reviewed-By: Trivikram Kamat --- deps/npm/docs/content/commands/npm-ls.md | 2 +- deps/npm/docs/content/commands/npm.md | 2 +- deps/npm/docs/output/commands/npm-access.html | 4 +- .../npm/docs/output/commands/npm-adduser.html | 4 +- .../output/commands/npm-approve-scripts.html | 4 +- deps/npm/docs/output/commands/npm-audit.html | 4 +- deps/npm/docs/output/commands/npm-bugs.html | 4 +- deps/npm/docs/output/commands/npm-cache.html | 4 +- deps/npm/docs/output/commands/npm-ci.html | 4 +- .../docs/output/commands/npm-completion.html | 4 +- deps/npm/docs/output/commands/npm-config.html | 4 +- deps/npm/docs/output/commands/npm-dedupe.html | 4 +- .../output/commands/npm-deny-scripts.html | 4 +- .../docs/output/commands/npm-deprecate.html | 4 +- deps/npm/docs/output/commands/npm-diff.html | 4 +- .../docs/output/commands/npm-dist-tag.html | 4 +- deps/npm/docs/output/commands/npm-docs.html | 4 +- deps/npm/docs/output/commands/npm-doctor.html | 4 +- deps/npm/docs/output/commands/npm-edit.html | 4 +- deps/npm/docs/output/commands/npm-exec.html | 4 +- .../npm/docs/output/commands/npm-explain.html | 4 +- .../npm/docs/output/commands/npm-explore.html | 4 +- .../docs/output/commands/npm-find-dupes.html | 4 +- deps/npm/docs/output/commands/npm-fund.html | 4 +- deps/npm/docs/output/commands/npm-get.html | 4 +- .../docs/output/commands/npm-help-search.html | 4 +- deps/npm/docs/output/commands/npm-help.html | 4 +- deps/npm/docs/output/commands/npm-init.html | 4 +- .../output/commands/npm-install-ci-test.html | 4 +- .../output/commands/npm-install-scripts.html | 4 +- .../output/commands/npm-install-test.html | 4 +- .../npm/docs/output/commands/npm-install.html | 4 +- deps/npm/docs/output/commands/npm-link.html | 4 +- deps/npm/docs/output/commands/npm-ll.html | 4 +- deps/npm/docs/output/commands/npm-login.html | 4 +- deps/npm/docs/output/commands/npm-logout.html | 4 +- deps/npm/docs/output/commands/npm-ls.html | 6 +- deps/npm/docs/output/commands/npm-org.html | 4 +- .../docs/output/commands/npm-outdated.html | 4 +- deps/npm/docs/output/commands/npm-owner.html | 4 +- deps/npm/docs/output/commands/npm-pack.html | 4 +- deps/npm/docs/output/commands/npm-ping.html | 4 +- deps/npm/docs/output/commands/npm-pkg.html | 4 +- deps/npm/docs/output/commands/npm-prefix.html | 4 +- .../npm/docs/output/commands/npm-profile.html | 4 +- deps/npm/docs/output/commands/npm-prune.html | 4 +- .../npm/docs/output/commands/npm-publish.html | 4 +- deps/npm/docs/output/commands/npm-query.html | 4 +- .../npm/docs/output/commands/npm-rebuild.html | 4 +- deps/npm/docs/output/commands/npm-repo.html | 4 +- .../npm/docs/output/commands/npm-restart.html | 4 +- deps/npm/docs/output/commands/npm-root.html | 4 +- deps/npm/docs/output/commands/npm-run.html | 4 +- deps/npm/docs/output/commands/npm-sbom.html | 4 +- deps/npm/docs/output/commands/npm-search.html | 4 +- deps/npm/docs/output/commands/npm-set.html | 4 +- .../docs/output/commands/npm-shrinkwrap.html | 4 +- deps/npm/docs/output/commands/npm-stage.html | 4 +- deps/npm/docs/output/commands/npm-star.html | 4 +- deps/npm/docs/output/commands/npm-stars.html | 4 +- deps/npm/docs/output/commands/npm-start.html | 4 +- deps/npm/docs/output/commands/npm-stop.html | 4 +- deps/npm/docs/output/commands/npm-team.html | 4 +- deps/npm/docs/output/commands/npm-test.html | 4 +- deps/npm/docs/output/commands/npm-token.html | 4 +- deps/npm/docs/output/commands/npm-trust.html | 4 +- .../docs/output/commands/npm-undeprecate.html | 4 +- .../docs/output/commands/npm-uninstall.html | 4 +- .../docs/output/commands/npm-unpublish.html | 4 +- deps/npm/docs/output/commands/npm-unstar.html | 4 +- deps/npm/docs/output/commands/npm-update.html | 4 +- .../npm/docs/output/commands/npm-version.html | 4 +- deps/npm/docs/output/commands/npm-view.html | 4 +- deps/npm/docs/output/commands/npm-whoami.html | 4 +- deps/npm/docs/output/commands/npm.html | 6 +- deps/npm/docs/output/commands/npx.html | 4 +- .../docs/output/configuring-npm/folders.html | 4 +- .../docs/output/configuring-npm/install.html | 4 +- .../output/configuring-npm/npm-global.html | 4 +- .../docs/output/configuring-npm/npm-json.html | 4 +- .../configuring-npm/npm-shrinkwrap-json.html | 4 +- .../docs/output/configuring-npm/npmrc.html | 4 +- .../output/configuring-npm/package-json.html | 4 +- .../configuring-npm/package-lock-json.html | 4 +- deps/npm/docs/output/using-npm/config.html | 4 +- .../using-npm/dependency-selectors.html | 4 +- .../npm/docs/output/using-npm/developers.html | 4 +- deps/npm/docs/output/using-npm/logging.html | 4 +- deps/npm/docs/output/using-npm/orgs.html | 4 +- .../docs/output/using-npm/package-spec.html | 4 +- deps/npm/docs/output/using-npm/registry.html | 4 +- deps/npm/docs/output/using-npm/removal.html | 4 +- deps/npm/docs/output/using-npm/scope.html | 4 +- deps/npm/docs/output/using-npm/scripts.html | 4 +- .../npm/docs/output/using-npm/workspaces.html | 4 +- deps/npm/lib/commands/owner.js | 5 +- deps/npm/lib/commands/pack.js | 16 ++- deps/npm/lib/utils/allow-scripts-writer.js | 2 +- deps/npm/lib/utils/reify-output.js | 2 +- deps/npm/lib/utils/resolve-allow-scripts.js | 12 +- .../lib/utils/warn-workspace-allow-scripts.js | 2 +- deps/npm/man/man1/npm-access.1 | 2 +- deps/npm/man/man1/npm-adduser.1 | 2 +- deps/npm/man/man1/npm-approve-scripts.1 | 2 +- deps/npm/man/man1/npm-audit.1 | 2 +- deps/npm/man/man1/npm-bugs.1 | 2 +- deps/npm/man/man1/npm-cache.1 | 2 +- deps/npm/man/man1/npm-ci.1 | 2 +- deps/npm/man/man1/npm-completion.1 | 2 +- deps/npm/man/man1/npm-config.1 | 2 +- deps/npm/man/man1/npm-dedupe.1 | 2 +- deps/npm/man/man1/npm-deny-scripts.1 | 2 +- deps/npm/man/man1/npm-deprecate.1 | 2 +- deps/npm/man/man1/npm-diff.1 | 2 +- deps/npm/man/man1/npm-dist-tag.1 | 2 +- deps/npm/man/man1/npm-docs.1 | 2 +- deps/npm/man/man1/npm-doctor.1 | 2 +- deps/npm/man/man1/npm-edit.1 | 2 +- deps/npm/man/man1/npm-exec.1 | 2 +- deps/npm/man/man1/npm-explain.1 | 2 +- deps/npm/man/man1/npm-explore.1 | 2 +- deps/npm/man/man1/npm-find-dupes.1 | 2 +- deps/npm/man/man1/npm-fund.1 | 2 +- deps/npm/man/man1/npm-get.1 | 2 +- deps/npm/man/man1/npm-help-search.1 | 2 +- deps/npm/man/man1/npm-help.1 | 2 +- deps/npm/man/man1/npm-init.1 | 2 +- deps/npm/man/man1/npm-install-ci-test.1 | 2 +- deps/npm/man/man1/npm-install-scripts.1 | 2 +- deps/npm/man/man1/npm-install-test.1 | 2 +- deps/npm/man/man1/npm-install.1 | 2 +- deps/npm/man/man1/npm-link.1 | 2 +- deps/npm/man/man1/npm-ll.1 | 2 +- deps/npm/man/man1/npm-login.1 | 2 +- deps/npm/man/man1/npm-logout.1 | 2 +- deps/npm/man/man1/npm-ls.1 | 4 +- deps/npm/man/man1/npm-org.1 | 2 +- deps/npm/man/man1/npm-outdated.1 | 2 +- deps/npm/man/man1/npm-owner.1 | 2 +- deps/npm/man/man1/npm-pack.1 | 2 +- deps/npm/man/man1/npm-ping.1 | 2 +- deps/npm/man/man1/npm-pkg.1 | 2 +- deps/npm/man/man1/npm-prefix.1 | 2 +- deps/npm/man/man1/npm-profile.1 | 2 +- deps/npm/man/man1/npm-prune.1 | 2 +- deps/npm/man/man1/npm-publish.1 | 2 +- deps/npm/man/man1/npm-query.1 | 2 +- deps/npm/man/man1/npm-rebuild.1 | 2 +- deps/npm/man/man1/npm-repo.1 | 2 +- deps/npm/man/man1/npm-restart.1 | 2 +- deps/npm/man/man1/npm-root.1 | 2 +- deps/npm/man/man1/npm-run.1 | 2 +- deps/npm/man/man1/npm-sbom.1 | 2 +- deps/npm/man/man1/npm-search.1 | 2 +- deps/npm/man/man1/npm-set.1 | 2 +- deps/npm/man/man1/npm-shrinkwrap.1 | 2 +- deps/npm/man/man1/npm-stage.1 | 2 +- deps/npm/man/man1/npm-star.1 | 2 +- deps/npm/man/man1/npm-stars.1 | 2 +- deps/npm/man/man1/npm-start.1 | 2 +- deps/npm/man/man1/npm-stop.1 | 2 +- deps/npm/man/man1/npm-team.1 | 2 +- deps/npm/man/man1/npm-test.1 | 2 +- deps/npm/man/man1/npm-token.1 | 2 +- deps/npm/man/man1/npm-trust.1 | 2 +- deps/npm/man/man1/npm-undeprecate.1 | 2 +- deps/npm/man/man1/npm-uninstall.1 | 2 +- deps/npm/man/man1/npm-unpublish.1 | 2 +- deps/npm/man/man1/npm-unstar.1 | 2 +- deps/npm/man/man1/npm-update.1 | 2 +- deps/npm/man/man1/npm-version.1 | 2 +- deps/npm/man/man1/npm-view.1 | 2 +- deps/npm/man/man1/npm-whoami.1 | 2 +- deps/npm/man/man1/npm.1 | 4 +- deps/npm/man/man1/npx.1 | 2 +- deps/npm/man/man5/folders.5 | 2 +- deps/npm/man/man5/install.5 | 2 +- deps/npm/man/man5/npm-global.5 | 2 +- deps/npm/man/man5/npm-json.5 | 2 +- deps/npm/man/man5/npm-shrinkwrap-json.5 | 2 +- deps/npm/man/man5/npmrc.5 | 2 +- deps/npm/man/man5/package-json.5 | 2 +- deps/npm/man/man5/package-lock-json.5 | 2 +- deps/npm/man/man7/config.7 | 2 +- deps/npm/man/man7/dependency-selectors.7 | 2 +- deps/npm/man/man7/developers.7 | 2 +- deps/npm/man/man7/logging.7 | 2 +- deps/npm/man/man7/orgs.7 | 2 +- deps/npm/man/man7/package-spec.7 | 2 +- deps/npm/man/man7/registry.7 | 2 +- deps/npm/man/man7/removal.7 | 2 +- deps/npm/man/man7/scope.7 | 2 +- deps/npm/man/man7/scripts.7 | 2 +- deps/npm/man/man7/workspaces.7 | 2 +- .../arborist/lib/arborist/build-ideal-tree.js | 6 + .../@npmcli/arborist/lib/can-place-dep.js | 15 ++- .../@npmcli/arborist/lib/place-dep.js | 1 + .../@npmcli/arborist/package.json | 2 +- deps/npm/node_modules/libnpmdiff/package.json | 4 +- deps/npm/node_modules/libnpmexec/package.json | 4 +- deps/npm/node_modules/libnpmfund/package.json | 4 +- deps/npm/node_modules/libnpmpack/package.json | 4 +- deps/npm/package.json | 12 +- deps/npm/test/lib/commands/approve-scripts.js | 2 +- deps/npm/test/lib/commands/owner.js | 103 ++++++++++++++++++ deps/npm/test/lib/commands/pack.js | 56 ++++++++++ deps/npm/test/lib/utils/reify-output.js | 8 +- .../test/lib/utils/resolve-allow-scripts.js | 12 +- 208 files changed, 516 insertions(+), 326 deletions(-) diff --git a/deps/npm/docs/content/commands/npm-ls.md b/deps/npm/docs/content/commands/npm-ls.md index fba77fd01cff..24746c173b7c 100644 --- a/deps/npm/docs/content/commands/npm-ls.md +++ b/deps/npm/docs/content/commands/npm-ls.md @@ -23,7 +23,7 @@ Note that nested packages will *also* show the paths to the specified packages. For example, running `npm ls promzard` in npm's source tree will show: ```bash -npm@11.18.0 /path/to/npm +npm@11.19.0 /path/to/npm └─┬ init-package-json@0.0.4 └── promzard@0.1.5 ``` diff --git a/deps/npm/docs/content/commands/npm.md b/deps/npm/docs/content/commands/npm.md index 088553c6ffc4..769bb262064e 100644 --- a/deps/npm/docs/content/commands/npm.md +++ b/deps/npm/docs/content/commands/npm.md @@ -14,7 +14,7 @@ Note: This command is unaware of workspaces. ### Version -11.18.0 +11.19.0 ### Description diff --git a/deps/npm/docs/output/commands/npm-access.html b/deps/npm/docs/output/commands/npm-access.html index 13475a5d16f4..2a044ef5ed59 100644 --- a/deps/npm/docs/output/commands/npm-access.html +++ b/deps/npm/docs/output/commands/npm-access.html @@ -186,9 +186,9 @@
-

+

npm-access - @11.18.0 + @11.19.0

Set access level on published packages
diff --git a/deps/npm/docs/output/commands/npm-adduser.html b/deps/npm/docs/output/commands/npm-adduser.html index e43659736fec..c59e21d94298 100644 --- a/deps/npm/docs/output/commands/npm-adduser.html +++ b/deps/npm/docs/output/commands/npm-adduser.html @@ -186,9 +186,9 @@
-

+

npm-adduser - @11.18.0 + @11.19.0

Add a registry user account
diff --git a/deps/npm/docs/output/commands/npm-approve-scripts.html b/deps/npm/docs/output/commands/npm-approve-scripts.html index c3bc0172b5a4..55c9db2b2a2b 100644 --- a/deps/npm/docs/output/commands/npm-approve-scripts.html +++ b/deps/npm/docs/output/commands/npm-approve-scripts.html @@ -186,9 +186,9 @@
-

+

npm-approve-scripts - @11.18.0 + @11.19.0

Approve install scripts for specific dependencies
diff --git a/deps/npm/docs/output/commands/npm-audit.html b/deps/npm/docs/output/commands/npm-audit.html index 61042b6fbb07..438891b03cbc 100644 --- a/deps/npm/docs/output/commands/npm-audit.html +++ b/deps/npm/docs/output/commands/npm-audit.html @@ -186,9 +186,9 @@
-

+

npm-audit - @11.18.0 + @11.19.0

Run a security audit
diff --git a/deps/npm/docs/output/commands/npm-bugs.html b/deps/npm/docs/output/commands/npm-bugs.html index 660f011d1a0d..eb7a91c9457f 100644 --- a/deps/npm/docs/output/commands/npm-bugs.html +++ b/deps/npm/docs/output/commands/npm-bugs.html @@ -186,9 +186,9 @@
-

+

npm-bugs - @11.18.0 + @11.19.0

Report bugs for a package in a web browser
diff --git a/deps/npm/docs/output/commands/npm-cache.html b/deps/npm/docs/output/commands/npm-cache.html index 553a38d06f42..374878c8d04e 100644 --- a/deps/npm/docs/output/commands/npm-cache.html +++ b/deps/npm/docs/output/commands/npm-cache.html @@ -186,9 +186,9 @@
-

+

npm-cache - @11.18.0 + @11.19.0

Manipulates packages cache
diff --git a/deps/npm/docs/output/commands/npm-ci.html b/deps/npm/docs/output/commands/npm-ci.html index 3e61cba31bea..0a91d5159b12 100644 --- a/deps/npm/docs/output/commands/npm-ci.html +++ b/deps/npm/docs/output/commands/npm-ci.html @@ -186,9 +186,9 @@
-

+

npm-ci - @11.18.0 + @11.19.0

Clean install a project
diff --git a/deps/npm/docs/output/commands/npm-completion.html b/deps/npm/docs/output/commands/npm-completion.html index d24da48ebb03..dc927a82868a 100644 --- a/deps/npm/docs/output/commands/npm-completion.html +++ b/deps/npm/docs/output/commands/npm-completion.html @@ -186,9 +186,9 @@
-

+

npm-completion - @11.18.0 + @11.19.0

Tab Completion for npm
diff --git a/deps/npm/docs/output/commands/npm-config.html b/deps/npm/docs/output/commands/npm-config.html index a3c0dab2dee7..5b4e3e1204bc 100644 --- a/deps/npm/docs/output/commands/npm-config.html +++ b/deps/npm/docs/output/commands/npm-config.html @@ -186,9 +186,9 @@
-

+

npm-config - @11.18.0 + @11.19.0

Manage the npm configuration files
diff --git a/deps/npm/docs/output/commands/npm-dedupe.html b/deps/npm/docs/output/commands/npm-dedupe.html index fa8d4b708437..0f08ae285737 100644 --- a/deps/npm/docs/output/commands/npm-dedupe.html +++ b/deps/npm/docs/output/commands/npm-dedupe.html @@ -186,9 +186,9 @@
-

+

npm-dedupe - @11.18.0 + @11.19.0

Reduce duplication in the package tree
diff --git a/deps/npm/docs/output/commands/npm-deny-scripts.html b/deps/npm/docs/output/commands/npm-deny-scripts.html index 4f7653c40173..8b82b3684feb 100644 --- a/deps/npm/docs/output/commands/npm-deny-scripts.html +++ b/deps/npm/docs/output/commands/npm-deny-scripts.html @@ -186,9 +186,9 @@
-

+

npm-deny-scripts - @11.18.0 + @11.19.0

Deny install scripts for specific dependencies
diff --git a/deps/npm/docs/output/commands/npm-deprecate.html b/deps/npm/docs/output/commands/npm-deprecate.html index 30c4c9122384..489345c280bc 100644 --- a/deps/npm/docs/output/commands/npm-deprecate.html +++ b/deps/npm/docs/output/commands/npm-deprecate.html @@ -186,9 +186,9 @@
-

+

npm-deprecate - @11.18.0 + @11.19.0

Deprecate a version of a package
diff --git a/deps/npm/docs/output/commands/npm-diff.html b/deps/npm/docs/output/commands/npm-diff.html index 6cf770360c10..3bb3d244a198 100644 --- a/deps/npm/docs/output/commands/npm-diff.html +++ b/deps/npm/docs/output/commands/npm-diff.html @@ -186,9 +186,9 @@
-

+

npm-diff - @11.18.0 + @11.19.0

The registry diff command
diff --git a/deps/npm/docs/output/commands/npm-dist-tag.html b/deps/npm/docs/output/commands/npm-dist-tag.html index 59985e4a43f3..9f3aafdf4314 100644 --- a/deps/npm/docs/output/commands/npm-dist-tag.html +++ b/deps/npm/docs/output/commands/npm-dist-tag.html @@ -186,9 +186,9 @@
-

+

npm-dist-tag - @11.18.0 + @11.19.0

Modify package distribution tags
diff --git a/deps/npm/docs/output/commands/npm-docs.html b/deps/npm/docs/output/commands/npm-docs.html index 884f20d70d2f..fbdd0ad2b626 100644 --- a/deps/npm/docs/output/commands/npm-docs.html +++ b/deps/npm/docs/output/commands/npm-docs.html @@ -186,9 +186,9 @@
-

+

npm-docs - @11.18.0 + @11.19.0

Open documentation for a package in a web browser
diff --git a/deps/npm/docs/output/commands/npm-doctor.html b/deps/npm/docs/output/commands/npm-doctor.html index 13bb25228dd2..b7dc5d2caf84 100644 --- a/deps/npm/docs/output/commands/npm-doctor.html +++ b/deps/npm/docs/output/commands/npm-doctor.html @@ -186,9 +186,9 @@
-

+

npm-doctor - @11.18.0 + @11.19.0

Check the health of your npm environment
diff --git a/deps/npm/docs/output/commands/npm-edit.html b/deps/npm/docs/output/commands/npm-edit.html index 6d1215ebfe10..3aed53c2677a 100644 --- a/deps/npm/docs/output/commands/npm-edit.html +++ b/deps/npm/docs/output/commands/npm-edit.html @@ -186,9 +186,9 @@
-

+

npm-edit - @11.18.0 + @11.19.0

Edit an installed package
diff --git a/deps/npm/docs/output/commands/npm-exec.html b/deps/npm/docs/output/commands/npm-exec.html index 3f57293a17fc..a5ea0d54c343 100644 --- a/deps/npm/docs/output/commands/npm-exec.html +++ b/deps/npm/docs/output/commands/npm-exec.html @@ -186,9 +186,9 @@
-

+

npm-exec - @11.18.0 + @11.19.0

Run a command from a local or remote npm package
diff --git a/deps/npm/docs/output/commands/npm-explain.html b/deps/npm/docs/output/commands/npm-explain.html index 5d5f6cea5ce1..bce1694bcf08 100644 --- a/deps/npm/docs/output/commands/npm-explain.html +++ b/deps/npm/docs/output/commands/npm-explain.html @@ -186,9 +186,9 @@
-

+

npm-explain - @11.18.0 + @11.19.0

Explain installed packages
diff --git a/deps/npm/docs/output/commands/npm-explore.html b/deps/npm/docs/output/commands/npm-explore.html index d1ecb4e81886..872892c9f1d5 100644 --- a/deps/npm/docs/output/commands/npm-explore.html +++ b/deps/npm/docs/output/commands/npm-explore.html @@ -186,9 +186,9 @@
-

+

npm-explore - @11.18.0 + @11.19.0

Browse an installed package
diff --git a/deps/npm/docs/output/commands/npm-find-dupes.html b/deps/npm/docs/output/commands/npm-find-dupes.html index 28a906639876..b71cbeb015db 100644 --- a/deps/npm/docs/output/commands/npm-find-dupes.html +++ b/deps/npm/docs/output/commands/npm-find-dupes.html @@ -186,9 +186,9 @@
-

+

npm-find-dupes - @11.18.0 + @11.19.0

Find duplication in the package tree
diff --git a/deps/npm/docs/output/commands/npm-fund.html b/deps/npm/docs/output/commands/npm-fund.html index 26761cc90196..9f04fd7ea66b 100644 --- a/deps/npm/docs/output/commands/npm-fund.html +++ b/deps/npm/docs/output/commands/npm-fund.html @@ -186,9 +186,9 @@
-

+

npm-fund - @11.18.0 + @11.19.0

Retrieve funding information
diff --git a/deps/npm/docs/output/commands/npm-get.html b/deps/npm/docs/output/commands/npm-get.html index e8fe5dfc0373..94a56fd4a81b 100644 --- a/deps/npm/docs/output/commands/npm-get.html +++ b/deps/npm/docs/output/commands/npm-get.html @@ -186,9 +186,9 @@
-

+

npm-get - @11.18.0 + @11.19.0

Get a value from the npm configuration
diff --git a/deps/npm/docs/output/commands/npm-help-search.html b/deps/npm/docs/output/commands/npm-help-search.html index 24f4ddafaea5..d3666992d1e9 100644 --- a/deps/npm/docs/output/commands/npm-help-search.html +++ b/deps/npm/docs/output/commands/npm-help-search.html @@ -186,9 +186,9 @@
-

+

npm-help-search - @11.18.0 + @11.19.0

Search npm help documentation
diff --git a/deps/npm/docs/output/commands/npm-help.html b/deps/npm/docs/output/commands/npm-help.html index 4fb2ef4be76c..e7fa1b32e601 100644 --- a/deps/npm/docs/output/commands/npm-help.html +++ b/deps/npm/docs/output/commands/npm-help.html @@ -186,9 +186,9 @@
-

+

npm-help - @11.18.0 + @11.19.0

Get help on npm
diff --git a/deps/npm/docs/output/commands/npm-init.html b/deps/npm/docs/output/commands/npm-init.html index deeaf25a2348..265ee78ad2cf 100644 --- a/deps/npm/docs/output/commands/npm-init.html +++ b/deps/npm/docs/output/commands/npm-init.html @@ -186,9 +186,9 @@
-

+

npm-init - @11.18.0 + @11.19.0

Create a package.json file
diff --git a/deps/npm/docs/output/commands/npm-install-ci-test.html b/deps/npm/docs/output/commands/npm-install-ci-test.html index 2227da948859..94d4e4a663b8 100644 --- a/deps/npm/docs/output/commands/npm-install-ci-test.html +++ b/deps/npm/docs/output/commands/npm-install-ci-test.html @@ -186,9 +186,9 @@
-

+

npm-install-ci-test - @11.18.0 + @11.19.0

Install a project with a clean slate and run tests
diff --git a/deps/npm/docs/output/commands/npm-install-scripts.html b/deps/npm/docs/output/commands/npm-install-scripts.html index 1b036581f6a3..d78f5efc1653 100644 --- a/deps/npm/docs/output/commands/npm-install-scripts.html +++ b/deps/npm/docs/output/commands/npm-install-scripts.html @@ -186,9 +186,9 @@
-

+

npm-install-scripts - @11.18.0 + @11.19.0

Manage install-script approvals for dependencies
diff --git a/deps/npm/docs/output/commands/npm-install-test.html b/deps/npm/docs/output/commands/npm-install-test.html index 94231076345a..c55b06547a9d 100644 --- a/deps/npm/docs/output/commands/npm-install-test.html +++ b/deps/npm/docs/output/commands/npm-install-test.html @@ -186,9 +186,9 @@
-

+

npm-install-test - @11.18.0 + @11.19.0

Install package(s) and run tests
diff --git a/deps/npm/docs/output/commands/npm-install.html b/deps/npm/docs/output/commands/npm-install.html index fd6fe12eb8cd..80af90cc3db9 100644 --- a/deps/npm/docs/output/commands/npm-install.html +++ b/deps/npm/docs/output/commands/npm-install.html @@ -186,9 +186,9 @@
-

+

npm-install - @11.18.0 + @11.19.0

Install a package
diff --git a/deps/npm/docs/output/commands/npm-link.html b/deps/npm/docs/output/commands/npm-link.html index e4c9f092f37c..3a5795b866f5 100644 --- a/deps/npm/docs/output/commands/npm-link.html +++ b/deps/npm/docs/output/commands/npm-link.html @@ -186,9 +186,9 @@
-

+

npm-link - @11.18.0 + @11.19.0

Symlink a package folder
diff --git a/deps/npm/docs/output/commands/npm-ll.html b/deps/npm/docs/output/commands/npm-ll.html index a7e3061ddf98..601f99e91029 100644 --- a/deps/npm/docs/output/commands/npm-ll.html +++ b/deps/npm/docs/output/commands/npm-ll.html @@ -186,9 +186,9 @@
-

+

npm-ll - @11.18.0 + @11.19.0

List installed packages
diff --git a/deps/npm/docs/output/commands/npm-login.html b/deps/npm/docs/output/commands/npm-login.html index b08ac7b33570..33d44e488838 100644 --- a/deps/npm/docs/output/commands/npm-login.html +++ b/deps/npm/docs/output/commands/npm-login.html @@ -186,9 +186,9 @@
-

+

npm-login - @11.18.0 + @11.19.0

Login to a registry user account
diff --git a/deps/npm/docs/output/commands/npm-logout.html b/deps/npm/docs/output/commands/npm-logout.html index dfc1b256538f..0be803c18ae7 100644 --- a/deps/npm/docs/output/commands/npm-logout.html +++ b/deps/npm/docs/output/commands/npm-logout.html @@ -186,9 +186,9 @@
-

+

npm-logout - @11.18.0 + @11.19.0

Log out of the registry
diff --git a/deps/npm/docs/output/commands/npm-ls.html b/deps/npm/docs/output/commands/npm-ls.html index a3de66d0323c..bda454a04c77 100644 --- a/deps/npm/docs/output/commands/npm-ls.html +++ b/deps/npm/docs/output/commands/npm-ls.html @@ -186,9 +186,9 @@
-

+

npm-ls - @11.18.0 + @11.19.0

List installed packages
@@ -209,7 +209,7 @@

Description

Positional arguments are name@version-range identifiers, which will limit the results to only the paths to the packages named. Note that nested packages will also show the paths to the specified packages. For example, running npm ls promzard in npm's source tree will show:

-
npm@11.18.0 /path/to/npm
+
npm@11.19.0 /path/to/npm
 └─┬ init-package-json@0.0.4
   └── promzard@0.1.5
 
diff --git a/deps/npm/docs/output/commands/npm-org.html b/deps/npm/docs/output/commands/npm-org.html index 7c0526d750c6..32df155d69ce 100644 --- a/deps/npm/docs/output/commands/npm-org.html +++ b/deps/npm/docs/output/commands/npm-org.html @@ -186,9 +186,9 @@
-

+

npm-org - @11.18.0 + @11.19.0

Manage orgs
diff --git a/deps/npm/docs/output/commands/npm-outdated.html b/deps/npm/docs/output/commands/npm-outdated.html index 5bf8ee630c26..5355d7dd09aa 100644 --- a/deps/npm/docs/output/commands/npm-outdated.html +++ b/deps/npm/docs/output/commands/npm-outdated.html @@ -186,9 +186,9 @@
-

+

npm-outdated - @11.18.0 + @11.19.0

Check for outdated packages
diff --git a/deps/npm/docs/output/commands/npm-owner.html b/deps/npm/docs/output/commands/npm-owner.html index c4947a01ae92..7dc17627f50a 100644 --- a/deps/npm/docs/output/commands/npm-owner.html +++ b/deps/npm/docs/output/commands/npm-owner.html @@ -186,9 +186,9 @@
-

+

npm-owner - @11.18.0 + @11.19.0

Manage package owners
diff --git a/deps/npm/docs/output/commands/npm-pack.html b/deps/npm/docs/output/commands/npm-pack.html index a875a527f81f..3f92c3ff7579 100644 --- a/deps/npm/docs/output/commands/npm-pack.html +++ b/deps/npm/docs/output/commands/npm-pack.html @@ -186,9 +186,9 @@
-

+

npm-pack - @11.18.0 + @11.19.0

Create a tarball from a package
diff --git a/deps/npm/docs/output/commands/npm-ping.html b/deps/npm/docs/output/commands/npm-ping.html index 74a7e2adc927..84d634064212 100644 --- a/deps/npm/docs/output/commands/npm-ping.html +++ b/deps/npm/docs/output/commands/npm-ping.html @@ -186,9 +186,9 @@
-

+

npm-ping - @11.18.0 + @11.19.0

Ping npm registry
diff --git a/deps/npm/docs/output/commands/npm-pkg.html b/deps/npm/docs/output/commands/npm-pkg.html index 9aec2f5f5602..cf995cca7679 100644 --- a/deps/npm/docs/output/commands/npm-pkg.html +++ b/deps/npm/docs/output/commands/npm-pkg.html @@ -186,9 +186,9 @@
-

+

npm-pkg - @11.18.0 + @11.19.0

Manages your package.json
diff --git a/deps/npm/docs/output/commands/npm-prefix.html b/deps/npm/docs/output/commands/npm-prefix.html index 887a4f26432c..0336ac659020 100644 --- a/deps/npm/docs/output/commands/npm-prefix.html +++ b/deps/npm/docs/output/commands/npm-prefix.html @@ -186,9 +186,9 @@
-

+

npm-prefix - @11.18.0 + @11.19.0

Display prefix
diff --git a/deps/npm/docs/output/commands/npm-profile.html b/deps/npm/docs/output/commands/npm-profile.html index c45be8a6052c..8c0d0a337c8c 100644 --- a/deps/npm/docs/output/commands/npm-profile.html +++ b/deps/npm/docs/output/commands/npm-profile.html @@ -186,9 +186,9 @@
-

+

npm-profile - @11.18.0 + @11.19.0

Change settings on your registry profile
diff --git a/deps/npm/docs/output/commands/npm-prune.html b/deps/npm/docs/output/commands/npm-prune.html index bfb3914265bd..d340e0fd84a6 100644 --- a/deps/npm/docs/output/commands/npm-prune.html +++ b/deps/npm/docs/output/commands/npm-prune.html @@ -186,9 +186,9 @@
-

+

npm-prune - @11.18.0 + @11.19.0

Remove extraneous packages
diff --git a/deps/npm/docs/output/commands/npm-publish.html b/deps/npm/docs/output/commands/npm-publish.html index f2a7ba059056..d1ee0efa5d16 100644 --- a/deps/npm/docs/output/commands/npm-publish.html +++ b/deps/npm/docs/output/commands/npm-publish.html @@ -186,9 +186,9 @@
-

+

npm-publish - @11.18.0 + @11.19.0

Publish a package
diff --git a/deps/npm/docs/output/commands/npm-query.html b/deps/npm/docs/output/commands/npm-query.html index 7d9bf80b88d2..a8347390a34a 100644 --- a/deps/npm/docs/output/commands/npm-query.html +++ b/deps/npm/docs/output/commands/npm-query.html @@ -186,9 +186,9 @@
-

+

npm-query - @11.18.0 + @11.19.0

Dependency selector query
diff --git a/deps/npm/docs/output/commands/npm-rebuild.html b/deps/npm/docs/output/commands/npm-rebuild.html index 5245b156b685..8c2277d8a914 100644 --- a/deps/npm/docs/output/commands/npm-rebuild.html +++ b/deps/npm/docs/output/commands/npm-rebuild.html @@ -186,9 +186,9 @@
-

+

npm-rebuild - @11.18.0 + @11.19.0

Rebuild a package
diff --git a/deps/npm/docs/output/commands/npm-repo.html b/deps/npm/docs/output/commands/npm-repo.html index c210a4efd308..b60cd8bc1ddd 100644 --- a/deps/npm/docs/output/commands/npm-repo.html +++ b/deps/npm/docs/output/commands/npm-repo.html @@ -186,9 +186,9 @@
-

+

npm-repo - @11.18.0 + @11.19.0

Open package repository page in the browser
diff --git a/deps/npm/docs/output/commands/npm-restart.html b/deps/npm/docs/output/commands/npm-restart.html index cc0ee51c458e..960530ad2975 100644 --- a/deps/npm/docs/output/commands/npm-restart.html +++ b/deps/npm/docs/output/commands/npm-restart.html @@ -186,9 +186,9 @@
-

+

npm-restart - @11.18.0 + @11.19.0

Restart a package
diff --git a/deps/npm/docs/output/commands/npm-root.html b/deps/npm/docs/output/commands/npm-root.html index 6f0af8585373..b5ad8c5c1389 100644 --- a/deps/npm/docs/output/commands/npm-root.html +++ b/deps/npm/docs/output/commands/npm-root.html @@ -186,9 +186,9 @@
-

+

npm-root - @11.18.0 + @11.19.0

Display npm root
diff --git a/deps/npm/docs/output/commands/npm-run.html b/deps/npm/docs/output/commands/npm-run.html index c7119fea964e..985df67fddef 100644 --- a/deps/npm/docs/output/commands/npm-run.html +++ b/deps/npm/docs/output/commands/npm-run.html @@ -186,9 +186,9 @@
-

+

npm-run - @11.18.0 + @11.19.0

Run arbitrary package scripts
diff --git a/deps/npm/docs/output/commands/npm-sbom.html b/deps/npm/docs/output/commands/npm-sbom.html index 70eb4780c5c9..db72c21adc4f 100644 --- a/deps/npm/docs/output/commands/npm-sbom.html +++ b/deps/npm/docs/output/commands/npm-sbom.html @@ -186,9 +186,9 @@
-

+

npm-sbom - @11.18.0 + @11.19.0

Generate a Software Bill of Materials (SBOM)
diff --git a/deps/npm/docs/output/commands/npm-search.html b/deps/npm/docs/output/commands/npm-search.html index b417f9ba815c..f4764833e25a 100644 --- a/deps/npm/docs/output/commands/npm-search.html +++ b/deps/npm/docs/output/commands/npm-search.html @@ -186,9 +186,9 @@
-

+

npm-search - @11.18.0 + @11.19.0

Search for packages
diff --git a/deps/npm/docs/output/commands/npm-set.html b/deps/npm/docs/output/commands/npm-set.html index dfd53bcf72f2..d0fe94ba7e98 100644 --- a/deps/npm/docs/output/commands/npm-set.html +++ b/deps/npm/docs/output/commands/npm-set.html @@ -186,9 +186,9 @@
-

+

npm-set - @11.18.0 + @11.19.0

Set a value in the npm configuration
diff --git a/deps/npm/docs/output/commands/npm-shrinkwrap.html b/deps/npm/docs/output/commands/npm-shrinkwrap.html index 8989040dd9f6..85fb2b99986f 100644 --- a/deps/npm/docs/output/commands/npm-shrinkwrap.html +++ b/deps/npm/docs/output/commands/npm-shrinkwrap.html @@ -186,9 +186,9 @@
-

+

npm-shrinkwrap - @11.18.0 + @11.19.0

Lock down dependency versions for publication
diff --git a/deps/npm/docs/output/commands/npm-stage.html b/deps/npm/docs/output/commands/npm-stage.html index de76caf167e9..225d4970fad3 100644 --- a/deps/npm/docs/output/commands/npm-stage.html +++ b/deps/npm/docs/output/commands/npm-stage.html @@ -186,9 +186,9 @@
-

+

npm-stage - @11.18.0 + @11.19.0

Stage packages for publishing
diff --git a/deps/npm/docs/output/commands/npm-star.html b/deps/npm/docs/output/commands/npm-star.html index 0642cfefb4bc..5521c413462c 100644 --- a/deps/npm/docs/output/commands/npm-star.html +++ b/deps/npm/docs/output/commands/npm-star.html @@ -186,9 +186,9 @@
-

+

npm-star - @11.18.0 + @11.19.0

Mark your favorite packages
diff --git a/deps/npm/docs/output/commands/npm-stars.html b/deps/npm/docs/output/commands/npm-stars.html index 3f586b4fb0a8..9f497020875d 100644 --- a/deps/npm/docs/output/commands/npm-stars.html +++ b/deps/npm/docs/output/commands/npm-stars.html @@ -186,9 +186,9 @@
-

+

npm-stars - @11.18.0 + @11.19.0

View packages marked as favorites
diff --git a/deps/npm/docs/output/commands/npm-start.html b/deps/npm/docs/output/commands/npm-start.html index 35a7c7798f85..4a6ec9e7e2c8 100644 --- a/deps/npm/docs/output/commands/npm-start.html +++ b/deps/npm/docs/output/commands/npm-start.html @@ -186,9 +186,9 @@
-

+

npm-start - @11.18.0 + @11.19.0

Start a package
diff --git a/deps/npm/docs/output/commands/npm-stop.html b/deps/npm/docs/output/commands/npm-stop.html index 37c8d91d4ba0..243f702c961e 100644 --- a/deps/npm/docs/output/commands/npm-stop.html +++ b/deps/npm/docs/output/commands/npm-stop.html @@ -186,9 +186,9 @@
-

+

npm-stop - @11.18.0 + @11.19.0

Stop a package
diff --git a/deps/npm/docs/output/commands/npm-team.html b/deps/npm/docs/output/commands/npm-team.html index 66263e4f1f53..0baf06db4b1a 100644 --- a/deps/npm/docs/output/commands/npm-team.html +++ b/deps/npm/docs/output/commands/npm-team.html @@ -186,9 +186,9 @@
-

+

npm-team - @11.18.0 + @11.19.0

Manage organization teams and team memberships
diff --git a/deps/npm/docs/output/commands/npm-test.html b/deps/npm/docs/output/commands/npm-test.html index b9e84c476715..2d46aafd0b14 100644 --- a/deps/npm/docs/output/commands/npm-test.html +++ b/deps/npm/docs/output/commands/npm-test.html @@ -186,9 +186,9 @@
-

+

npm-test - @11.18.0 + @11.19.0

Test a package
diff --git a/deps/npm/docs/output/commands/npm-token.html b/deps/npm/docs/output/commands/npm-token.html index c3c59a86cabe..58a528ce2180 100644 --- a/deps/npm/docs/output/commands/npm-token.html +++ b/deps/npm/docs/output/commands/npm-token.html @@ -186,9 +186,9 @@
-

+

npm-token - @11.18.0 + @11.19.0

Manage your authentication tokens
diff --git a/deps/npm/docs/output/commands/npm-trust.html b/deps/npm/docs/output/commands/npm-trust.html index 00d389d044c8..429e22957b18 100644 --- a/deps/npm/docs/output/commands/npm-trust.html +++ b/deps/npm/docs/output/commands/npm-trust.html @@ -186,9 +186,9 @@
-

+

npm-trust - @11.18.0 + @11.19.0

Manage trusted publishing relationships between packages and CI/CD providers
diff --git a/deps/npm/docs/output/commands/npm-undeprecate.html b/deps/npm/docs/output/commands/npm-undeprecate.html index 06ca5b07fbb3..67b6d048c079 100644 --- a/deps/npm/docs/output/commands/npm-undeprecate.html +++ b/deps/npm/docs/output/commands/npm-undeprecate.html @@ -186,9 +186,9 @@
-

+

npm-undeprecate - @11.18.0 + @11.19.0

Undeprecate a version of a package
diff --git a/deps/npm/docs/output/commands/npm-uninstall.html b/deps/npm/docs/output/commands/npm-uninstall.html index f82cbbc1dcec..8197d3646c62 100644 --- a/deps/npm/docs/output/commands/npm-uninstall.html +++ b/deps/npm/docs/output/commands/npm-uninstall.html @@ -186,9 +186,9 @@
-

+

npm-uninstall - @11.18.0 + @11.19.0

Remove a package
diff --git a/deps/npm/docs/output/commands/npm-unpublish.html b/deps/npm/docs/output/commands/npm-unpublish.html index f6557a7a40ab..21b698cad72e 100644 --- a/deps/npm/docs/output/commands/npm-unpublish.html +++ b/deps/npm/docs/output/commands/npm-unpublish.html @@ -186,9 +186,9 @@
-

+

npm-unpublish - @11.18.0 + @11.19.0

Remove a package from the registry
diff --git a/deps/npm/docs/output/commands/npm-unstar.html b/deps/npm/docs/output/commands/npm-unstar.html index d91decf5f963..2564643c7435 100644 --- a/deps/npm/docs/output/commands/npm-unstar.html +++ b/deps/npm/docs/output/commands/npm-unstar.html @@ -186,9 +186,9 @@
-

+

npm-unstar - @11.18.0 + @11.19.0

Remove an item from your favorite packages
diff --git a/deps/npm/docs/output/commands/npm-update.html b/deps/npm/docs/output/commands/npm-update.html index 9b395086246d..c89bc400aa3c 100644 --- a/deps/npm/docs/output/commands/npm-update.html +++ b/deps/npm/docs/output/commands/npm-update.html @@ -186,9 +186,9 @@
-

+

npm-update - @11.18.0 + @11.19.0

Update packages
diff --git a/deps/npm/docs/output/commands/npm-version.html b/deps/npm/docs/output/commands/npm-version.html index 17e504dd49ef..2cb36d263256 100644 --- a/deps/npm/docs/output/commands/npm-version.html +++ b/deps/npm/docs/output/commands/npm-version.html @@ -186,9 +186,9 @@
-

+

npm-version - @11.18.0 + @11.19.0

Bump a package version
diff --git a/deps/npm/docs/output/commands/npm-view.html b/deps/npm/docs/output/commands/npm-view.html index 6424eed09df9..2356b4e79166 100644 --- a/deps/npm/docs/output/commands/npm-view.html +++ b/deps/npm/docs/output/commands/npm-view.html @@ -186,9 +186,9 @@
-

+

npm-view - @11.18.0 + @11.19.0

View registry info
diff --git a/deps/npm/docs/output/commands/npm-whoami.html b/deps/npm/docs/output/commands/npm-whoami.html index 9830a8c3c8d2..bd768a237695 100644 --- a/deps/npm/docs/output/commands/npm-whoami.html +++ b/deps/npm/docs/output/commands/npm-whoami.html @@ -186,9 +186,9 @@
-

+

npm-whoami - @11.18.0 + @11.19.0

Display npm username
diff --git a/deps/npm/docs/output/commands/npm.html b/deps/npm/docs/output/commands/npm.html index cef7c6a5f2c8..cc749dc6e59a 100644 --- a/deps/npm/docs/output/commands/npm.html +++ b/deps/npm/docs/output/commands/npm.html @@ -186,9 +186,9 @@
-

+

npm - @11.18.0 + @11.19.0

javascript package manager
@@ -203,7 +203,7 @@

Table of contents

Note: This command is unaware of workspaces.

Version

-

11.18.0

+

11.19.0

Description

npm is the package manager for the Node JavaScript platform. It puts modules in place so that node can find them, and manages dependency conflicts intelligently.

diff --git a/deps/npm/docs/output/commands/npx.html b/deps/npm/docs/output/commands/npx.html index f06143a5c2cd..8abb0349d715 100644 --- a/deps/npm/docs/output/commands/npx.html +++ b/deps/npm/docs/output/commands/npx.html @@ -186,9 +186,9 @@
-

+

npx - @11.18.0 + @11.19.0

Run a command from a local or remote npm package
diff --git a/deps/npm/docs/output/configuring-npm/folders.html b/deps/npm/docs/output/configuring-npm/folders.html index 78f9f0f1ebd7..7b04e59dbed2 100644 --- a/deps/npm/docs/output/configuring-npm/folders.html +++ b/deps/npm/docs/output/configuring-npm/folders.html @@ -186,9 +186,9 @@
-

+

Folders - @11.18.0 + @11.19.0

Folder structures used by npm
diff --git a/deps/npm/docs/output/configuring-npm/install.html b/deps/npm/docs/output/configuring-npm/install.html index 9ae64ab1d402..d607ef84697b 100644 --- a/deps/npm/docs/output/configuring-npm/install.html +++ b/deps/npm/docs/output/configuring-npm/install.html @@ -186,9 +186,9 @@
-

+

Install - @11.18.0 + @11.19.0

Download and install node and npm
diff --git a/deps/npm/docs/output/configuring-npm/npm-global.html b/deps/npm/docs/output/configuring-npm/npm-global.html index 78f9f0f1ebd7..7b04e59dbed2 100644 --- a/deps/npm/docs/output/configuring-npm/npm-global.html +++ b/deps/npm/docs/output/configuring-npm/npm-global.html @@ -186,9 +186,9 @@
-

+

Folders - @11.18.0 + @11.19.0

Folder structures used by npm
diff --git a/deps/npm/docs/output/configuring-npm/npm-json.html b/deps/npm/docs/output/configuring-npm/npm-json.html index cb9a3626a0ce..0cdb967a8c34 100644 --- a/deps/npm/docs/output/configuring-npm/npm-json.html +++ b/deps/npm/docs/output/configuring-npm/npm-json.html @@ -186,9 +186,9 @@
-

+

package.json - @11.18.0 + @11.19.0

Specifics of npm's package.json handling
diff --git a/deps/npm/docs/output/configuring-npm/npm-shrinkwrap-json.html b/deps/npm/docs/output/configuring-npm/npm-shrinkwrap-json.html index ecbc9d9e1502..f26ba837f409 100644 --- a/deps/npm/docs/output/configuring-npm/npm-shrinkwrap-json.html +++ b/deps/npm/docs/output/configuring-npm/npm-shrinkwrap-json.html @@ -186,9 +186,9 @@
-

+

npm-shrinkwrap.json - @11.18.0 + @11.19.0

A publishable lockfile
diff --git a/deps/npm/docs/output/configuring-npm/npmrc.html b/deps/npm/docs/output/configuring-npm/npmrc.html index bad73af8064b..6e88197b3fbf 100644 --- a/deps/npm/docs/output/configuring-npm/npmrc.html +++ b/deps/npm/docs/output/configuring-npm/npmrc.html @@ -186,9 +186,9 @@
-

+

.npmrc - @11.18.0 + @11.19.0

The npm config files
diff --git a/deps/npm/docs/output/configuring-npm/package-json.html b/deps/npm/docs/output/configuring-npm/package-json.html index cb9a3626a0ce..0cdb967a8c34 100644 --- a/deps/npm/docs/output/configuring-npm/package-json.html +++ b/deps/npm/docs/output/configuring-npm/package-json.html @@ -186,9 +186,9 @@
-

+

package.json - @11.18.0 + @11.19.0

Specifics of npm's package.json handling
diff --git a/deps/npm/docs/output/configuring-npm/package-lock-json.html b/deps/npm/docs/output/configuring-npm/package-lock-json.html index 834e14f62638..68476b2f14e4 100644 --- a/deps/npm/docs/output/configuring-npm/package-lock-json.html +++ b/deps/npm/docs/output/configuring-npm/package-lock-json.html @@ -186,9 +186,9 @@
-

+

package-lock.json - @11.18.0 + @11.19.0

A manifestation of the manifest
diff --git a/deps/npm/docs/output/using-npm/config.html b/deps/npm/docs/output/using-npm/config.html index f52caeefd184..9d288a3a2e86 100644 --- a/deps/npm/docs/output/using-npm/config.html +++ b/deps/npm/docs/output/using-npm/config.html @@ -186,9 +186,9 @@
-

+

Config - @11.18.0 + @11.19.0

About npm configuration
diff --git a/deps/npm/docs/output/using-npm/dependency-selectors.html b/deps/npm/docs/output/using-npm/dependency-selectors.html index f3001125637e..80acfdd6ea80 100644 --- a/deps/npm/docs/output/using-npm/dependency-selectors.html +++ b/deps/npm/docs/output/using-npm/dependency-selectors.html @@ -186,9 +186,9 @@
-

+

Dependency Selectors - @11.18.0 + @11.19.0

Dependency Selector Syntax & Querying
diff --git a/deps/npm/docs/output/using-npm/developers.html b/deps/npm/docs/output/using-npm/developers.html index 66a27a8a3dd6..5457f4a7943b 100644 --- a/deps/npm/docs/output/using-npm/developers.html +++ b/deps/npm/docs/output/using-npm/developers.html @@ -186,9 +186,9 @@
-

+

Developers - @11.18.0 + @11.19.0

Developer guide
diff --git a/deps/npm/docs/output/using-npm/logging.html b/deps/npm/docs/output/using-npm/logging.html index 19c47d4adc39..62b819e82646 100644 --- a/deps/npm/docs/output/using-npm/logging.html +++ b/deps/npm/docs/output/using-npm/logging.html @@ -186,9 +186,9 @@
-

+

Logging - @11.18.0 + @11.19.0

Why, What & How we Log
diff --git a/deps/npm/docs/output/using-npm/orgs.html b/deps/npm/docs/output/using-npm/orgs.html index 3444f3bf1c4c..39559c361b78 100644 --- a/deps/npm/docs/output/using-npm/orgs.html +++ b/deps/npm/docs/output/using-npm/orgs.html @@ -186,9 +186,9 @@
-

+

Organizations - @11.18.0 + @11.19.0

Working with teams & organizations
diff --git a/deps/npm/docs/output/using-npm/package-spec.html b/deps/npm/docs/output/using-npm/package-spec.html index 92cd9d2af617..82b8d1d9ad2e 100644 --- a/deps/npm/docs/output/using-npm/package-spec.html +++ b/deps/npm/docs/output/using-npm/package-spec.html @@ -186,9 +186,9 @@
-

+

Package spec - @11.18.0 + @11.19.0

Package name specifier
diff --git a/deps/npm/docs/output/using-npm/registry.html b/deps/npm/docs/output/using-npm/registry.html index ed47e3a6ed71..b3d34df82033 100644 --- a/deps/npm/docs/output/using-npm/registry.html +++ b/deps/npm/docs/output/using-npm/registry.html @@ -186,9 +186,9 @@
-

+

Registry - @11.18.0 + @11.19.0

The JavaScript Package Registry
diff --git a/deps/npm/docs/output/using-npm/removal.html b/deps/npm/docs/output/using-npm/removal.html index 57f02ed1ee2b..b85ee02590cc 100644 --- a/deps/npm/docs/output/using-npm/removal.html +++ b/deps/npm/docs/output/using-npm/removal.html @@ -186,9 +186,9 @@
-

+

Removal - @11.18.0 + @11.19.0

Cleaning the slate
diff --git a/deps/npm/docs/output/using-npm/scope.html b/deps/npm/docs/output/using-npm/scope.html index 118613479205..f2e733bc9ebf 100644 --- a/deps/npm/docs/output/using-npm/scope.html +++ b/deps/npm/docs/output/using-npm/scope.html @@ -186,9 +186,9 @@
-

+

Scope - @11.18.0 + @11.19.0

Scoped packages
diff --git a/deps/npm/docs/output/using-npm/scripts.html b/deps/npm/docs/output/using-npm/scripts.html index 65ff19a4673a..c2a9641f62d2 100644 --- a/deps/npm/docs/output/using-npm/scripts.html +++ b/deps/npm/docs/output/using-npm/scripts.html @@ -186,9 +186,9 @@
-

+

Scripts - @11.18.0 + @11.19.0

How npm handles the "scripts" field
diff --git a/deps/npm/docs/output/using-npm/workspaces.html b/deps/npm/docs/output/using-npm/workspaces.html index fd75a0ad62a6..3a9986755339 100644 --- a/deps/npm/docs/output/using-npm/workspaces.html +++ b/deps/npm/docs/output/using-npm/workspaces.html @@ -186,9 +186,9 @@
-

+

Workspaces - @11.18.0 + @11.19.0

Working with workspaces
diff --git a/deps/npm/lib/commands/owner.js b/deps/npm/lib/commands/owner.js index d53c1c97b77f..e7540c9bf7b8 100644 --- a/deps/npm/lib/commands/owner.js +++ b/deps/npm/lib/commands/owner.js @@ -160,7 +160,10 @@ class Owner extends BaseCommand { let u try { - u = await npmFetch.json(uri, this.npm.flatOptions) + u = await npmFetch.json(uri, { + ...this.npm.flatOptions, + spec, + }) } catch (err) { log.error('owner mutate', `Error getting user data for ${user}`) throw err diff --git a/deps/npm/lib/commands/pack.js b/deps/npm/lib/commands/pack.js index ca57de03997b..be4a1b240bea 100644 --- a/deps/npm/lib/commands/pack.js +++ b/deps/npm/lib/commands/pack.js @@ -2,6 +2,10 @@ const pacote = require('pacote') const libpack = require('libnpmpack') const npa = require('npm-package-arg') const { log, output } = require('proc-log') +const { + isReleaseAgeExcluded, + trustedSpecName, +} = require('@npmcli/arborist/lib/release-age-exclude.js') const { getContents, logTar } = require('../utils/tar.js') const BaseCommand = require('../base-cmd.js') @@ -35,8 +39,12 @@ class Pack extends BaseCommand { const manifests = [] for (const arg of args) { const spec = npa(arg) + const options = isReleaseAgeExcluded( + trustedSpecName(spec), + this.npm.flatOptions.minReleaseAgeExclude + ) ? { ...this.npm.flatOptions, before: null } : this.npm.flatOptions const manifest = await pacote.manifest(spec, { - ...this.npm.flatOptions, + ...options, Arborist, preferOnline: true, _isRoot: true, @@ -44,14 +52,14 @@ class Pack extends BaseCommand { if (!manifest._id) { throw new Error('Invalid package, must have name and version') } - manifests.push({ arg, manifest }) + manifests.push({ arg, manifest, options }) } // Load tarball names up for printing afterward to isolate from the noise generated during packing const tarballs = [] - for (const { arg, manifest } of manifests) { + for (const { arg, manifest, options } of manifests) { const tarballData = await libpack(arg, { - ...this.npm.flatOptions, + ...options, foregroundScripts: this.npm.config.isDefault('foreground-scripts') ? true : this.npm.config.get('foreground-scripts'), diff --git a/deps/npm/lib/utils/allow-scripts-writer.js b/deps/npm/lib/utils/allow-scripts-writer.js index b9476905d427..6964279f2f2e 100644 --- a/deps/npm/lib/utils/allow-scripts-writer.js +++ b/deps/npm/lib/utils/allow-scripts-writer.js @@ -50,7 +50,7 @@ const versionedKeyFor = (node) => { // parse (private-registry mirror, alternate CDN URL shape). Leave a // breadcrumb so users notice when policy keys are silently pruned. log.silly( - 'allow-scripts', + 'install-scripts', `unable to derive trusted versioned key for ${node.path || node.name || ''} ` + `(resolved: ${resolved}); key will be pruned on next save` ) diff --git a/deps/npm/lib/utils/reify-output.js b/deps/npm/lib/utils/reify-output.js index 6e25f9434063..fa229a318d26 100644 --- a/deps/npm/lib/utils/reify-output.js +++ b/deps/npm/lib/utils/reify-output.js @@ -259,7 +259,7 @@ const unreviewedScriptsMessage = (npm, unreviewedScripts) => { }) log.warn( - 'allow-scripts', + 'install-scripts', [ header, ...lines, diff --git a/deps/npm/lib/utils/resolve-allow-scripts.js b/deps/npm/lib/utils/resolve-allow-scripts.js index b658e1a68ad0..ef0236992ddf 100644 --- a/deps/npm/lib/utils/resolve-allow-scripts.js +++ b/deps/npm/lib/utils/resolve-allow-scripts.js @@ -46,7 +46,7 @@ const validatePolicy = (policy, sourceLabel) => { try { parsed = npa(key) } catch { - log.warn('allow-scripts', `${sourceLabel}: ignoring unparseable entry "${key}"`) + log.warn('install-scripts', `${sourceLabel}: ignoring unparseable entry "${key}"`) continue } if (parsed.type === 'tag') { @@ -54,7 +54,7 @@ const validatePolicy = (policy, sourceLabel) => { // only — the matcher has no way to verify what the tag points at // when scripts run. Reject for the same reason as semver ranges. log.warn( - 'allow-scripts', + 'install-scripts', `${sourceLabel}: ignoring "${key}" — dist-tag specs (@latest, @next, ...) are not allowed; ` + 'use exact versions joined by "||", or the bare package name, instead' ) @@ -66,7 +66,7 @@ const validatePolicy = (policy, sourceLabel) => { || parsed.rawSpec === '*' if (!isNameOnly && !isExactVersionDisjunction(parsed.fetchSpec)) { log.warn( - 'allow-scripts', + 'install-scripts', `${sourceLabel}: ignoring "${key}" — semver ranges (^, ~, >=, <) are not allowed; ` + 'use exact versions joined by "||" instead' ) @@ -137,7 +137,7 @@ const resolveAllowScripts = async (npm, { skipProjectConfig = false } = {}) => { } } } catch (err) { - log.silly('allow-scripts', 'no package.json at prefix', err.message) + log.silly('install-scripts', 'no package.json at prefix', err.message) } } @@ -154,7 +154,7 @@ const resolveAllowScripts = async (npm, { skipProjectConfig = false } = {}) => { // set, package.json is never consulted. if (rc) { log.warn( - 'allow-scripts', + 'install-scripts', '.npmrc allow-scripts setting is being ignored because --allow-scripts was passed on the command line' ) } @@ -164,7 +164,7 @@ const resolveAllowScripts = async (npm, { skipProjectConfig = false } = {}) => { if (pkg) { if (rc) { log.warn( - 'allow-scripts', + 'install-scripts', '.npmrc allow-scripts setting is being ignored because package.json declares its own allowScripts field' ) } diff --git a/deps/npm/lib/utils/warn-workspace-allow-scripts.js b/deps/npm/lib/utils/warn-workspace-allow-scripts.js index e46e6cf4d2a1..f867e93aa040 100644 --- a/deps/npm/lib/utils/warn-workspace-allow-scripts.js +++ b/deps/npm/lib/utils/warn-workspace-allow-scripts.js @@ -28,7 +28,7 @@ const warnWorkspaceAllowScripts = (tree) => { for (const node of findWorkspaceAllowScripts(tree)) { const name = node.packageName || node.name log.warn( - 'allow-scripts', + 'install-scripts', `allowScripts in workspace ${name} (${node.path}) is ignored. ` + 'Move the field to the project root package.json.' ) diff --git a/deps/npm/man/man1/npm-access.1 b/deps/npm/man/man1/npm-access.1 index 707bc69f167c..3b7d77d8962f 100644 --- a/deps/npm/man/man1/npm-access.1 +++ b/deps/npm/man/man1/npm-access.1 @@ -1,4 +1,4 @@ -.TH "NPM-ACCESS" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-ACCESS" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-access\fR - Set access level on published packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-adduser.1 b/deps/npm/man/man1/npm-adduser.1 index 091bcf2e3fe8..53ed267a0aa4 100644 --- a/deps/npm/man/man1/npm-adduser.1 +++ b/deps/npm/man/man1/npm-adduser.1 @@ -1,4 +1,4 @@ -.TH "NPM-ADDUSER" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-ADDUSER" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-adduser\fR - Add a registry user account .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-approve-scripts.1 b/deps/npm/man/man1/npm-approve-scripts.1 index cd2aee3190b7..943459b20562 100644 --- a/deps/npm/man/man1/npm-approve-scripts.1 +++ b/deps/npm/man/man1/npm-approve-scripts.1 @@ -1,4 +1,4 @@ -.TH "NPM-APPROVE-SCRIPTS" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-APPROVE-SCRIPTS" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-approve-scripts\fR - Approve install scripts for specific dependencies .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-audit.1 b/deps/npm/man/man1/npm-audit.1 index 4c9bf13ecc27..77fbcbe595ef 100644 --- a/deps/npm/man/man1/npm-audit.1 +++ b/deps/npm/man/man1/npm-audit.1 @@ -1,4 +1,4 @@ -.TH "NPM-AUDIT" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-AUDIT" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-audit\fR - Run a security audit .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-bugs.1 b/deps/npm/man/man1/npm-bugs.1 index 529f369d12da..05a37e504c7e 100644 --- a/deps/npm/man/man1/npm-bugs.1 +++ b/deps/npm/man/man1/npm-bugs.1 @@ -1,4 +1,4 @@ -.TH "NPM-BUGS" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-BUGS" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-bugs\fR - Report bugs for a package in a web browser .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-cache.1 b/deps/npm/man/man1/npm-cache.1 index 1f609ffd785a..fd725dd57fe2 100644 --- a/deps/npm/man/man1/npm-cache.1 +++ b/deps/npm/man/man1/npm-cache.1 @@ -1,4 +1,4 @@ -.TH "NPM-CACHE" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-CACHE" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-cache\fR - Manipulates packages cache .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-ci.1 b/deps/npm/man/man1/npm-ci.1 index 2e62f4a1e73f..bbae51d8e983 100644 --- a/deps/npm/man/man1/npm-ci.1 +++ b/deps/npm/man/man1/npm-ci.1 @@ -1,4 +1,4 @@ -.TH "NPM-CI" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-CI" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-ci\fR - Clean install a project .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-completion.1 b/deps/npm/man/man1/npm-completion.1 index 5327001ec3f1..d01c54ab63af 100644 --- a/deps/npm/man/man1/npm-completion.1 +++ b/deps/npm/man/man1/npm-completion.1 @@ -1,4 +1,4 @@ -.TH "NPM-COMPLETION" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-COMPLETION" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-completion\fR - Tab Completion for npm .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-config.1 b/deps/npm/man/man1/npm-config.1 index 82fd108f72c0..34b79e9d1c57 100644 --- a/deps/npm/man/man1/npm-config.1 +++ b/deps/npm/man/man1/npm-config.1 @@ -1,4 +1,4 @@ -.TH "NPM-CONFIG" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-CONFIG" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-config\fR - Manage the npm configuration files .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-dedupe.1 b/deps/npm/man/man1/npm-dedupe.1 index 68e369035eae..0b11deb4d467 100644 --- a/deps/npm/man/man1/npm-dedupe.1 +++ b/deps/npm/man/man1/npm-dedupe.1 @@ -1,4 +1,4 @@ -.TH "NPM-DEDUPE" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-DEDUPE" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-dedupe\fR - Reduce duplication in the package tree .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-deny-scripts.1 b/deps/npm/man/man1/npm-deny-scripts.1 index 67daaa87b720..d8a41f57479e 100644 --- a/deps/npm/man/man1/npm-deny-scripts.1 +++ b/deps/npm/man/man1/npm-deny-scripts.1 @@ -1,4 +1,4 @@ -.TH "NPM-DENY-SCRIPTS" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-DENY-SCRIPTS" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-deny-scripts\fR - Deny install scripts for specific dependencies .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-deprecate.1 b/deps/npm/man/man1/npm-deprecate.1 index 1d89f067e730..c45dd75584ec 100644 --- a/deps/npm/man/man1/npm-deprecate.1 +++ b/deps/npm/man/man1/npm-deprecate.1 @@ -1,4 +1,4 @@ -.TH "NPM-DEPRECATE" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-DEPRECATE" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-deprecate\fR - Deprecate a version of a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-diff.1 b/deps/npm/man/man1/npm-diff.1 index cc06fc954386..45efb60c236c 100644 --- a/deps/npm/man/man1/npm-diff.1 +++ b/deps/npm/man/man1/npm-diff.1 @@ -1,4 +1,4 @@ -.TH "NPM-DIFF" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-DIFF" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-diff\fR - The registry diff command .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-dist-tag.1 b/deps/npm/man/man1/npm-dist-tag.1 index 6c3e74e3c651..989a2c64d307 100644 --- a/deps/npm/man/man1/npm-dist-tag.1 +++ b/deps/npm/man/man1/npm-dist-tag.1 @@ -1,4 +1,4 @@ -.TH "NPM-DIST-TAG" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-DIST-TAG" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-dist-tag\fR - Modify package distribution tags .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-docs.1 b/deps/npm/man/man1/npm-docs.1 index 952c18cf115f..ee614b710885 100644 --- a/deps/npm/man/man1/npm-docs.1 +++ b/deps/npm/man/man1/npm-docs.1 @@ -1,4 +1,4 @@ -.TH "NPM-DOCS" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-DOCS" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-docs\fR - Open documentation for a package in a web browser .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-doctor.1 b/deps/npm/man/man1/npm-doctor.1 index 1e8813a13a14..5344645b17ed 100644 --- a/deps/npm/man/man1/npm-doctor.1 +++ b/deps/npm/man/man1/npm-doctor.1 @@ -1,4 +1,4 @@ -.TH "NPM-DOCTOR" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-DOCTOR" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-doctor\fR - Check the health of your npm environment .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-edit.1 b/deps/npm/man/man1/npm-edit.1 index d46ea046ddb0..bca15e054f60 100644 --- a/deps/npm/man/man1/npm-edit.1 +++ b/deps/npm/man/man1/npm-edit.1 @@ -1,4 +1,4 @@ -.TH "NPM-EDIT" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-EDIT" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-edit\fR - Edit an installed package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-exec.1 b/deps/npm/man/man1/npm-exec.1 index 93245000380f..4b580f484bc1 100644 --- a/deps/npm/man/man1/npm-exec.1 +++ b/deps/npm/man/man1/npm-exec.1 @@ -1,4 +1,4 @@ -.TH "NPM-EXEC" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-EXEC" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-exec\fR - Run a command from a local or remote npm package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-explain.1 b/deps/npm/man/man1/npm-explain.1 index 0c4b22410bf6..20374a8fbb37 100644 --- a/deps/npm/man/man1/npm-explain.1 +++ b/deps/npm/man/man1/npm-explain.1 @@ -1,4 +1,4 @@ -.TH "NPM-EXPLAIN" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-EXPLAIN" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-explain\fR - Explain installed packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-explore.1 b/deps/npm/man/man1/npm-explore.1 index bd6788e463ea..bee110155bd5 100644 --- a/deps/npm/man/man1/npm-explore.1 +++ b/deps/npm/man/man1/npm-explore.1 @@ -1,4 +1,4 @@ -.TH "NPM-EXPLORE" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-EXPLORE" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-explore\fR - Browse an installed package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-find-dupes.1 b/deps/npm/man/man1/npm-find-dupes.1 index 8cab256a973b..a1cfc3abd0d2 100644 --- a/deps/npm/man/man1/npm-find-dupes.1 +++ b/deps/npm/man/man1/npm-find-dupes.1 @@ -1,4 +1,4 @@ -.TH "NPM-FIND-DUPES" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-FIND-DUPES" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-find-dupes\fR - Find duplication in the package tree .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-fund.1 b/deps/npm/man/man1/npm-fund.1 index a334c59e9900..ec516b28396d 100644 --- a/deps/npm/man/man1/npm-fund.1 +++ b/deps/npm/man/man1/npm-fund.1 @@ -1,4 +1,4 @@ -.TH "NPM-FUND" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-FUND" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-fund\fR - Retrieve funding information .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-get.1 b/deps/npm/man/man1/npm-get.1 index 1eb86d69f161..d4763eb28c8c 100644 --- a/deps/npm/man/man1/npm-get.1 +++ b/deps/npm/man/man1/npm-get.1 @@ -1,4 +1,4 @@ -.TH "NPM-GET" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-GET" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-get\fR - Get a value from the npm configuration .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-help-search.1 b/deps/npm/man/man1/npm-help-search.1 index b1752ad2b563..63213fa4188a 100644 --- a/deps/npm/man/man1/npm-help-search.1 +++ b/deps/npm/man/man1/npm-help-search.1 @@ -1,4 +1,4 @@ -.TH "NPM-HELP-SEARCH" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-HELP-SEARCH" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-help-search\fR - Search npm help documentation .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-help.1 b/deps/npm/man/man1/npm-help.1 index aef5394daaa2..59f2f1df9436 100644 --- a/deps/npm/man/man1/npm-help.1 +++ b/deps/npm/man/man1/npm-help.1 @@ -1,4 +1,4 @@ -.TH "NPM-HELP" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-HELP" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-help\fR - Get help on npm .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-init.1 b/deps/npm/man/man1/npm-init.1 index 990e0a58ed28..cf4c37761d14 100644 --- a/deps/npm/man/man1/npm-init.1 +++ b/deps/npm/man/man1/npm-init.1 @@ -1,4 +1,4 @@ -.TH "NPM-INIT" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-INIT" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-init\fR - Create a package.json file .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-install-ci-test.1 b/deps/npm/man/man1/npm-install-ci-test.1 index 88db36a8799a..e30140015a1c 100644 --- a/deps/npm/man/man1/npm-install-ci-test.1 +++ b/deps/npm/man/man1/npm-install-ci-test.1 @@ -1,4 +1,4 @@ -.TH "NPM-INSTALL-CI-TEST" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-INSTALL-CI-TEST" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-install-ci-test\fR - Install a project with a clean slate and run tests .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-install-scripts.1 b/deps/npm/man/man1/npm-install-scripts.1 index 457b55c9fb7a..907b48c50ff5 100644 --- a/deps/npm/man/man1/npm-install-scripts.1 +++ b/deps/npm/man/man1/npm-install-scripts.1 @@ -1,4 +1,4 @@ -.TH "NPM-INSTALL-SCRIPTS" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-INSTALL-SCRIPTS" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-install-scripts\fR - Manage install-script approvals for dependencies .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-install-test.1 b/deps/npm/man/man1/npm-install-test.1 index 56a0a454c8e6..88e0ed187377 100644 --- a/deps/npm/man/man1/npm-install-test.1 +++ b/deps/npm/man/man1/npm-install-test.1 @@ -1,4 +1,4 @@ -.TH "NPM-INSTALL-TEST" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-INSTALL-TEST" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-install-test\fR - Install package(s) and run tests .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-install.1 b/deps/npm/man/man1/npm-install.1 index 1c853a06d9e7..b1ccd245238d 100644 --- a/deps/npm/man/man1/npm-install.1 +++ b/deps/npm/man/man1/npm-install.1 @@ -1,4 +1,4 @@ -.TH "NPM-INSTALL" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-INSTALL" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-install\fR - Install a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-link.1 b/deps/npm/man/man1/npm-link.1 index 9b45424b521c..d124bcf66a33 100644 --- a/deps/npm/man/man1/npm-link.1 +++ b/deps/npm/man/man1/npm-link.1 @@ -1,4 +1,4 @@ -.TH "NPM-LINK" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-LINK" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-link\fR - Symlink a package folder .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-ll.1 b/deps/npm/man/man1/npm-ll.1 index 76cca43a86c5..982d8ee3a6c7 100644 --- a/deps/npm/man/man1/npm-ll.1 +++ b/deps/npm/man/man1/npm-ll.1 @@ -1,4 +1,4 @@ -.TH "NPM-LL" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-LL" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-ll\fR - List installed packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-login.1 b/deps/npm/man/man1/npm-login.1 index c08365bf41b2..392c5b25d84d 100644 --- a/deps/npm/man/man1/npm-login.1 +++ b/deps/npm/man/man1/npm-login.1 @@ -1,4 +1,4 @@ -.TH "NPM-LOGIN" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-LOGIN" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-login\fR - Login to a registry user account .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-logout.1 b/deps/npm/man/man1/npm-logout.1 index e27692a1f361..7ca6be27366c 100644 --- a/deps/npm/man/man1/npm-logout.1 +++ b/deps/npm/man/man1/npm-logout.1 @@ -1,4 +1,4 @@ -.TH "NPM-LOGOUT" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-LOGOUT" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-logout\fR - Log out of the registry .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-ls.1 b/deps/npm/man/man1/npm-ls.1 index 8d6a48be7adb..6fe6245988fe 100644 --- a/deps/npm/man/man1/npm-ls.1 +++ b/deps/npm/man/man1/npm-ls.1 @@ -1,4 +1,4 @@ -.TH "NPM-LS" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-LS" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-ls\fR - List installed packages .SS "Synopsis" @@ -20,7 +20,7 @@ Positional arguments are \fBname@version-range\fR identifiers, which will limit .P .RS 2 .nf -npm@11.18.0 /path/to/npm +npm@11.19.0 /path/to/npm └─┬ init-package-json@0.0.4 └── promzard@0.1.5 .fi diff --git a/deps/npm/man/man1/npm-org.1 b/deps/npm/man/man1/npm-org.1 index 87b14daf94bb..50e94a589d49 100644 --- a/deps/npm/man/man1/npm-org.1 +++ b/deps/npm/man/man1/npm-org.1 @@ -1,4 +1,4 @@ -.TH "NPM-ORG" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-ORG" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-org\fR - Manage orgs .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-outdated.1 b/deps/npm/man/man1/npm-outdated.1 index 0b2677ecbe64..a2ecbcc50b5c 100644 --- a/deps/npm/man/man1/npm-outdated.1 +++ b/deps/npm/man/man1/npm-outdated.1 @@ -1,4 +1,4 @@ -.TH "NPM-OUTDATED" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-OUTDATED" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-outdated\fR - Check for outdated packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-owner.1 b/deps/npm/man/man1/npm-owner.1 index b42394745474..3815d60d8b2b 100644 --- a/deps/npm/man/man1/npm-owner.1 +++ b/deps/npm/man/man1/npm-owner.1 @@ -1,4 +1,4 @@ -.TH "NPM-OWNER" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-OWNER" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-owner\fR - Manage package owners .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-pack.1 b/deps/npm/man/man1/npm-pack.1 index 4220d6eab1ba..f5f159779bdc 100644 --- a/deps/npm/man/man1/npm-pack.1 +++ b/deps/npm/man/man1/npm-pack.1 @@ -1,4 +1,4 @@ -.TH "NPM-PACK" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-PACK" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-pack\fR - Create a tarball from a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-ping.1 b/deps/npm/man/man1/npm-ping.1 index b214ef9bbe29..e44d02e67e88 100644 --- a/deps/npm/man/man1/npm-ping.1 +++ b/deps/npm/man/man1/npm-ping.1 @@ -1,4 +1,4 @@ -.TH "NPM-PING" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-PING" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-ping\fR - Ping npm registry .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-pkg.1 b/deps/npm/man/man1/npm-pkg.1 index e61280ea9b3b..ad4adfa8c654 100644 --- a/deps/npm/man/man1/npm-pkg.1 +++ b/deps/npm/man/man1/npm-pkg.1 @@ -1,4 +1,4 @@ -.TH "NPM-PKG" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-PKG" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-pkg\fR - Manages your package.json .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-prefix.1 b/deps/npm/man/man1/npm-prefix.1 index 8e5f186bc494..a810bc8c0140 100644 --- a/deps/npm/man/man1/npm-prefix.1 +++ b/deps/npm/man/man1/npm-prefix.1 @@ -1,4 +1,4 @@ -.TH "NPM-PREFIX" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-PREFIX" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-prefix\fR - Display prefix .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-profile.1 b/deps/npm/man/man1/npm-profile.1 index 7a3a7c3e73ae..0593fa483958 100644 --- a/deps/npm/man/man1/npm-profile.1 +++ b/deps/npm/man/man1/npm-profile.1 @@ -1,4 +1,4 @@ -.TH "NPM-PROFILE" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-PROFILE" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-profile\fR - Change settings on your registry profile .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-prune.1 b/deps/npm/man/man1/npm-prune.1 index d36938d3e27d..c62e7d6bd395 100644 --- a/deps/npm/man/man1/npm-prune.1 +++ b/deps/npm/man/man1/npm-prune.1 @@ -1,4 +1,4 @@ -.TH "NPM-PRUNE" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-PRUNE" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-prune\fR - Remove extraneous packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-publish.1 b/deps/npm/man/man1/npm-publish.1 index 36ad2d781aca..62676301d5ab 100644 --- a/deps/npm/man/man1/npm-publish.1 +++ b/deps/npm/man/man1/npm-publish.1 @@ -1,4 +1,4 @@ -.TH "NPM-PUBLISH" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-PUBLISH" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-publish\fR - Publish a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-query.1 b/deps/npm/man/man1/npm-query.1 index f5e07ec1954d..3ca4eb1b659f 100644 --- a/deps/npm/man/man1/npm-query.1 +++ b/deps/npm/man/man1/npm-query.1 @@ -1,4 +1,4 @@ -.TH "NPM-QUERY" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-QUERY" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-query\fR - Dependency selector query .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-rebuild.1 b/deps/npm/man/man1/npm-rebuild.1 index 972bb0fadefa..b0710049573e 100644 --- a/deps/npm/man/man1/npm-rebuild.1 +++ b/deps/npm/man/man1/npm-rebuild.1 @@ -1,4 +1,4 @@ -.TH "NPM-REBUILD" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-REBUILD" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-rebuild\fR - Rebuild a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-repo.1 b/deps/npm/man/man1/npm-repo.1 index f354706b7a96..46fdc0cb111e 100644 --- a/deps/npm/man/man1/npm-repo.1 +++ b/deps/npm/man/man1/npm-repo.1 @@ -1,4 +1,4 @@ -.TH "NPM-REPO" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-REPO" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-repo\fR - Open package repository page in the browser .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-restart.1 b/deps/npm/man/man1/npm-restart.1 index 754c1450517e..b552a5e2a86a 100644 --- a/deps/npm/man/man1/npm-restart.1 +++ b/deps/npm/man/man1/npm-restart.1 @@ -1,4 +1,4 @@ -.TH "NPM-RESTART" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-RESTART" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-restart\fR - Restart a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-root.1 b/deps/npm/man/man1/npm-root.1 index 21a04731a3c8..9d701e336bff 100644 --- a/deps/npm/man/man1/npm-root.1 +++ b/deps/npm/man/man1/npm-root.1 @@ -1,4 +1,4 @@ -.TH "NPM-ROOT" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-ROOT" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-root\fR - Display npm root .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-run.1 b/deps/npm/man/man1/npm-run.1 index c481d3ea7f3a..185111dbafbb 100644 --- a/deps/npm/man/man1/npm-run.1 +++ b/deps/npm/man/man1/npm-run.1 @@ -1,4 +1,4 @@ -.TH "NPM-RUN" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-RUN" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-run\fR - Run arbitrary package scripts .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-sbom.1 b/deps/npm/man/man1/npm-sbom.1 index 4528c2287d36..a8a74f9e02c6 100644 --- a/deps/npm/man/man1/npm-sbom.1 +++ b/deps/npm/man/man1/npm-sbom.1 @@ -1,4 +1,4 @@ -.TH "NPM-SBOM" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-SBOM" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-sbom\fR - Generate a Software Bill of Materials (SBOM) .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-search.1 b/deps/npm/man/man1/npm-search.1 index 88088ac83030..2a0fbbc58709 100644 --- a/deps/npm/man/man1/npm-search.1 +++ b/deps/npm/man/man1/npm-search.1 @@ -1,4 +1,4 @@ -.TH "NPM-SEARCH" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-SEARCH" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-search\fR - Search for packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-set.1 b/deps/npm/man/man1/npm-set.1 index feee9c27be67..4333006b47ff 100644 --- a/deps/npm/man/man1/npm-set.1 +++ b/deps/npm/man/man1/npm-set.1 @@ -1,4 +1,4 @@ -.TH "NPM-SET" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-SET" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-set\fR - Set a value in the npm configuration .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-shrinkwrap.1 b/deps/npm/man/man1/npm-shrinkwrap.1 index 12d1b01f5553..0fc20a6ccaeb 100644 --- a/deps/npm/man/man1/npm-shrinkwrap.1 +++ b/deps/npm/man/man1/npm-shrinkwrap.1 @@ -1,4 +1,4 @@ -.TH "NPM-SHRINKWRAP" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-SHRINKWRAP" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-shrinkwrap\fR - Lock down dependency versions for publication .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-stage.1 b/deps/npm/man/man1/npm-stage.1 index 9e14f2c5b251..9a677ba6b03e 100644 --- a/deps/npm/man/man1/npm-stage.1 +++ b/deps/npm/man/man1/npm-stage.1 @@ -1,4 +1,4 @@ -.TH "NPM-STAGE" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-STAGE" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-stage\fR - Stage packages for publishing .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-star.1 b/deps/npm/man/man1/npm-star.1 index 37d94b17a7c3..dbabe90b0bd6 100644 --- a/deps/npm/man/man1/npm-star.1 +++ b/deps/npm/man/man1/npm-star.1 @@ -1,4 +1,4 @@ -.TH "NPM-STAR" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-STAR" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-star\fR - Mark your favorite packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-stars.1 b/deps/npm/man/man1/npm-stars.1 index 4db22ed60395..2999dd8abbf0 100644 --- a/deps/npm/man/man1/npm-stars.1 +++ b/deps/npm/man/man1/npm-stars.1 @@ -1,4 +1,4 @@ -.TH "NPM-STARS" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-STARS" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-stars\fR - View packages marked as favorites .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-start.1 b/deps/npm/man/man1/npm-start.1 index 0729d9cd1754..c8966bd38a68 100644 --- a/deps/npm/man/man1/npm-start.1 +++ b/deps/npm/man/man1/npm-start.1 @@ -1,4 +1,4 @@ -.TH "NPM-START" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-START" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-start\fR - Start a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-stop.1 b/deps/npm/man/man1/npm-stop.1 index e9261943c03c..c900d58437f5 100644 --- a/deps/npm/man/man1/npm-stop.1 +++ b/deps/npm/man/man1/npm-stop.1 @@ -1,4 +1,4 @@ -.TH "NPM-STOP" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-STOP" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-stop\fR - Stop a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-team.1 b/deps/npm/man/man1/npm-team.1 index d1e9e481cd88..4de50f8c6b06 100644 --- a/deps/npm/man/man1/npm-team.1 +++ b/deps/npm/man/man1/npm-team.1 @@ -1,4 +1,4 @@ -.TH "NPM-TEAM" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-TEAM" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-team\fR - Manage organization teams and team memberships .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-test.1 b/deps/npm/man/man1/npm-test.1 index a9c28e79e980..311ba399498b 100644 --- a/deps/npm/man/man1/npm-test.1 +++ b/deps/npm/man/man1/npm-test.1 @@ -1,4 +1,4 @@ -.TH "NPM-TEST" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-TEST" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-test\fR - Test a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-token.1 b/deps/npm/man/man1/npm-token.1 index 383a825d14fb..4b1cbae98b24 100644 --- a/deps/npm/man/man1/npm-token.1 +++ b/deps/npm/man/man1/npm-token.1 @@ -1,4 +1,4 @@ -.TH "NPM-TOKEN" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-TOKEN" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-token\fR - Manage your authentication tokens .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-trust.1 b/deps/npm/man/man1/npm-trust.1 index 5dc2dfc702a2..f8de6022c0f0 100644 --- a/deps/npm/man/man1/npm-trust.1 +++ b/deps/npm/man/man1/npm-trust.1 @@ -1,4 +1,4 @@ -.TH "NPM-TRUST" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-TRUST" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-trust\fR - Manage trusted publishing relationships between packages and CI/CD providers .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-undeprecate.1 b/deps/npm/man/man1/npm-undeprecate.1 index 2fc8e0050091..1a26688b4530 100644 --- a/deps/npm/man/man1/npm-undeprecate.1 +++ b/deps/npm/man/man1/npm-undeprecate.1 @@ -1,4 +1,4 @@ -.TH "NPM-UNDEPRECATE" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-UNDEPRECATE" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-undeprecate\fR - Undeprecate a version of a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-uninstall.1 b/deps/npm/man/man1/npm-uninstall.1 index d2843a8a3305..39791e588e7e 100644 --- a/deps/npm/man/man1/npm-uninstall.1 +++ b/deps/npm/man/man1/npm-uninstall.1 @@ -1,4 +1,4 @@ -.TH "NPM-UNINSTALL" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-UNINSTALL" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-uninstall\fR - Remove a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-unpublish.1 b/deps/npm/man/man1/npm-unpublish.1 index 7c62b0c1324a..f839376efe0f 100644 --- a/deps/npm/man/man1/npm-unpublish.1 +++ b/deps/npm/man/man1/npm-unpublish.1 @@ -1,4 +1,4 @@ -.TH "NPM-UNPUBLISH" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-UNPUBLISH" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-unpublish\fR - Remove a package from the registry .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-unstar.1 b/deps/npm/man/man1/npm-unstar.1 index fe080180b661..555868c3fb68 100644 --- a/deps/npm/man/man1/npm-unstar.1 +++ b/deps/npm/man/man1/npm-unstar.1 @@ -1,4 +1,4 @@ -.TH "NPM-UNSTAR" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-UNSTAR" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-unstar\fR - Remove an item from your favorite packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-update.1 b/deps/npm/man/man1/npm-update.1 index 5ad8146b0da9..55b4191e801b 100644 --- a/deps/npm/man/man1/npm-update.1 +++ b/deps/npm/man/man1/npm-update.1 @@ -1,4 +1,4 @@ -.TH "NPM-UPDATE" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-UPDATE" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-update\fR - Update packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-version.1 b/deps/npm/man/man1/npm-version.1 index d80b0e8288c0..fd261b5cd42a 100644 --- a/deps/npm/man/man1/npm-version.1 +++ b/deps/npm/man/man1/npm-version.1 @@ -1,4 +1,4 @@ -.TH "NPM-VERSION" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-VERSION" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-version\fR - Bump a package version .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-view.1 b/deps/npm/man/man1/npm-view.1 index 1c965ded8586..a150ce2d979d 100644 --- a/deps/npm/man/man1/npm-view.1 +++ b/deps/npm/man/man1/npm-view.1 @@ -1,4 +1,4 @@ -.TH "NPM-VIEW" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-VIEW" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-view\fR - View registry info .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-whoami.1 b/deps/npm/man/man1/npm-whoami.1 index 7ddb5ca6a84e..920d19146bd3 100644 --- a/deps/npm/man/man1/npm-whoami.1 +++ b/deps/npm/man/man1/npm-whoami.1 @@ -1,4 +1,4 @@ -.TH "NPM-WHOAMI" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM-WHOAMI" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-whoami\fR - Display npm username .SS "Synopsis" diff --git a/deps/npm/man/man1/npm.1 b/deps/npm/man/man1/npm.1 index 58f6815ebadf..9791f5f274f5 100644 --- a/deps/npm/man/man1/npm.1 +++ b/deps/npm/man/man1/npm.1 @@ -1,4 +1,4 @@ -.TH "NPM" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPM" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm\fR - javascript package manager .SS "Synopsis" @@ -12,7 +12,7 @@ npm Note: This command is unaware of workspaces. .SS "Version" .P -11.18.0 +11.19.0 .SS "Description" .P npm is the package manager for the Node JavaScript platform. It puts modules in place so that node can find them, and manages dependency conflicts intelligently. diff --git a/deps/npm/man/man1/npx.1 b/deps/npm/man/man1/npx.1 index c3a4f521f667..3c4afb8aec51 100644 --- a/deps/npm/man/man1/npx.1 +++ b/deps/npm/man/man1/npx.1 @@ -1,4 +1,4 @@ -.TH "NPX" "1" "June 2026" "NPM@11.18.0" "" +.TH "NPX" "1" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpx\fR - Run a command from a local or remote npm package .SS "Synopsis" diff --git a/deps/npm/man/man5/folders.5 b/deps/npm/man/man5/folders.5 index 0202a97198c8..0717f575c47f 100644 --- a/deps/npm/man/man5/folders.5 +++ b/deps/npm/man/man5/folders.5 @@ -1,4 +1,4 @@ -.TH "FOLDERS" "5" "June 2026" "NPM@11.18.0" "" +.TH "FOLDERS" "5" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBFolders\fR - Folder structures used by npm .SS "Description" diff --git a/deps/npm/man/man5/install.5 b/deps/npm/man/man5/install.5 index 8e23936cba2d..200dcc3dfd3a 100644 --- a/deps/npm/man/man5/install.5 +++ b/deps/npm/man/man5/install.5 @@ -1,4 +1,4 @@ -.TH "INSTALL" "5" "June 2026" "NPM@11.18.0" "" +.TH "INSTALL" "5" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBInstall\fR - Download and install node and npm .SS "Description" diff --git a/deps/npm/man/man5/npm-global.5 b/deps/npm/man/man5/npm-global.5 index 0202a97198c8..0717f575c47f 100644 --- a/deps/npm/man/man5/npm-global.5 +++ b/deps/npm/man/man5/npm-global.5 @@ -1,4 +1,4 @@ -.TH "FOLDERS" "5" "June 2026" "NPM@11.18.0" "" +.TH "FOLDERS" "5" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBFolders\fR - Folder structures used by npm .SS "Description" diff --git a/deps/npm/man/man5/npm-json.5 b/deps/npm/man/man5/npm-json.5 index bd9d1bd5b7ee..4d4a9084ac7a 100644 --- a/deps/npm/man/man5/npm-json.5 +++ b/deps/npm/man/man5/npm-json.5 @@ -1,4 +1,4 @@ -.TH "PACKAGE.JSON" "5" "June 2026" "NPM@11.18.0" "" +.TH "PACKAGE.JSON" "5" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBpackage.json\fR - Specifics of npm's package.json handling .SS "Description" diff --git a/deps/npm/man/man5/npm-shrinkwrap-json.5 b/deps/npm/man/man5/npm-shrinkwrap-json.5 index 5f1f80576e9a..9f3d7597d161 100644 --- a/deps/npm/man/man5/npm-shrinkwrap-json.5 +++ b/deps/npm/man/man5/npm-shrinkwrap-json.5 @@ -1,4 +1,4 @@ -.TH "NPM-SHRINKWRAP.JSON" "5" "June 2026" "NPM@11.18.0" "" +.TH "NPM-SHRINKWRAP.JSON" "5" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBnpm-shrinkwrap.json\fR - A publishable lockfile .SS "Description" diff --git a/deps/npm/man/man5/npmrc.5 b/deps/npm/man/man5/npmrc.5 index 14d449b3111b..83578c624089 100644 --- a/deps/npm/man/man5/npmrc.5 +++ b/deps/npm/man/man5/npmrc.5 @@ -1,4 +1,4 @@ -.TH ".NPMRC" "5" "June 2026" "NPM@11.18.0" "" +.TH ".NPMRC" "5" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fB.npmrc\fR - The npm config files .SS "Description" diff --git a/deps/npm/man/man5/package-json.5 b/deps/npm/man/man5/package-json.5 index bd9d1bd5b7ee..4d4a9084ac7a 100644 --- a/deps/npm/man/man5/package-json.5 +++ b/deps/npm/man/man5/package-json.5 @@ -1,4 +1,4 @@ -.TH "PACKAGE.JSON" "5" "June 2026" "NPM@11.18.0" "" +.TH "PACKAGE.JSON" "5" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBpackage.json\fR - Specifics of npm's package.json handling .SS "Description" diff --git a/deps/npm/man/man5/package-lock-json.5 b/deps/npm/man/man5/package-lock-json.5 index 0223c95c68a4..7bc632269f50 100644 --- a/deps/npm/man/man5/package-lock-json.5 +++ b/deps/npm/man/man5/package-lock-json.5 @@ -1,4 +1,4 @@ -.TH "PACKAGE-LOCK.JSON" "5" "June 2026" "NPM@11.18.0" "" +.TH "PACKAGE-LOCK.JSON" "5" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBpackage-lock.json\fR - A manifestation of the manifest .SS "Description" diff --git a/deps/npm/man/man7/config.7 b/deps/npm/man/man7/config.7 index eb4145df9526..b68980abc38f 100644 --- a/deps/npm/man/man7/config.7 +++ b/deps/npm/man/man7/config.7 @@ -1,4 +1,4 @@ -.TH "CONFIG" "7" "June 2026" "NPM@11.18.0" "" +.TH "CONFIG" "7" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBConfig\fR - About npm configuration .SS "Description" diff --git a/deps/npm/man/man7/dependency-selectors.7 b/deps/npm/man/man7/dependency-selectors.7 index 0a9ea65ac2a4..4d242ab6e6c4 100644 --- a/deps/npm/man/man7/dependency-selectors.7 +++ b/deps/npm/man/man7/dependency-selectors.7 @@ -1,4 +1,4 @@ -.TH "SELECTORS" "7" "June 2026" "NPM@11.18.0" "" +.TH "SELECTORS" "7" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBSelectors\fR - Dependency Selector Syntax & Querying .SS "Description" diff --git a/deps/npm/man/man7/developers.7 b/deps/npm/man/man7/developers.7 index 85cc7a5f7940..201aedc081dd 100644 --- a/deps/npm/man/man7/developers.7 +++ b/deps/npm/man/man7/developers.7 @@ -1,4 +1,4 @@ -.TH "DEVELOPERS" "7" "June 2026" "NPM@11.18.0" "" +.TH "DEVELOPERS" "7" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBDevelopers\fR - Developer guide .SS "Description" diff --git a/deps/npm/man/man7/logging.7 b/deps/npm/man/man7/logging.7 index 350348aa714e..be1e6ea6fffa 100644 --- a/deps/npm/man/man7/logging.7 +++ b/deps/npm/man/man7/logging.7 @@ -1,4 +1,4 @@ -.TH "LOGGING" "7" "June 2026" "NPM@11.18.0" "" +.TH "LOGGING" "7" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBLogging\fR - Why, What & How we Log .SS "Description" diff --git a/deps/npm/man/man7/orgs.7 b/deps/npm/man/man7/orgs.7 index bb6cad63c511..43732ecdb5cb 100644 --- a/deps/npm/man/man7/orgs.7 +++ b/deps/npm/man/man7/orgs.7 @@ -1,4 +1,4 @@ -.TH "ORGANIZATIONS" "7" "June 2026" "NPM@11.18.0" "" +.TH "ORGANIZATIONS" "7" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBOrganizations\fR - Working with teams & organizations .SS "Description" diff --git a/deps/npm/man/man7/package-spec.7 b/deps/npm/man/man7/package-spec.7 index 2ec37301d540..d76da151b48e 100644 --- a/deps/npm/man/man7/package-spec.7 +++ b/deps/npm/man/man7/package-spec.7 @@ -1,4 +1,4 @@ -.TH "SPEC" "7" "June 2026" "NPM@11.18.0" "" +.TH "SPEC" "7" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBspec\fR - Package name specifier .SS "Description" diff --git a/deps/npm/man/man7/registry.7 b/deps/npm/man/man7/registry.7 index 49f471fd2cc1..16e295f6ac11 100644 --- a/deps/npm/man/man7/registry.7 +++ b/deps/npm/man/man7/registry.7 @@ -1,4 +1,4 @@ -.TH "REGISTRY" "7" "June 2026" "NPM@11.18.0" "" +.TH "REGISTRY" "7" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBRegistry\fR - The JavaScript Package Registry .SS "Description" diff --git a/deps/npm/man/man7/removal.7 b/deps/npm/man/man7/removal.7 index de875cb8e0c9..1e0a0ddd0cf3 100644 --- a/deps/npm/man/man7/removal.7 +++ b/deps/npm/man/man7/removal.7 @@ -1,4 +1,4 @@ -.TH "REMOVAL" "7" "June 2026" "NPM@11.18.0" "" +.TH "REMOVAL" "7" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBRemoval\fR - Cleaning the slate .SS "Synopsis" diff --git a/deps/npm/man/man7/scope.7 b/deps/npm/man/man7/scope.7 index 793031ca0110..6bb685d20984 100644 --- a/deps/npm/man/man7/scope.7 +++ b/deps/npm/man/man7/scope.7 @@ -1,4 +1,4 @@ -.TH "SCOPE" "7" "June 2026" "NPM@11.18.0" "" +.TH "SCOPE" "7" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBScope\fR - Scoped packages .SS "Description" diff --git a/deps/npm/man/man7/scripts.7 b/deps/npm/man/man7/scripts.7 index 368e0fd18930..797e70b65138 100644 --- a/deps/npm/man/man7/scripts.7 +++ b/deps/npm/man/man7/scripts.7 @@ -1,4 +1,4 @@ -.TH "SCRIPTS" "7" "June 2026" "NPM@11.18.0" "" +.TH "SCRIPTS" "7" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBScripts\fR - How npm handles the "scripts" field .SS "Description" diff --git a/deps/npm/man/man7/workspaces.7 b/deps/npm/man/man7/workspaces.7 index 5dfa00e64927..fb9807cdfe4c 100644 --- a/deps/npm/man/man7/workspaces.7 +++ b/deps/npm/man/man7/workspaces.7 @@ -1,4 +1,4 @@ -.TH "WORKSPACES" "7" "June 2026" "NPM@11.18.0" "" +.TH "WORKSPACES" "7" "July 2026" "NPM@11.19.0" "" .SH "NAME" \fBWorkspaces\fR - Working with workspaces .SS "Description" diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js index 63157b9a550d..2f8ed83e1153 100644 --- a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js +++ b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js @@ -1505,6 +1505,12 @@ This is a one-time fix-up, please be patient... continue } + // The recursion above may replace node with a compatible peer, detaching it. + // A detached node has no real conflict to report, so stop instead of crashing on it. + if (!node.parent) { + break + } + // problem this.#failPeerConflict(edge, parentEdge) } diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/can-place-dep.js b/deps/npm/node_modules/@npmcli/arborist/lib/can-place-dep.js index 1a3ccff66922..1069fee8d65a 100644 --- a/deps/npm/node_modules/@npmcli/arborist/lib/can-place-dep.js +++ b/deps/npm/node_modules/@npmcli/arborist/lib/can-place-dep.js @@ -21,6 +21,8 @@ // - if satisfying and preferDedupe? KEEP // - else: REPLACE // - if there is a newer version present, and preferDedupe, REPLACE +// - if there is a newer vulnerable version present during an audit fix, +// and the candidate is older and not vulnerable, REPLACE // - if the version present satisfies the edge, KEEP // - else: CONFLICT // - if the node is not in conflict, check each of its peers: @@ -62,6 +64,7 @@ class CanPlaceDep { parent = null, peerPath = [], explicitRequest = false, + auditReport = null, } = options debug(() => { @@ -92,6 +95,7 @@ class CanPlaceDep { this.target = target this.edge = edge this.explicitRequest = explicitRequest + this.auditReport = auditReport // preventing cycles when we check peer sets this.peerPath = peerPath @@ -170,7 +174,15 @@ class CanPlaceDep { const { version: curVer } = current const { version: newVer } = dep - const tryReplace = curVer && newVer && semver.gte(newVer, curVer) + // Audit fix may select an older safe version within the declared range. + // This only makes the candidate eligible; canReplace() and peer checks + // below still enforce placement compatibility. + const auditFixDowngrade = curVer && newVer && + semver.lt(newVer, curVer) && + this.auditReport?.isVulnerable(current) && + !this.auditReport.isVulnerable(dep) + const tryReplace = curVer && newVer && + (semver.gte(newVer, curVer) || auditFixDowngrade) if (tryReplace && dep.canReplace(current)) { // It's extremely rare that a replaceable node would be a conflict, if // the current one wasn't a conflict, but it is theoretically possible @@ -381,6 +393,7 @@ class CanPlaceDep { parent: this, edge: peerEdge, peerPath, + auditReport: this.auditReport, // always place peers in preferDedupe mode preferDedupe: true, }) diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/place-dep.js b/deps/npm/node_modules/@npmcli/arborist/lib/place-dep.js index c7b3e10d408d..bf21f01464d2 100644 --- a/deps/npm/node_modules/@npmcli/arborist/lib/place-dep.js +++ b/deps/npm/node_modules/@npmcli/arborist/lib/place-dep.js @@ -96,6 +96,7 @@ class PlaceDep { target, preferDedupe: this.preferDedupe, explicitRequest: this.explicitRequest, + auditReport: this.auditReport, }) this.checks.set(target, cpd) diff --git a/deps/npm/node_modules/@npmcli/arborist/package.json b/deps/npm/node_modules/@npmcli/arborist/package.json index 11c142a0092a..1b71dfa4f124 100644 --- a/deps/npm/node_modules/@npmcli/arborist/package.json +++ b/deps/npm/node_modules/@npmcli/arborist/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/arborist", - "version": "9.9.0", + "version": "9.9.1", "description": "Manage node_modules trees", "dependencies": { "@gar/promise-retry": "^1.0.0", diff --git a/deps/npm/node_modules/libnpmdiff/package.json b/deps/npm/node_modules/libnpmdiff/package.json index 31aadc1a7718..8ce3729c325a 100644 --- a/deps/npm/node_modules/libnpmdiff/package.json +++ b/deps/npm/node_modules/libnpmdiff/package.json @@ -1,6 +1,6 @@ { "name": "libnpmdiff", - "version": "8.1.11", + "version": "8.1.12", "description": "The registry diff", "repository": { "type": "git", @@ -47,7 +47,7 @@ "tap": "^16.3.8" }, "dependencies": { - "@npmcli/arborist": "^9.9.0", + "@npmcli/arborist": "^9.9.1", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", diff --git a/deps/npm/node_modules/libnpmexec/package.json b/deps/npm/node_modules/libnpmexec/package.json index 097673618ae8..c76c285e56a4 100644 --- a/deps/npm/node_modules/libnpmexec/package.json +++ b/deps/npm/node_modules/libnpmexec/package.json @@ -1,6 +1,6 @@ { "name": "libnpmexec", - "version": "10.3.1", + "version": "10.3.2", "files": [ "bin/", "lib/" @@ -61,7 +61,7 @@ }, "dependencies": { "@gar/promise-retry": "^1.0.0", - "@npmcli/arborist": "^9.9.0", + "@npmcli/arborist": "^9.9.1", "@npmcli/package-json": "^7.0.0", "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", diff --git a/deps/npm/node_modules/libnpmfund/package.json b/deps/npm/node_modules/libnpmfund/package.json index a6dc15e4b727..88852421c70f 100644 --- a/deps/npm/node_modules/libnpmfund/package.json +++ b/deps/npm/node_modules/libnpmfund/package.json @@ -1,6 +1,6 @@ { "name": "libnpmfund", - "version": "7.0.25", + "version": "7.0.26", "main": "lib/index.js", "files": [ "bin/", @@ -46,7 +46,7 @@ "tap": "^16.3.8" }, "dependencies": { - "@npmcli/arborist": "^9.9.0" + "@npmcli/arborist": "^9.9.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" diff --git a/deps/npm/node_modules/libnpmpack/package.json b/deps/npm/node_modules/libnpmpack/package.json index 92aa96af6408..5741c8522c19 100644 --- a/deps/npm/node_modules/libnpmpack/package.json +++ b/deps/npm/node_modules/libnpmpack/package.json @@ -1,6 +1,6 @@ { "name": "libnpmpack", - "version": "9.1.11", + "version": "9.1.12", "description": "Programmatic API for the bits behind npm pack", "author": "GitHub Inc.", "main": "lib/index.js", @@ -37,7 +37,7 @@ "bugs": "https://github.com/npm/libnpmpack/issues", "homepage": "https://npmjs.com/package/libnpmpack", "dependencies": { - "@npmcli/arborist": "^9.9.0", + "@npmcli/arborist": "^9.9.1", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" diff --git a/deps/npm/package.json b/deps/npm/package.json index b2ee14b3db8b..5ce61281a2b0 100644 --- a/deps/npm/package.json +++ b/deps/npm/package.json @@ -1,5 +1,5 @@ { - "version": "11.18.0", + "version": "11.19.0", "name": "npm", "description": "a package manager for JavaScript", "workspaces": [ @@ -52,7 +52,7 @@ }, "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.9.0", + "@npmcli/arborist": "^9.9.1", "@npmcli/config": "^10.12.0", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", @@ -77,11 +77,11 @@ "is-cidr": "^6.0.4", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", - "libnpmdiff": "^8.1.11", - "libnpmexec": "^10.3.1", - "libnpmfund": "^7.0.25", + "libnpmdiff": "^8.1.12", + "libnpmexec": "^10.3.2", + "libnpmfund": "^7.0.26", "libnpmorg": "^8.0.1", - "libnpmpack": "^9.1.11", + "libnpmpack": "^9.1.12", "libnpmpublish": "^11.2.0", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", diff --git a/deps/npm/test/lib/commands/approve-scripts.js b/deps/npm/test/lib/commands/approve-scripts.js index 4b2e7e5a73e1..1f29cb898949 100644 --- a/deps/npm/test/lib/commands/approve-scripts.js +++ b/deps/npm/test/lib/commands/approve-scripts.js @@ -627,7 +627,7 @@ t.test('forbidden semver range in package.json#allowScripts is dropped with a wa }) await mock.npm.exec('approve-scripts', []) - const warnings = mock.logs.warn.byTitle('allow-scripts') + const warnings = mock.logs.warn.byTitle('install-scripts') t.ok( warnings.some(m => /semver ranges/.test(m) && /canvas@\^0\.33\.0/.test(m)), 'resolver emits warning about forbidden range' diff --git a/deps/npm/test/lib/commands/owner.js b/deps/npm/test/lib/commands/owner.js index e8b890a753eb..baaf960b9037 100644 --- a/deps/npm/test/lib/commands/owner.js +++ b/deps/npm/test/lib/commands/owner.js @@ -189,6 +189,58 @@ t.test('owner add ', async t => { t.equal(joinedOutput(), `+ ${username} (${packageName})`) }) +t.test('owner add resolves user from package scoped registry', async t => { + const scopedRegistryUrl = 'https://scoped.registry.npmjs.org' + const scopedAuth = '//scoped.registry.npmjs.org/:_authToken' + const { npm, joinedOutput } = await loadMockNpm(t, { + config: { + ...auth, + '@npmcli:registry': scopedRegistryUrl, + [scopedAuth]: 'scoped-auth-token', + }, + }) + const username = 'requested-user' + const globalRegistry = new MockRegistry({ + tap: t, + registry: npm.config.get('registry'), + authorization: 'test-auth-token', + }) + const scopedRegistry = new MockRegistry({ + tap: t, + registry: scopedRegistryUrl, + authorization: 'scoped-auth-token', + }) + + // If `owner add` mistakenly asked the global registry for user data + // instead of the package's scoped registry, this substituted user would + // be added as the owner instead of the one requested. + globalRegistry.nock = globalRegistry.nock + .get(`/-/user/org.couchdb.user:${encodeURIComponent(username)}`) + .optionally() + .reply(200, { name: 'substituted-user', email: 'substituted@example.com' }) + scopedRegistry.couchuser({ username, body: { name: username, email: 'requested@example.com' } }) + + const manifest = scopedRegistry.manifest({ + name: packageName, + packuments: [{ maintainers, version: '1.0.0' }], + }) + await scopedRegistry.package({ manifest }) + scopedRegistry.nock.put(`/${spec.escapedName}/-rev/${manifest._rev}`, body => { + t.match(body, { + _id: manifest._id, + _rev: manifest._rev, + maintainers: [ + ...manifest.maintainers, + { name: username, email: 'requested@example.com' }, + ], + }) + return true + }).reply(200, {}) + + await npm.exec('owner', ['add', username, packageName]) + t.equal(joinedOutput(), `+ ${username} (${packageName})`) +}) + t.test('owner add cwd package', async t => { const { npm, joinedOutput } = await loadMockNpm(t, { prefixDir: { @@ -364,6 +416,57 @@ t.test('owner rm ', async t => { t.equal(joinedOutput(), `- ${username} (${packageName})`) }) +t.test('owner rm resolves user from package scoped registry', async t => { + const scopedRegistryUrl = 'https://scoped.registry.npmjs.org' + const scopedAuth = '//scoped.registry.npmjs.org/:_authToken' + const { npm, joinedOutput, logs } = await loadMockNpm(t, { + config: { + ...auth, + '@npmcli:registry': scopedRegistryUrl, + [scopedAuth]: 'scoped-auth-token', + }, + }) + const username = 'requested-user' + const globalRegistry = new MockRegistry({ + tap: t, + registry: npm.config.get('registry'), + authorization: 'test-auth-token', + }) + const scopedRegistry = new MockRegistry({ + tap: t, + registry: scopedRegistryUrl, + authorization: 'scoped-auth-token', + }) + + // If `owner rm` mistakenly asked the global registry for user data instead + // of the package's scoped registry, this substituted user would be + // resolved and treated as an existing owner rather than the one requested. + globalRegistry.nock = globalRegistry.nock + .get(`/-/user/org.couchdb.user:${encodeURIComponent(username)}`) + .optionally() + .reply(200, maintainers[0]) + scopedRegistry.couchuser({ username, body: { name: username, email: 'requested@example.com' } }) + + const manifest = scopedRegistry.manifest({ + name: packageName, + packuments: [{ maintainers, version: '1.0.0' }], + }) + await scopedRegistry.package({ manifest }) + let update + scopedRegistry.nock + .put(`/${spec.escapedName}/-rev/${manifest._rev}`, body => { + update = body + return true + }) + .optionally() + .reply(200, {}) + + await npm.exec('owner', ['rm', username, packageName]) + t.equal(update, undefined, 'does not remove the substituted user') + t.equal(joinedOutput(), '', 'does not report a removal') + t.match(logs.info.byTitle('owner rm'), [`Not a package owner: ${username}`]) +}) + t.test('owner rm not a current owner', async t => { const { npm, logs } = await loadMockNpm(t, { config: { ...auth }, diff --git a/deps/npm/test/lib/commands/pack.js b/deps/npm/test/lib/commands/pack.js index 7e98b12704df..9805427de4f6 100644 --- a/deps/npm/test/lib/commands/pack.js +++ b/deps/npm/test/lib/commands/pack.js @@ -1,6 +1,7 @@ const t = require('tap') const { load: loadMockNpm } = require('../../fixtures/mock-npm') const { cleanZlib } = require('../../fixtures/clean-snapshot') +const MockRegistry = require('@npmcli/mock-registry') const path = require('node:path') const fs = require('node:fs') @@ -167,6 +168,61 @@ t.test('foreground-scripts can still be set to false', async t => { t.throws(() => fs.statSync(path.resolve(npm.prefix, filename))) }) +t.test('min-release-age-exclude applies to registry packages', async t => { + const name = '@myscope/some-package' + const version = '1.2.3' + const { npm, outputs } = await loadMockNpm(t, { + prefixDir: { + package: { + 'package.json': JSON.stringify({ name, version }), + }, + }, + config: { + 'min-release-age': 7, + 'min-release-age-exclude': ['@myscope/*'], + }, + }) + const registry = new MockRegistry({ + tap: t, + registry: npm.config.get('registry'), + }) + const manifest = registry.manifest({ name, versions: [version] }) + await registry.package({ + manifest, + times: 2, + tarballs: { [version]: path.join(npm.prefix, 'package') }, + }) + + await npm.exec('pack', [`${name}@${version}`]) + + const filename = 'myscope-some-package-1.2.3.tgz' + t.strictSame(outputs, [filename]) + t.ok(fs.statSync(path.resolve(npm.prefix, filename))) +}) + +t.test('excluded alias name does not bypass min-release-age for its target', async t => { + const target = 'other-package' + const version = '1.2.3' + const { npm } = await loadMockNpm(t, { + config: { + 'min-release-age': 7, + 'min-release-age-exclude': ['@myscope/*'], + }, + }) + const registry = new MockRegistry({ + tap: t, + registry: npm.config.get('registry'), + }) + await registry.package({ + manifest: registry.manifest({ name: target, versions: [version] }), + }) + + await t.rejects( + npm.exec('pack', [`@myscope/alias@npm:${target}@${version}`]), + { code: 'ETARGET' } + ) +}) + t.test('invalid packument', async t => { const { npm, outputs } = await loadMockNpm(t, { prefixDir: { diff --git a/deps/npm/test/lib/utils/reify-output.js b/deps/npm/test/lib/utils/reify-output.js index 0f2be24b0246..ee9201482a75 100644 --- a/deps/npm/test/lib/utils/reify-output.js +++ b/deps/npm/test/lib/utils/reify-output.js @@ -499,7 +499,7 @@ t.test('prints unreviewed install scripts summary', async t => { ] const mock = await mockReifyWithExtras(t, baseReify, { unreviewedScripts }) - const warn = mock.logs.warn.byTitle('allow-scripts').join('\n') + const warn = mock.logs.warn.byTitle('install-scripts').join('\n') t.match(warn, /2 packages have install scripts not yet covered/) t.match(warn, /canvas@2\.11\.0 \(install: node-gyp rebuild\)/) t.match(warn, /sharp@0\.33\.2 \(preinstall: pre; postinstall: post\)/) @@ -531,7 +531,7 @@ t.test('global install suggests --allow-scripts, not approve-scripts', async t = ] const mock = await mockReifyWithExtras(t, baseReify, { unreviewedScripts }, { global: true }) - const warn = mock.logs.warn.byTitle('allow-scripts').join('\n') + const warn = mock.logs.warn.byTitle('install-scripts').join('\n') t.match(warn, /2 packages have install scripts not yet covered/) t.match(warn, /canvas@2\.11\.0 \(install: node-gyp rebuild\)/) t.match(warn, /npm install -g --allow-scripts=canvas,sharp/) @@ -557,7 +557,7 @@ t.test('single unreviewed script uses singular wording', async t => { }], } ) - t.match(mock.logs.warn.byTitle('allow-scripts').join('\n'), /1 package has install scripts/) + t.match(mock.logs.warn.byTitle('install-scripts').join('\n'), /1 package has install scripts/) }) t.test('json output includes unreviewedScripts', async t => { @@ -593,7 +593,7 @@ t.test('unreviewed script with node.name only (no packageName) still renders', a }], }) mock.npm.finish() - t.match(mock.logs.warn.byTitle('allow-scripts').join('\n'), / fallback \(install: cmd\)/) + t.match(mock.logs.warn.byTitle('install-scripts').join('\n'), / fallback \(install: cmd\)/) }) t.test('json output includes node.name when packageName is missing', async t => { diff --git a/deps/npm/test/lib/utils/resolve-allow-scripts.js b/deps/npm/test/lib/utils/resolve-allow-scripts.js index 0d6cdb8c040a..a27d600d98f0 100644 --- a/deps/npm/test/lib/utils/resolve-allow-scripts.js +++ b/deps/npm/test/lib/utils/resolve-allow-scripts.js @@ -119,7 +119,7 @@ t.test('package.json wins over .npmrc setting (RFC layer 2 > layer 3)', async t t.equal(result.source, 'package.json') t.strictSame(result.policy, { sharp: true }) t.match( - mock.logs.warn.byTitle('allow-scripts'), + mock.logs.warn.byTitle('install-scripts'), [/\.npmrc allow-scripts setting is being ignored because package.json/] ) }) @@ -150,7 +150,7 @@ t.test('--allow-scripts CLI flag is accepted via skipProjectConfig (npm exec)', t.equal(result.source, 'cli') t.strictSame(result.policy, { sharp: true }) t.match( - mock.logs.warn.byTitle('allow-scripts'), + mock.logs.warn.byTitle('install-scripts'), [/\.npmrc allow-scripts setting is being ignored because --allow-scripts/] ) }) @@ -224,7 +224,7 @@ t.test('drops package.json entries with forbidden semver ranges and warns', asyn 'also-good': true, 'disjunction@1.0.0 || 2.0.0': true, }) - const warnings = mock.logs.warn.byTitle('allow-scripts') + const warnings = mock.logs.warn.byTitle('install-scripts') t.equal(warnings.filter(m => /semver ranges/.test(m)).length, 3) }) @@ -245,7 +245,7 @@ t.test('drops package.json entries with dist-tag specs and warns', async t => { const result = await resolveAllowScripts(mock.npm) t.equal(result.source, 'package.json') t.strictSame(result.policy, { 'good@1.2.3': true }) - const warnings = mock.logs.warn.byTitle('allow-scripts') + const warnings = mock.logs.warn.byTitle('install-scripts') t.equal(warnings.filter(m => /dist-tag specs/.test(m)).length, 2) }) @@ -260,7 +260,7 @@ t.test('drops .npmrc forbidden ranges (and warns) but keeps valid entries', asyn const result = await resolveAllowScripts(mock.npm) t.equal(result.source, '.npmrc') t.strictSame(result.policy, { canvas: true, 'lodash@4.17.21': true }) - const warnings = mock.logs.warn.byTitle('allow-scripts') + const warnings = mock.logs.warn.byTitle('install-scripts') t.ok(warnings.some(m => /sharp@\^0\.33\.0/.test(m) && /semver ranges/.test(m))) }) @@ -280,7 +280,7 @@ t.test('drops package.json entries that fail npa parse', async t => { const result = await resolveAllowScripts(mock.npm) t.equal(result.source, 'package.json') t.strictSame(result.policy, { good: true }) - t.ok(mock.logs.warn.byTitle('allow-scripts').some(m => /unparseable/.test(m))) + t.ok(mock.logs.warn.byTitle('install-scripts').some(m => /unparseable/.test(m))) }) t.test('returns null when all package.json entries are dropped as invalid', async t => { From 6bab948d3425cec5aac793dbd953746f52be5bfa Mon Sep 17 00:00:00 2001 From: Archkon <180910180+Archkon@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:17:00 +0800 Subject: [PATCH 23/24] buffer: treat detached ArrayBuffers as empty Treat detached ArrayBuffers and Buffer or TypedArray views backed by them as zero-length inputs in buffer.isUtf8() and buffer.isAscii(). Both functions now return true for these inputs, consistent with other empty inputs. Signed-off-by: Archkon <180910180+Archkon@users.noreply.github.com> PR-URL: https://github.com/nodejs/node/pull/64504 Fixes: https://github.com/nodejs/node/issues/64503 Reviewed-By: James M Snell --- doc/api/buffer.md | 14 ++++++- src/node_buffer.cc | 56 +++++----------------------- test/parallel/test-buffer-isascii.js | 23 ++++++++---- test/parallel/test-buffer-isutf8.js | 23 ++++++++---- 4 files changed, 51 insertions(+), 65 deletions(-) diff --git a/doc/api/buffer.md b/doc/api/buffer.md index db80254ef11e..eb65f31ee6f6 100644 --- a/doc/api/buffer.md +++ b/doc/api/buffer.md @@ -5321,6 +5321,11 @@ npx codemod@latest @nodejs/buffer-atob-btoa added: - v19.6.0 - v18.15.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/64504 + description: Detached `ArrayBuffer`s and views backed by them are treated + as empty. --> * `input` {Buffer | ArrayBuffer | TypedArray} The input to validate. @@ -5329,7 +5334,7 @@ added: This function returns `true` if `input` contains only valid ASCII-encoded data, including the case in which `input` is empty. -Throws if the `input` is a detached array buffer. +A detached `ArrayBuffer`, or a `TypedArray` backed by one, is treated as empty. ### `buffer.isUtf8(input)` @@ -5337,6 +5342,11 @@ Throws if the `input` is a detached array buffer. added: - v19.4.0 - v18.14.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/64504 + description: Detached `ArrayBuffer`s and views backed by them are treated + as empty. --> * `input` {Buffer | ArrayBuffer | TypedArray} The input to validate. @@ -5345,7 +5355,7 @@ added: This function returns `true` if `input` contains only valid UTF-8-encoded data, including the case in which `input` is empty. -Throws if the `input` is a detached array buffer. +A detached `ArrayBuffer`, or a `TypedArray` backed by one, is treated as empty. ### `buffer.INSPECT_MAX_BYTES` diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 19c28609660d..29aeedd68f48 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -1360,31 +1360,17 @@ void FastSwap64(Local receiver, static CFunction fast_swap64(CFunction::Make(FastSwap64)); -struct ValidationResult { - bool is_valid; - bool was_detached; -}; - -static ValidationResult ValidateUtf8(Local value) { +static bool ValidateUtf8(Local value) { ArrayBufferViewContents abv(value); - bool was_detached = abv.WasDetached(); - return {!was_detached && simdutf::validate_utf8(abv.data(), abv.length()), - was_detached}; + return abv.length() == 0 || simdutf::validate_utf8(abv.data(), abv.length()); } static void IsUtf8(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsTypedArray() || args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer()); - const ValidationResult result = ValidateUtf8(args[0]); - if (result.was_detached) { - return node::THROW_ERR_INVALID_STATE( - env, "Cannot validate on a detached buffer"); - } - - args.GetReturnValue().Set(result.is_valid); + args.GetReturnValue().Set(ValidateUtf8(args[0])); } static bool FastIsUtf8(Local receiver, @@ -1393,40 +1379,23 @@ static bool FastIsUtf8(Local receiver, FastApiCallbackOptions& options) { TRACK_V8_FAST_API_CALL("buffer.isUtf8"); HandleScope scope(options.isolate); - - const ValidationResult result = ValidateUtf8(value); - if (result.was_detached) { - node::THROW_ERR_INVALID_STATE(options.isolate, - "Cannot validate on a detached buffer"); - return false; - } - return result.is_valid; + return ValidateUtf8(value); } static CFunction fast_is_utf8(CFunction::Make(FastIsUtf8)); -static ValidationResult ValidateAscii(Local value) { +static bool ValidateAscii(Local value) { ArrayBufferViewContents abv(value); - bool was_detached = abv.WasDetached(); - return { - !was_detached && - !simdutf::validate_ascii_with_errors(abv.data(), abv.length()).error, - was_detached}; + return abv.length() == 0 || + !simdutf::validate_ascii_with_errors(abv.data(), abv.length()).error; } static void IsAscii(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsTypedArray() || args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer()); - const ValidationResult result = ValidateAscii(args[0]); - if (result.was_detached) { - return node::THROW_ERR_INVALID_STATE( - env, "Cannot validate on a detached buffer"); - } - - args.GetReturnValue().Set(result.is_valid); + args.GetReturnValue().Set(ValidateAscii(args[0])); } static bool FastIsAscii(Local receiver, @@ -1435,14 +1404,7 @@ static bool FastIsAscii(Local receiver, FastApiCallbackOptions& options) { TRACK_V8_FAST_API_CALL("buffer.isAscii"); HandleScope scope(options.isolate); - - const ValidationResult result = ValidateAscii(value); - if (result.was_detached) { - node::THROW_ERR_INVALID_STATE(options.isolate, - "Cannot validate on a detached buffer"); - return false; - } - return result.is_valid; + return ValidateAscii(value); } static CFunction fast_is_ascii(CFunction::Make(FastIsAscii)); diff --git a/test/parallel/test-buffer-isascii.js b/test/parallel/test-buffer-isascii.js index b9468ca13359..48cc96a17d61 100644 --- a/test/parallel/test-buffer-isascii.js +++ b/test/parallel/test-buffer-isascii.js @@ -30,13 +30,20 @@ assert.strictEqual(isAscii(Buffer.from([])), true); }); { - // Test with detached array buffers - const arrayBuffer = new ArrayBuffer(1024); + // Detached array buffers and views are treated as empty. + const arrayBuffer = new ArrayBuffer(1); + const typedArray = new Uint8Array(arrayBuffer); + typedArray[0] = 0xff; + const inputs = [ + arrayBuffer, + typedArray, + Buffer.from(arrayBuffer), + ]; + for (const input of inputs) { + assert.strictEqual(isAscii(input), false); + } structuredClone(arrayBuffer, { transfer: [arrayBuffer] }); - assert.throws( - () => { isAscii(arrayBuffer); }, - { - code: 'ERR_INVALID_STATE' - } - ); + for (const input of inputs) { + assert.strictEqual(isAscii(input), true); + } } diff --git a/test/parallel/test-buffer-isutf8.js b/test/parallel/test-buffer-isutf8.js index 204db3e6a5fe..151fc0baf4ae 100644 --- a/test/parallel/test-buffer-isutf8.js +++ b/test/parallel/test-buffer-isutf8.js @@ -74,13 +74,20 @@ assert.strictEqual(isUtf8(Buffer.from([])), true); }); { - // Test with detached array buffers - const arrayBuffer = new ArrayBuffer(1024); + // Detached array buffers and views are treated as empty. + const arrayBuffer = new ArrayBuffer(1); + const typedArray = new Uint8Array(arrayBuffer); + typedArray[0] = 0xff; + const inputs = [ + arrayBuffer, + typedArray, + Buffer.from(arrayBuffer), + ]; + for (const input of inputs) { + assert.strictEqual(isUtf8(input), false); + } structuredClone(arrayBuffer, { transfer: [arrayBuffer] }); - assert.throws( - () => { isUtf8(arrayBuffer); }, - { - code: 'ERR_INVALID_STATE' - } - ); + for (const input of inputs) { + assert.strictEqual(isUtf8(input), true); + } } From 579fb17ada1045e43d49f25df4a4af98ae439d85 Mon Sep 17 00:00:00 2001 From: Archkon <180910180+Archkon@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:17:26 +0800 Subject: [PATCH 24/24] tools: sync mk-ca-bundle.pl with curl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Synchronize mk-ca-bundle.pl with curl 1.33. This brings in curl's corrected handling of NSS distrust-after metadata. Refs: https://github.com/curl/curl/blob/0ada20387c31c638cfd7f6b4ae7e5cab5b318caf/scripts/mk-ca-bundle.pl Signed-off-by: Archkon <180910180+Archkon@users.noreply.github.com> PR-URL: https://github.com/nodejs/node/pull/64753 Fixes: https://github.com/nodejs/node/issues/64752 Reviewed-By: Tim Perry Reviewed-By: James M Snell Reviewed-By: René --- src/node_root_certs.h | 33 +++ tools/mk-ca-bundle.pl | 529 ++++++++++++++++++++++++++---------------- 2 files changed, 358 insertions(+), 204 deletions(-) diff --git a/src/node_root_certs.h b/src/node_root_certs.h index 48d2fc5cb7d1..517dc8814c6d 100644 --- a/src/node_root_certs.h +++ b/src/node_root_certs.h @@ -118,6 +118,39 @@ "WD9f\n" "-----END CERTIFICATE-----", +/* Izenpe.com */ +"-----BEGIN CERTIFICATE-----\n" +"MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYD\n" +"VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcN\n" +"MDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwL\n" +"SVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4IC\n" +"DwAwggIKAoICAQDJ03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5Tz\n" +"cqQsRNiekpsUOqHnJJAKClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpz\n" +"bm3benhB6QiIEn6HLmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJ\n" +"GjMxCrFXuaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD\n" +"yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8\n" +"hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG7\n" +"0t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyNBjNaooXlkDWgYlwWTvDjovoD\n" +"GrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+0rnq49qlw0dpEuDb8PYZi+17cNcC\n" +"1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQD\n" +"fo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNV\n" +"HREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4g\n" +"LSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB\n" +"BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAxMCBWaXRv\n" +"cmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE\n" +"FB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l\n" +"Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9\n" +"fbgakEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJO\n" +"ubv5vr8qhT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m\n" +"5hzkQiCeR7Csg1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Py\n" +"e6kfLqCTVyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk\n" +"LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqt\n" +"ujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZ\n" +"pR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6i\n" +"SNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE4\n" +"1V4tC5h9Pmzb/CaIxw==\n" +"-----END CERTIFICATE-----", + /* Go Daddy Root Certificate Authority - G2 */ "-----BEGIN CERTIFICATE-----\n" "MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNV\n" diff --git a/tools/mk-ca-bundle.pl b/tools/mk-ca-bundle.pl index 4057c808bc60..6763ac52bf20 100755 --- a/tools/mk-ca-bundle.pl +++ b/tools/mk-ca-bundle.pl @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/env perl # *************************************************************************** # * _ _ ____ _ # * Project ___| | | | _ \| | @@ -6,11 +6,11 @@ # * | (__| |_| | _ <| |___ # * \___|\___/|_| \_\_____| # * -# * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. +# * Copyright (C) Daniel Stenberg, , et al. # * # * This software is licensed as described in the file COPYING, which # * you should have received as part of this distribution. The terms -# * are also available at http://curl.haxx.se/docs/copyright.html. +# * are also available at https://curl.se/docs/copyright.html. # * # * You may opt to use, copy, modify, merge, publish, distribute and/or sell # * copies of the Software, and permit persons to whom the Software is @@ -19,6 +19,8 @@ # * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY # * KIND, either express or implied. # * +# * SPDX-License-Identifier: curl +# * # *************************************************************************** # This Perl script creates a fresh ca-bundle.crt file for use with libcurl. # It downloads certdata.txt from Mozilla's source tree (see URL below), @@ -34,156 +36,165 @@ use Getopt::Std; use MIME::Base64; use strict; -use vars qw($opt_h $opt_i $opt_l $opt_p $opt_q $opt_s $opt_t $opt_v $opt_w); +use warnings; +use vars qw($opt_h $opt_i $opt_l $opt_m $opt_p $opt_q $opt_s $opt_t $opt_v $opt_w); use List::Util; use Text::Wrap; # If the OpenSSL commandline is not in search path you can configure it here! my $openssl = 'openssl'; -my $version = '1.25'; +my $version = '1.33'; $opt_w = 72; # default base64 encoded lines length -# default cert types to include in the output (default is to include CAs which may issue SSL server certs) +# default cert types to include in the output (default is to include CAs which +# may issue SSL server certs) my $default_mozilla_trust_purposes = "SERVER_AUTH"; my $default_mozilla_trust_levels = "TRUSTED_DELEGATOR"; $opt_p = $default_mozilla_trust_purposes . ":" . $default_mozilla_trust_levels; my @valid_mozilla_trust_purposes = ( - "DIGITAL_SIGNATURE", - "NON_REPUDIATION", - "KEY_ENCIPHERMENT", - "DATA_ENCIPHERMENT", - "KEY_AGREEMENT", - "KEY_CERT_SIGN", - "CRL_SIGN", - "SERVER_AUTH", - "CLIENT_AUTH", - "CODE_SIGNING", - "EMAIL_PROTECTION", - "IPSEC_END_SYSTEM", - "IPSEC_TUNNEL", - "IPSEC_USER", - "TIME_STAMPING", - "STEP_UP_APPROVED" + "DIGITAL_SIGNATURE", + "NON_REPUDIATION", + "KEY_ENCIPHERMENT", + "DATA_ENCIPHERMENT", + "KEY_AGREEMENT", + "KEY_CERT_SIGN", + "CRL_SIGN", + "SERVER_AUTH", + "CLIENT_AUTH", + "CODE_SIGNING", + "EMAIL_PROTECTION", + "IPSEC_END_SYSTEM", + "IPSEC_TUNNEL", + "IPSEC_USER", + "TIME_STAMPING", + "STEP_UP_APPROVED" ); my @valid_mozilla_trust_levels = ( - "TRUSTED_DELEGATOR", # CAs - "NOT_TRUSTED", # Don't trust these certs. - "MUST_VERIFY_TRUST", # This explicitly tells us that it ISN'T a CA but is otherwise ok. In other words, this should tell the app to ignore any other sources that claim this is a CA. - "TRUSTED" # This cert is trusted, but only for itself and not for delegates (i.e. it is not a CA). + "TRUSTED_DELEGATOR", # CAs + "NOT_TRUSTED", # Do not trust these certs. + "MUST_VERIFY_TRUST", # This explicitly tells us that it IS NOT a CA but is + # otherwise ok. In other words, this should tell the + # app to ignore any other sources that claim this is + # a CA. + "TRUSTED" # This cert is trusted, but only for itself and not + # for delegates (i.e. it is not a CA). ); -my $default_signature_algorithms = $opt_s = "MD5"; +my $default_signature_algorithms = $opt_s = "SHA256"; my @valid_signature_algorithms = ( - "MD5", - "SHA1", - "SHA256", - "SHA384", - "SHA512" + "SHA256", + "SHA384", + "SHA512" ); $0 =~ s@.*(/|\\)@@; $Getopt::Std::STANDARD_HELP_VERSION = 1; -getopts('bd:fhilnp:qs:tuvw:'); - -if ($opt_i) { - print ("=" x 78 . "\n"); - print "Script Version : $version\n"; - print "Perl Version : $]\n"; - print "Operating System Name : $^O\n"; - print "Getopt::Std.pm Version : ${Getopt::Std::VERSION}\n"; - print "MIME::Base64.pm Version : ${MIME::Base64::VERSION}\n"; - print ("=" x 78 . "\n"); +getopts('hilmp:qs:tvw:'); + +if($opt_i) { + print ("=" x 78 . "\n"); + print "Script Version : $version\n"; + print "Perl Version : $]\n"; + print "Operating System Name : $^O\n"; + print "Getopt::Std.pm Version : ${Getopt::Std::VERSION}\n"; + print "MIME::Base64.pm Version : ${MIME::Base64::VERSION}\n"; + print ("=" x 78 . "\n"); } sub HELP_MESSAGE() { - print "Usage:\t${0} [-i] [-l] [-p] [-q] [-s] [-t] [-v] [-w] []\n"; - print "\t-i\tprint version info about used modules\n"; - print "\t-l\tprint license info about certdata.txt\n"; - print wrap("\t","\t\t", "-p\tlist of Mozilla trust purposes and levels for certificates to include in output. Takes the form of a comma separated list of purposes, a colon, and a comma separated list of levels. (default: $default_mozilla_trust_purposes:$default_mozilla_trust_levels)"), "\n"; - print "\t\t Valid purposes are:\n"; - print wrap("\t\t ","\t\t ", join( ", ", "ALL", @valid_mozilla_trust_purposes ) ), "\n"; - print "\t\t Valid levels are:\n"; - print wrap("\t\t ","\t\t ", join( ", ", "ALL", @valid_mozilla_trust_levels ) ), "\n"; - print "\t-q\tbe really quiet (no progress output at all)\n"; - print wrap("\t","\t\t", "-s\tcomma separated list of certificate signatures/hashes to output in plain text mode. (default: $default_signature_algorithms)\n"); - print "\t\t Valid signature algorithms are:\n"; - print wrap("\t\t ","\t\t ", join( ", ", "ALL", @valid_signature_algorithms ) ), "\n"; - print "\t-t\tinclude plain text listing of certificates\n"; - print "\t-v\tbe verbose and print out processed CAs\n"; - print "\t-w \twrap base64 output lines after chars (default: ${opt_w})\n"; - exit; + print "Usage:\t${0} [-i] [-l] [-m] [-p] [-q] [-s] [-t] [-v] [-w] []\n"; + print "\t-i\tprint version info about used modules\n"; + print "\t-l\tprint license info about certdata.txt\n"; + print "\t-m\tinclude meta data in output\n"; + print wrap("\t","\t\t", "-p\tlist of Mozilla trust purposes and levels for certificates to include in output. " . + "Takes the form of a comma separated list of purposes, a colon, and a comma separated list of levels. " . + "(default: $default_mozilla_trust_purposes:$default_mozilla_trust_levels)"), "\n"; + print "\t\t Valid purposes are:\n"; + print wrap("\t\t ","\t\t ", join(", ", "ALL", @valid_mozilla_trust_purposes)), "\n"; + print "\t\t Valid levels are:\n"; + print wrap("\t\t ","\t\t ", join(", ", "ALL", @valid_mozilla_trust_levels)), "\n"; + print "\t-q\tbe really quiet (no progress output at all)\n"; + print wrap("\t","\t\t", "-s\tcomma separated list of certificate signatures/hashes to output in plain text mode. (default: $default_signature_algorithms)\n"); + print "\t\t Valid signature algorithms are:\n"; + print wrap("\t\t ","\t\t ", join(", ", "ALL", @valid_signature_algorithms)), "\n"; + print "\t-t\tinclude plain text listing of certificates\n"; + print "\t-v\tbe verbose and print out processed CAs\n"; + print "\t-w \twrap base64 output lines after chars (default: ${opt_w})\n"; + exit; } sub VERSION_MESSAGE() { - print "${0} version ${version} running Perl ${]} on ${^O}\n"; + print "${0} version ${version} running Perl ${]} on ${^O}\n"; } -HELP_MESSAGE() if ($opt_h); +HELP_MESSAGE() if($opt_h); sub report($@) { - my $output = shift; + my $output = shift; - print STDERR $output . "\n" unless $opt_q; + print STDERR $output . "\n" unless $opt_q; } sub is_in_list($@) { - my $target = shift; + my $target = shift; - return defined(List::Util::first { $target eq $_ } @_); + return defined(List::Util::first { $target eq $_ } @_); } -# Parses $param_string as a case insensitive comma separated list with optional whitespace -# validates that only allowed parameters are supplied +# Parses $param_string as a case insensitive comma separated list with optional +# whitespace validates that only allowed parameters are supplied sub parse_csv_param($$@) { - my $description = shift; - my $param_string = shift; - my @valid_values = @_; - - my @values = map { - s/^\s+//; # strip leading spaces - s/\s+$//; # strip trailing spaces - uc $_ # return the modified string as upper case - } split( ',', $param_string ); - - # Find all values which are not in the list of valid values or "ALL" - my @invalid = grep { !is_in_list($_,"ALL",@valid_values) } @values; - - if ( scalar(@invalid) > 0 ) { - # Tell the user which parameters were invalid and print the standard help message which will exit - print "Error: Invalid ", $description, scalar(@invalid) == 1 ? ": " : "s: ", join( ", ", map { "\"$_\"" } @invalid ), "\n"; - HELP_MESSAGE(); - } + my $description = shift; + my $param_string = shift; + my @valid_values = @_; + + my @values = map { + s/^\s+//; # strip leading spaces + s/\s+$//; # strip trailing spaces + uc $_ # return the modified string as upper case + } split(',', $param_string); + + # Find all values which are not in the list of valid values or "ALL" + my @invalid = grep { !is_in_list($_, "ALL", @valid_values) } @values; + + if(scalar(@invalid) > 0) { + # Tell the user which parameters were invalid and print the standard help + # message which also exits + print "Error: Invalid ", $description, scalar(@invalid) == 1 ? ": " : "s: ", join(", ", map { "\"$_\"" } @invalid), "\n"; + HELP_MESSAGE(); + } - @values = @valid_values if ( is_in_list("ALL",@values) ); + @values = @valid_values if(is_in_list("ALL", @values)); - return @values; + return @values; } -if ( $opt_p !~ m/:/ ) { - print "Error: Mozilla trust identifier list must include both purposes and levels\n"; - HELP_MESSAGE(); +if($opt_p !~ m/:/) { + print "Error: Mozilla trust identifier list must include both purposes and levels\n"; + HELP_MESSAGE(); } -(my $included_mozilla_trust_purposes_string, my $included_mozilla_trust_levels_string) = split( ':', $opt_p ); -my @included_mozilla_trust_purposes = parse_csv_param( "trust purpose", $included_mozilla_trust_purposes_string, @valid_mozilla_trust_purposes ); -my @included_mozilla_trust_levels = parse_csv_param( "trust level", $included_mozilla_trust_levels_string, @valid_mozilla_trust_levels ); +(my $included_mozilla_trust_purposes_string, my $included_mozilla_trust_levels_string) = split(':', $opt_p); +my @included_mozilla_trust_purposes = parse_csv_param("trust purpose", $included_mozilla_trust_purposes_string, @valid_mozilla_trust_purposes); +my @included_mozilla_trust_levels = parse_csv_param("trust level", $included_mozilla_trust_levels_string, @valid_mozilla_trust_levels); -my @included_signature_algorithms = parse_csv_param( "signature algorithm", $opt_s, @valid_signature_algorithms ); +my @included_signature_algorithms = parse_csv_param("signature algorithm", $opt_s, @valid_signature_algorithms); sub should_output_cert(%) { - my %trust_purposes_by_level = @_; + my %trust_purposes_by_level = @_; - foreach my $level (@included_mozilla_trust_levels) { - # for each level we want to output, see if any of our desired purposes are included - return 1 if ( defined( List::Util::first { is_in_list( $_, @included_mozilla_trust_purposes ) } @{$trust_purposes_by_level{$level}} ) ); - } + foreach my $level (@included_mozilla_trust_levels) { + # for each level we want to output, see if any of our desired purposes are + # included + return 1 if(defined(List::Util::first { is_in_list($_, @included_mozilla_trust_purposes) } @{$trust_purposes_by_level{$level}})); + } - return 0; + return 0; } my $crt = $ARGV[0] || dirname(__FILE__) . '/../src/node_root_certs.h'; @@ -191,132 +202,242 @@ (%) my $stdout = $crt eq '-'; -if( $stdout ) { - open(CRT, '> -') or die "Couldn't open STDOUT: $!\n"; +if($stdout) { + open(CRT, '> -') or die "Could not open STDOUT: $!\n"; } else { - open(CRT,">$crt.~") or die "Couldn't open $crt.~: $!\n"; + open(CRT, ">", "$crt.~") or die "Could not open $crt.~: $!\n"; } my $caname; my $certnum = 0; my $skipnum = 0; my $start_of_cert = 0; - -open(TXT,"$txt") or die "Couldn't open $txt: $!\n"; +my $main_block = 0; +my $main_block_name; +my $trust_block = 0; +my $trust_block_name; +my @precert; +my $cka_value; +my $valid = 0; + +open(TXT, $txt) or die "Could not open $txt: $!\n"; print CRT "#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS\n"; -while () { - if (/\*\*\*\*\* BEGIN LICENSE BLOCK \*\*\*\*\*/) { - print CRT; - print if ($opt_l); - while () { - print CRT; - print if ($opt_l); - last if (/\*\*\*\*\* END LICENSE BLOCK \*\*\*\*\*/); +while() { + if(/\*\*\*\*\* BEGIN LICENSE BLOCK \*\*\*\*\*/) { + print CRT; + print if($opt_l); + while() { + print CRT; + print if($opt_l); + last if(/\*\*\*\*\* END LICENSE BLOCK \*\*\*\*\*/); + } + next; } - } - next if /^#|^\s*$/; - chomp; - if (/^CVS_ID\s+\"(.*)\"/) { - print CRT "/* $1 */\n"; - } - - # this is a match for the start of a certificate - if (/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/) { - $start_of_cert = 1 - } - if ($start_of_cert && /^CKA_LABEL UTF8 \"(.*)\"/) { - $caname = $1; - } - my %trust_purposes_by_level; - if ($start_of_cert && /^CKA_VALUE MULTILINE_OCTAL/) { - my $data; - while () { - last if (/^END/); - chomp; - my @octets = split(/\\/); - shift @octets; - for (@octets) { - $data .= chr(oct); - } + # The input file format consists of blocks of Mozilla objects. + # The blocks are separated by blank lines but may be related. + elsif(/^\s*$/) { + $main_block = 0; + $trust_block = 0; + next; } - # scan forwards until the trust part - while () { - last if (/^CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST/); - chomp; + # Each certificate has a main block. + elsif(/^# Certificate "(.*)"/) { + (!$main_block && !$trust_block) or die "Unexpected certificate block"; + $main_block = 1; + $main_block_name = $1; + # Reset all other certificate variables. + $trust_block = 0; + $trust_block_name = ""; + $valid = 0; + $start_of_cert = 0; + $caname = ""; + $cka_value = ""; + undef @precert; + next; } - # now scan the trust part to determine how we should trust this cert - while () { - last if (/^#/); - if (/^CKA_TRUST_([A-Z_]+)\s+CK_TRUST\s+CKT_NSS_([A-Z_]+)\s*$/) { - if ( !is_in_list($1,@valid_mozilla_trust_purposes) ) { - report "Warning: Unrecognized trust purpose for cert: $caname. Trust purpose: $1. Trust Level: $2"; - } elsif ( !is_in_list($2,@valid_mozilla_trust_levels) ) { - report "Warning: Unrecognized trust level for cert: $caname. Trust purpose: $1. Trust Level: $2"; - } else { - push @{$trust_purposes_by_level{$2}}, $1; + # Each certificate's main block is followed by a trust block. + elsif(/^# Trust for (?:Certificate )?"(.*)"/) { + (!$main_block && !$trust_block) or die "Unexpected trust block"; + $trust_block = 1; + $trust_block_name = $1; + if($main_block_name ne $trust_block_name) { + die "cert name \"$main_block_name\" != trust name \"$trust_block_name\""; + } + next; + } + # Ignore other blocks. + # + # There is a documentation comment block, a BEGINDATA block, and a bunch of + # blocks starting with "# Explicitly Distrust ". + # + # The latter is for certificates that have already been removed and are not + # included. Not all explicitly distrusted certificates are ignored at this + # point, only those without an actual certificate. + elsif(!$main_block && !$trust_block) { + next; + } + elsif(/^#/) { + # The commented lines in a main block are plaintext metadata that describes + # the certificate. Issuer, Subject, Fingerprint, etc. + if($main_block) { + push @precert, s{^#}{//}r if not /^#$/; + if(/^# Not Valid After : (.*)/) { + my $stamp = $1; + use Time::Piece; + # Not Valid After : Thu Sep 30 14:01:15 2021 + my $t = Time::Piece->strptime($stamp, "%a %b %d %H:%M:%S %Y"); + my $delta = ($t->epoch - time()); # negative means no longer valid + if($delta < 0) { + $skipnum++; + report "Skipping: $main_block_name is not valid anymore" if($opt_v); + $valid = 0; + } + else { + $valid = 1; + } + } } - } + next; + } + elsif(!$valid) { + next; } - if ( !should_output_cert(%trust_purposes_by_level) ) { - $skipnum ++; - } elsif ($caname =~ /TrustCor/) { - $skipnum ++; - } else { - my $encoded = MIME::Base64::encode_base64($data, ''); - $encoded =~ s/(.{1,${opt_w}})/"$1\\n"\n/g; - my $pem = "\"-----BEGIN CERTIFICATE-----\\n\"\n" - . $encoded - . "\"-----END CERTIFICATE-----\",\n"; - print CRT "\n/* $caname */\n"; - - my $maxStringLength = length($caname); - if ($opt_t) { - foreach my $key (keys %trust_purposes_by_level) { - my $string = $key . ": " . join(", ", @{$trust_purposes_by_level{$key}}); - $maxStringLength = List::Util::max( length($string), $maxStringLength ); - print CRT $string . "\n"; + chomp; + + if($main_block) { + if(/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/) { + !$start_of_cert or die "Duplicate CKO_CERTIFICATE object"; + $start_of_cert = 1; + next; + } + elsif(!$start_of_cert) { + next; + } + elsif(/^CKA_LABEL UTF8 \"(.*)\"/) { + ($caname eq "") or die "Duplicate CKA_LABEL attribute"; + $caname = $1; + if($caname ne $main_block_name) { + die "caname \"$caname\" != cert name \"$main_block_name\""; + } + next; } - } - if (!$opt_t) { - print CRT $pem; - } else { - my $pipe = ""; - foreach my $hash (@included_signature_algorithms) { - $pipe = "|$openssl x509 -" . $hash . " -fingerprint -noout -inform PEM"; - if (!$stdout) { - $pipe .= " >> $crt.~"; - close(CRT) or die "Couldn't close $crt.~: $!"; - } - open(TMP, $pipe) or die "Couldn't open openssl pipe: $!"; - print TMP $pem; - close(TMP) or die "Couldn't close openssl pipe: $!"; - if (!$stdout) { - open(CRT, ">>$crt.~") or die "Couldn't open $crt.~: $!"; - } + elsif(/^CKA_VALUE MULTILINE_OCTAL/) { + ($cka_value eq "") or die "Duplicate CKA_VALUE attribute"; + while() { + last if(/^END/); + chomp; + my @octets = split(/\\/); + shift @octets; + for(@octets) { + $cka_value .= chr(oct); + } + } + next; + } + else { + next; + } + } + + if(!$trust_block || !$start_of_cert || $caname eq "" || $cka_value eq "") { + die "Certificate extraction failed"; + } + + my %trust_purposes_by_level; + + if(/^CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST/) { + # now scan the trust part to determine how we should trust this cert + while() { + if(/^\s*$/) { + $trust_block = 0; + last; + } + if(/^CKA_TRUST_([A-Z_]+)\s+CK_TRUST\s+CKT_NSS_([A-Z_]+)\s*$/) { + if(!is_in_list($1, @valid_mozilla_trust_purposes)) { + report "Warning: Unrecognized trust purpose for cert: $caname. Trust purpose: $1. Trust Level: $2"; + } elsif(!is_in_list($2, @valid_mozilla_trust_levels)) { + report "Warning: Unrecognized trust level for cert: $caname. Trust purpose: $1. Trust Level: $2"; + } else { + push @{$trust_purposes_by_level{$2}}, $1; + } + } } - $pipe = "|$openssl x509 -text -inform PEM"; - if (!$stdout) { - $pipe .= " >> $crt.~"; - close(CRT) or die "Couldn't close $crt.~: $!"; + + # Sanity check that an explicitly distrusted certificate only has trust + # purposes with a trust level of NOT_TRUSTED. + # + # Certificate objects that are explicitly distrusted are in a certificate + # block that starts # Certificate "Explicitly Distrust(ed) ", + # where "Explicitly Distrust(ed) " was prepended to the original cert name. + if($caname =~ /distrust/i || + $main_block_name =~ /distrust/i || + $trust_block_name =~ /distrust/i) { + my @levels = keys %trust_purposes_by_level; + if(scalar(@levels) != 1 || $levels[0] ne "NOT_TRUSTED") { + die "\"$caname\" must have all trust purposes at level NOT_TRUSTED."; + } } - open(TMP, $pipe) or die "Couldn't open openssl pipe: $!"; - print TMP $pem; - close(TMP) or die "Couldn't close openssl pipe: $!"; - if (!$stdout) { - open(CRT, ">>$crt.~") or die "Couldn't open $crt.~: $!"; + + if(!should_output_cert(%trust_purposes_by_level)) { + $skipnum ++; + report "Skipping: $caname lacks acceptable trust level" if($opt_v); + } elsif($caname =~ /TrustCor/) { + $skipnum ++; + } else { + my $encoded = MIME::Base64::encode_base64($cka_value, ''); + $encoded =~ s/(.{1,${opt_w}})/"$1\\n"\n/g; + my $pem = "\"-----BEGIN CERTIFICATE-----\\n\"\n" + . $encoded + . "\"-----END CERTIFICATE-----\",\n"; + print CRT "\n/* $caname */\n"; + if($opt_t) { + foreach my $key (sort keys %trust_purposes_by_level) { + my $string = $key . ": " . join(", ", @{$trust_purposes_by_level{$key}}); + print CRT $string . "\n"; + } + } + if($opt_m) { + print CRT for @precert; + } + if(!$opt_t) { + print CRT $pem; + } else { + my $pipe = ""; + foreach my $hash (@included_signature_algorithms) { + $pipe = "|$openssl x509 -" . $hash . " -fingerprint -noout -inform PEM"; + if(!$stdout) { + $pipe .= " >> $crt.~"; + close(CRT) or die "Could not close $crt.~: $!"; + } + open(TMP, $pipe) or die "Could not open openssl pipe: $!"; + print TMP $pem; + close(TMP) or die "Could not close openssl pipe: $!"; + if(!$stdout) { + open(CRT, ">>", "$crt.~") or die "Could not open $crt.~: $!"; + } + } + $pipe = "|$openssl x509 -text -inform PEM"; + if(!$stdout) { + $pipe .= " >> $crt.~"; + close(CRT) or die "Could not close $crt.~: $!"; + } + open(TMP, $pipe) or die "Could not open openssl pipe: $!"; + print TMP $pem; + close(TMP) or die "Could not close openssl pipe: $!"; + if(!$stdout) { + open(CRT, ">>", "$crt.~") or die "Could not open $crt.~: $!"; + } + } + report "Processed: $caname" if($opt_v); + $certnum++; } - } - report "Parsing: $caname" if ($opt_v); - $certnum ++; - $start_of_cert = 0; } - } } print CRT "#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS\n"; -close(TXT) or die "Couldn't close $txt: $!\n"; -close(CRT) or die "Couldn't close $crt.~: $!\n"; -unless( $stdout ) { +close(TXT) or die "Could not close $txt: $!\n"; +close(CRT) or die "Could not close $crt.~: $!\n"; +unless($stdout) { rename "$crt.~", $crt or die "Failed to rename $crt.~ to $crt: $!\n"; } report "Done ($certnum CA certs processed, $skipnum skipped).";