From ab150d77812ff9ac14b64b4b7354d22f8d396bcc Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Sat, 8 Feb 2025 19:51:06 -0500 Subject: [PATCH 01/92] test: remove flakiness on macOS test PR-URL: https://github.com/nodejs/node/pull/56971 Refs: https://github.com/nodejs/node/actions/runs/13220154720/job/36904132463?pr=56970 Reviewed-By: Matteo Collina Reviewed-By: Marco Ippolito --- test/parallel/test-net-write-fully-async-hex-string.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-net-write-fully-async-hex-string.js b/test/parallel/test-net-write-fully-async-hex-string.js index b80b09f3244585..48c54819b3b676 100644 --- a/test/parallel/test-net-write-fully-async-hex-string.js +++ b/test/parallel/test-net-write-fully-async-hex-string.js @@ -21,7 +21,7 @@ const server = net.createServer(common.mustCall(function(conn) { } while (conn.write(data, 'hex')); - globalThis.gc({ type: 'minor' }); + globalThis.gc({ type: 'major' }); // The buffer allocated inside the .write() call should still be alive. } From 0a82d27d28d2008dcec6ba931925ea722502e2a1 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Sun, 9 Feb 2025 14:30:23 -0500 Subject: [PATCH 02/92] test: reduce flakiness on test-net-write-fully-async-buffer PR-URL: https://github.com/nodejs/node/pull/56971 Refs: https://github.com/nodejs/node/actions/runs/13220154720/job/36904132463?pr=56970 Reviewed-By: Matteo Collina Reviewed-By: Marco Ippolito --- test/parallel/test-net-write-fully-async-buffer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-net-write-fully-async-buffer.js b/test/parallel/test-net-write-fully-async-buffer.js index 042dd79cb03127..3a3426ba703845 100644 --- a/test/parallel/test-net-write-fully-async-buffer.js +++ b/test/parallel/test-net-write-fully-async-buffer.js @@ -23,7 +23,7 @@ const server = net.createServer(common.mustCall(function(conn) { } while (conn.write(Buffer.from(data))); - globalThis.gc({ type: 'minor' }); + globalThis.gc({ type: 'major' }); // The buffer allocated above should still be alive. } From d0ee8c0a20f00d3864130e49d01a286f52346373 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 11 Feb 2025 08:28:04 -0800 Subject: [PATCH 03/92] src: improve error handling in process_wrap Replace ToLocalChecked uses. PR-URL: https://github.com/nodejs/node/pull/56977 Reviewed-By: Yagiz Nizipli Reviewed-By: Chengzhong Wu --- src/process_wrap.cc | 158 +++++++++++++++++++++++++++++++------------- 1 file changed, 112 insertions(+), 46 deletions(-) diff --git a/src/process_wrap.cc b/src/process_wrap.cc index 27a294eb384960..98ca6742d4fd45 100644 --- a/src/process_wrap.cc +++ b/src/process_wrap.cc @@ -20,6 +20,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. #include "env-inl.h" +#include "node_errors.h" #include "node_external_reference.h" #include "permission/permission.h" #include "stream_base-inl.h" @@ -40,7 +41,11 @@ using v8::HandleScope; using v8::Int32; using v8::Integer; using v8::Isolate; +using v8::Just; +using v8::JustVoid; using v8::Local; +using v8::Maybe; +using v8::Nothing; using v8::Number; using v8::Object; using v8::String; @@ -96,58 +101,84 @@ class ProcessWrap : public HandleWrap { MarkAsUninitialized(); } - static uv_stream_t* StreamForWrap(Environment* env, Local stdio) { + static Maybe StreamForWrap(Environment* env, + Local stdio) { Local handle_key = env->handle_string(); // This property has always been set by JS land if we are in this code path. - Local handle = - stdio->Get(env->context(), handle_key).ToLocalChecked().As(); + Local val; + if (!stdio->Get(env->context(), handle_key).ToLocal(&val)) { + return Nothing(); + } + Local handle = val.As(); uv_stream_t* stream = LibuvStreamWrap::From(env, handle)->stream(); CHECK_NOT_NULL(stream); - return stream; + return Just(stream); } - static void ParseStdioOptions(Environment* env, - Local js_options, - uv_process_options_t* options) { + static Maybe ParseStdioOptions(Environment* env, + Local js_options, + uv_process_options_t* options) { Local context = env->context(); Local stdio_key = env->stdio_string(); - Local stdios = - js_options->Get(context, stdio_key).ToLocalChecked().As(); + Local stdios_val; + if (!js_options->Get(context, stdio_key).ToLocal(&stdios_val)) { + return Nothing(); + } + if (!stdios_val->IsArray()) { + THROW_ERR_INVALID_ARG_TYPE(env, "options.stdio must be an array"); + return Nothing(); + } + Local stdios = stdios_val.As(); uint32_t len = stdios->Length(); options->stdio = new uv_stdio_container_t[len]; options->stdio_count = len; for (uint32_t i = 0; i < len; i++) { - Local stdio = - stdios->Get(context, i).ToLocalChecked().As(); - Local type = - stdio->Get(context, env->type_string()).ToLocalChecked(); + Local val; + if (!stdios->Get(context, i).ToLocal(&val)) { + return Nothing(); + } + Local stdio = val.As(); + Local type; + if (!stdio->Get(context, env->type_string()).ToLocal(&type)) { + return Nothing(); + } if (type->StrictEquals(env->ignore_string())) { options->stdio[i].flags = UV_IGNORE; } else if (type->StrictEquals(env->pipe_string())) { options->stdio[i].flags = static_cast( UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE); - options->stdio[i].data.stream = StreamForWrap(env, stdio); + if (!StreamForWrap(env, stdio).To(&options->stdio[i].data.stream)) { + return Nothing(); + } } else if (type->StrictEquals(env->overlapped_string())) { options->stdio[i].flags = static_cast( UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE | UV_OVERLAPPED_PIPE); - options->stdio[i].data.stream = StreamForWrap(env, stdio); + if (!StreamForWrap(env, stdio).To(&options->stdio[i].data.stream)) { + return Nothing(); + } } else if (type->StrictEquals(env->wrap_string())) { options->stdio[i].flags = UV_INHERIT_STREAM; - options->stdio[i].data.stream = StreamForWrap(env, stdio); + if (!StreamForWrap(env, stdio).To(&options->stdio[i].data.stream)) { + return Nothing(); + } } else { Local fd_key = env->fd_string(); - Local fd_value = stdio->Get(context, fd_key).ToLocalChecked(); + Local fd_value; + if (!stdio->Get(context, fd_key).ToLocal(&fd_value)) { + return Nothing(); + } CHECK(fd_value->IsNumber()); int fd = static_cast(fd_value.As()->Value()); options->stdio[i].flags = UV_INHERIT_FD; options->stdio[i].data.fd = fd; } } + return JustVoid(); } static void Spawn(const FunctionCallbackInfo& args) { @@ -159,8 +190,11 @@ class ProcessWrap : public HandleWrap { env, permission::PermissionScope::kChildProcess, ""); int err = 0; - Local js_options = - args[0]->ToObject(env->context()).ToLocalChecked(); + if (!args[0]->IsObject()) { + return THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object"); + } + + Local js_options = args[0].As(); uv_process_options_t options; memset(&options, 0, sizeof(uv_process_options_t)); @@ -168,8 +202,10 @@ class ProcessWrap : public HandleWrap { options.exit_cb = OnExit; // options.uid - Local uid_v = - js_options->Get(context, env->uid_string()).ToLocalChecked(); + Local uid_v; + if (!js_options->Get(context, env->uid_string()).ToLocal(&uid_v)) { + return; + } if (!uid_v->IsUndefined() && !uid_v->IsNull()) { CHECK(uid_v->IsInt32()); const int32_t uid = uid_v.As()->Value(); @@ -178,8 +214,10 @@ class ProcessWrap : public HandleWrap { } // options.gid - Local gid_v = - js_options->Get(context, env->gid_string()).ToLocalChecked(); + Local gid_v; + if (!js_options->Get(context, env->gid_string()).ToLocal(&gid_v)) { + return; + } if (!gid_v->IsUndefined() && !gid_v->IsNull()) { CHECK(gid_v->IsInt32()); const int32_t gid = gid_v.As()->Value(); @@ -190,8 +228,10 @@ class ProcessWrap : public HandleWrap { // TODO(bnoordhuis) is this possible to do without mallocing ? // options.file - Local file_v = - js_options->Get(context, env->file_string()).ToLocalChecked(); + Local file_v; + if (!js_options->Get(context, env->file_string()).ToLocal(&file_v)) { + return; + } CHECK(file_v->IsString()); node::Utf8Value file(env->isolate(), file_v); options.file = *file; @@ -206,8 +246,10 @@ class ProcessWrap : public HandleWrap { #endif // options.args - Local argv_v = - js_options->Get(context, env->args_string()).ToLocalChecked(); + Local argv_v; + if (!js_options->Get(context, env->args_string()).ToLocal(&argv_v)) { + return; + } if (!argv_v.IsEmpty() && argv_v->IsArray()) { Local js_argv = argv_v.As(); int argc = js_argv->Length(); @@ -216,8 +258,11 @@ class ProcessWrap : public HandleWrap { // Heap allocate to detect errors. +1 is for nullptr. options.args = new char*[argc + 1]; for (int i = 0; i < argc; i++) { - node::Utf8Value arg(env->isolate(), - js_argv->Get(context, i).ToLocalChecked()); + Local val; + if (!js_argv->Get(context, i).ToLocal(&val)) { + return; + } + node::Utf8Value arg(env->isolate(), val); options.args[i] = strdup(*arg); CHECK_NOT_NULL(options.args[i]); } @@ -225,8 +270,10 @@ class ProcessWrap : public HandleWrap { } // options.cwd - Local cwd_v = - js_options->Get(context, env->cwd_string()).ToLocalChecked(); + Local cwd_v; + if (!js_options->Get(context, env->cwd_string()).ToLocal(&cwd_v)) { + return; + } node::Utf8Value cwd(env->isolate(), cwd_v->IsString() ? cwd_v : Local()); if (cwd.length() > 0) { @@ -234,16 +281,21 @@ class ProcessWrap : public HandleWrap { } // options.env - Local env_v = - js_options->Get(context, env->env_pairs_string()).ToLocalChecked(); + Local env_v; + if (!js_options->Get(context, env->env_pairs_string()).ToLocal(&env_v)) { + return; + } if (!env_v.IsEmpty() && env_v->IsArray()) { Local env_opt = env_v.As(); int envc = env_opt->Length(); CHECK_LT(envc, INT_MAX); // Check for overflow. options.env = new char*[envc + 1]; // Heap allocated to detect errors. for (int i = 0; i < envc; i++) { - node::Utf8Value pair(env->isolate(), - env_opt->Get(context, i).ToLocalChecked()); + Local val; + if (!env_opt->Get(context, i).ToLocal(&val)) { + return; + } + node::Utf8Value pair(env->isolate(), val); options.env[i] = strdup(*pair); CHECK_NOT_NULL(options.env[i]); } @@ -251,11 +303,16 @@ class ProcessWrap : public HandleWrap { } // options.stdio - ParseStdioOptions(env, js_options, &options); + if (ParseStdioOptions(env, js_options, &options).IsNothing()) { + return; + } // options.windowsHide - Local hide_v = - js_options->Get(context, env->windows_hide_string()).ToLocalChecked(); + Local hide_v; + if (!js_options->Get(context, env->windows_hide_string()) + .ToLocal(&hide_v)) { + return; + } if (hide_v->IsTrue()) { options.flags |= UV_PROCESS_WINDOWS_HIDE; @@ -266,17 +323,22 @@ class ProcessWrap : public HandleWrap { } // options.windows_verbatim_arguments - Local wva_v = - js_options->Get(context, env->windows_verbatim_arguments_string()) - .ToLocalChecked(); + Local wva_v; + if (!js_options->Get(context, env->windows_verbatim_arguments_string()) + .ToLocal(&wva_v)) { + return; + } if (wva_v->IsTrue()) { options.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS; } // options.detached - Local detached_v = - js_options->Get(context, env->detached_string()).ToLocalChecked(); + Local detached_v; + if (!js_options->Get(context, env->detached_string()) + .ToLocal(&detached_v)) { + return; + } if (detached_v->IsTrue()) { options.flags |= UV_PROCESS_DETACHED; @@ -289,9 +351,13 @@ class ProcessWrap : public HandleWrap { if (err == 0) { CHECK_EQ(wrap->process_.data, wrap); - wrap->object()->Set(context, env->pid_string(), - Integer::New(env->isolate(), - wrap->process_.pid)).Check(); + if (wrap->object() + ->Set(context, + env->pid_string(), + Integer::New(env->isolate(), wrap->process_.pid)) + .IsNothing()) { + return; + } } if (options.args) { From 41f444fa78c6fbd99d25d844f789c5798cd8f307 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 11 Feb 2025 08:40:02 -0800 Subject: [PATCH 04/92] src: improve error handling in string_bytes/decoder PR-URL: https://github.com/nodejs/node/pull/56978 Reviewed-By: Yagiz Nizipli Reviewed-By: Chengzhong Wu --- src/string_bytes.cc | 34 ++++++++++++++++------------------ src/string_decoder.cc | 16 +++++++++------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/string_bytes.cc b/src/string_bytes.cc index 8a94d0eb63245c..4324ed52d7cd6a 100644 --- a/src/string_bytes.cc +++ b/src/string_bytes.cc @@ -112,16 +112,17 @@ class ExternString: public ResourceType { ExternString* h_str = new ExternString(isolate, data, length); - MaybeLocal str = NewExternal(isolate, h_str); - isolate->AdjustAmountOfExternalAllocatedMemory(h_str->byte_length()); + Local str; - if (str.IsEmpty()) { + if (!NewExternal(isolate, h_str).ToLocal(&str)) { delete h_str; *error = node::ERR_STRING_TOO_LONG(isolate); return MaybeLocal(); } - return str.ToLocalChecked(); + isolate->AdjustAmountOfExternalAllocatedMemory(h_str->byte_length()); + + return str; } inline Isolate* isolate() const { return isolate_; } @@ -168,16 +169,16 @@ MaybeLocal ExternOneByteString::NewSimpleFromCopy(Isolate* isolate, const char* data, size_t length, Local* error) { - MaybeLocal str = - String::NewFromOneByte(isolate, - reinterpret_cast(data), - v8::NewStringType::kNormal, - length); - if (str.IsEmpty()) { + Local str; + if (!String::NewFromOneByte(isolate, + reinterpret_cast(data), + v8::NewStringType::kNormal, + length) + .ToLocal(&str)) { *error = node::ERR_STRING_TOO_LONG(isolate); return MaybeLocal(); } - return str.ToLocalChecked(); + return str; } @@ -186,16 +187,13 @@ MaybeLocal ExternTwoByteString::NewSimpleFromCopy(Isolate* isolate, const uint16_t* data, size_t length, Local* error) { - MaybeLocal str = - String::NewFromTwoByte(isolate, - data, - v8::NewStringType::kNormal, - length); - if (str.IsEmpty()) { + Local str; + if (!String::NewFromTwoByte(isolate, data, v8::NewStringType::kNormal, length) + .ToLocal(&str)) { *error = node::ERR_STRING_TOO_LONG(isolate); return MaybeLocal(); } - return str.ToLocalChecked(); + return str; } } // anonymous namespace diff --git a/src/string_decoder.cc b/src/string_decoder.cc index 5493bc1d9cc871..c7a6f5e8e58d24 100644 --- a/src/string_decoder.cc +++ b/src/string_decoder.cc @@ -272,19 +272,21 @@ void DecodeData(const FunctionCallbackInfo& args) { ArrayBufferViewContents content(args[1].As()); size_t length = content.length(); - MaybeLocal ret = - decoder->DecodeData(args.GetIsolate(), content.data(), &length); - if (!ret.IsEmpty()) - args.GetReturnValue().Set(ret.ToLocalChecked()); + Local ret; + if (decoder->DecodeData(args.GetIsolate(), content.data(), &length) + .ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } } void FlushData(const FunctionCallbackInfo& args) { StringDecoder* decoder = reinterpret_cast(Buffer::Data(args[0])); CHECK_NOT_NULL(decoder); - MaybeLocal ret = decoder->FlushData(args.GetIsolate()); - if (!ret.IsEmpty()) - args.GetReturnValue().Set(ret.ToLocalChecked()); + Local ret; + if (decoder->FlushData(args.GetIsolate()).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } } void InitializeStringDecoder(Local target, From d8e70dcaa66de79961eecfaf8dc954ab546c1f1d Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 11 Feb 2025 22:26:51 +0000 Subject: [PATCH 05/92] src: improve node::Dotenv trimming the trimming functionality that the dotenv parsing uses currently only takes into consideration plain spaces (' '), other type of space characters such as tabs and newlines are not trimmed, this can cause subtle bugs, so the changes here make sure that such characters get trimmed as well Co-authored-by: Yagiz Nizipli PR-URL: https://github.com/nodejs/node/pull/56983 Reviewed-By: Yagiz Nizipli Reviewed-By: James M Snell --- src/node_dotenv.cc | 24 +++++++++++++++---- .../dotenv/lines-with-only-spaces.env | 8 +++++++ test/parallel/test-dotenv-edge-cases.js | 19 +++++++++++++++ 3 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 test/fixtures/dotenv/lines-with-only-spaces.env diff --git a/src/node_dotenv.cc b/src/node_dotenv.cc index 049f5cfcb77b9c..fcad2f23f50557 100644 --- a/src/node_dotenv.cc +++ b/src/node_dotenv.cc @@ -105,15 +105,22 @@ Local Dotenv::ToObject(Environment* env) const { return result; } +// Removes space characters (spaces, tabs and newlines) from +// the start and end of a given input string std::string_view trim_spaces(std::string_view input) { if (input.empty()) return ""; - if (input.front() == ' ') { - input.remove_prefix(input.find_first_not_of(' ')); + + auto pos_start = input.find_first_not_of(" \t\n"); + if (pos_start == std::string_view::npos) { + return ""; } - if (!input.empty() && input.back() == ' ') { - input = input.substr(0, input.find_last_not_of(' ') + 1); + + auto pos_end = input.find_last_not_of(" \t\n"); + if (pos_end == std::string_view::npos) { + return input.substr(pos_start); } - return input; + + return input.substr(pos_start, pos_end - pos_start + 1); } void Dotenv::ParseContent(const std::string_view input) { @@ -147,6 +154,13 @@ void Dotenv::ParseContent(const std::string_view input) { key = content.substr(0, equal); content.remove_prefix(equal + 1); key = trim_spaces(key); + + // If the value is not present (e.g. KEY=) set is to an empty string + if (content.front() == '\n') { + store_.insert_or_assign(std::string(key), ""); + continue; + } + content = trim_spaces(content); if (key.empty()) { diff --git a/test/fixtures/dotenv/lines-with-only-spaces.env b/test/fixtures/dotenv/lines-with-only-spaces.env new file mode 100644 index 00000000000000..5eeb5f48f53ff1 --- /dev/null +++ b/test/fixtures/dotenv/lines-with-only-spaces.env @@ -0,0 +1,8 @@ + +EMPTY_LINE='value after an empty line' + +SPACES_LINE='value after a line with just some spaces' + +TABS_LINE='value after a line with just some tabs' + +SPACES_TABS_LINE='value after a line with just some spaces and tabs' diff --git a/test/parallel/test-dotenv-edge-cases.js b/test/parallel/test-dotenv-edge-cases.js index 926c8d0793ac8b..99f6687d677b7c 100644 --- a/test/parallel/test-dotenv-edge-cases.js +++ b/test/parallel/test-dotenv-edge-cases.js @@ -137,6 +137,25 @@ describe('.env supports edge cases', () => { assert.strictEqual(child.code, 0); }); + it('should handle lines that come after lines with only spaces (and tabs)', async () => { + // Ref: https://github.com/nodejs/node/issues/56686 + const code = ` + process.loadEnvFile('./lines-with-only-spaces.env'); + assert.strictEqual(process.env.EMPTY_LINE, 'value after an empty line'); + assert.strictEqual(process.env.SPACES_LINE, 'value after a line with just some spaces'); + assert.strictEqual(process.env.TABS_LINE, 'value after a line with just some tabs'); + assert.strictEqual(process.env.SPACES_TABS_LINE, 'value after a line with just some spaces and tabs'); + `.trim(); + const child = await common.spawnPromisified( + process.execPath, + [ '--eval', code ], + { cwd: fixtures.path('dotenv') }, + ); + assert.strictEqual(child.stdout, ''); + assert.strictEqual(child.stderr, ''); + assert.strictEqual(child.code, 0); + }); + it('should handle when --env-file is passed along with --', async () => { const child = await common.spawnPromisified( process.execPath, From 9b98ac6a81bad6f17331c964f3c84032078033fd Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Tue, 11 Feb 2025 19:32:07 -0500 Subject: [PATCH 06/92] test: update WPT for urlpattern to ef6d83d789 PR-URL: https://github.com/nodejs/node/pull/56984 Reviewed-By: Yagiz Nizipli Reviewed-By: Jason Zhang Reviewed-By: James M Snell Reviewed-By: Luigi Pinca --- test/fixtures/wpt/README.md | 2 +- .../resources/urlpatterntestdata.json | 57 +++++++++++++++++++ test/fixtures/wpt/versions.json | 2 +- 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index c88edde07cdbd3..a1b3026c4637e9 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -29,7 +29,7 @@ Last update: - resources: https://github.com/web-platform-tests/wpt/tree/1e140d63ec/resources - streams: https://github.com/web-platform-tests/wpt/tree/bc9dcbbf1a/streams - url: https://github.com/web-platform-tests/wpt/tree/a23788b77a/url -- urlpattern: https://github.com/web-platform-tests/wpt/tree/1b56d89a26/urlpattern +- urlpattern: https://github.com/web-platform-tests/wpt/tree/ef6d83d789/urlpattern - user-timing: https://github.com/web-platform-tests/wpt/tree/5ae85bf826/user-timing - wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/cde25e7e3c/wasm/jsapi - wasm/webapi: https://github.com/web-platform-tests/wpt/tree/fd1b23eeaa/wasm/webapi diff --git a/test/fixtures/wpt/urlpattern/resources/urlpatterntestdata.json b/test/fixtures/wpt/urlpattern/resources/urlpatterntestdata.json index 058079bb6d17ac..1d2ba25ff7d696 100644 --- a/test/fixtures/wpt/urlpattern/resources/urlpatterntestdata.json +++ b/test/fixtures/wpt/urlpattern/resources/urlpatterntestdata.json @@ -1121,6 +1121,63 @@ "hostname": { "input": "xn--caf-dma.com", "groups": {}} } }, + { + "pattern": ["http://\uD83D\uDEB2.com/"], + "inputs": ["http://\uD83D\uDEB2.com/"], + "exactly_empty_components": [ "port" ], + "expected_obj": { + "protocol": "http", + "hostname": "xn--h78h.com", + "pathname": "/" + }, + "expected_match": { + "protocol": { "input": "http", "groups": {}}, + "hostname": { "input": "xn--h78h.com", "groups": {}}, + "pathname": { "input": "/", "groups": {}} + } + }, + { + "pattern": ["http://\uD83D \uDEB2"], + "expected_obj": "error" + }, + { + "pattern": [{"hostname":"\uD83D \uDEB2"}], + "expected_obj": "error" + }, + { + "pattern": [{"pathname":"\uD83D \uDEB2"}], + "inputs": [], + "expected_obj": { + "pathname": "%EF%BF%BD%20%EF%BF%BD" + }, + "expected_match": null + }, + { + "pattern": [{"pathname":":\uD83D \uDEB2"}], + "expected_obj": "error" + }, + { + "pattern": [{"pathname":":a\uDB40\uDD00b"}], + "inputs": [], + "expected_obj": { + "pathname": ":a\uDB40\uDD00b" + }, + "expected_match": null + }, + { + "pattern": [{"pathname":"test/:a\uD801\uDC50b"}], + "inputs": [{"pathname":"test/foo"}], + "expected_obj": { + "pathname": "test/:a\uD801\uDC50b" + }, + "expected_match": { + "pathname": { "input": "test/foo", "groups": { "a\uD801\uDC50b": "foo" }} + } + }, + { + "pattern": [{"pathname":":\uD83D\uDEB2"}], + "expected_obj": "error" + }, { "pattern": [{ "port": "" }], "inputs": [{ "protocol": "http", "port": "80" }], diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index 3a93f2734a93d5..9b2ef6464a5a6b 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -76,7 +76,7 @@ "path": "url" }, "urlpattern": { - "commit": "1b56d89a261b86dedfd2854b53c1732e435f1f57", + "commit": "ef6d83d789483763207af8cedcbf1f3c1317b981", "path": "urlpattern" }, "user-timing": { From 2990cc86161e31397f88a6a8f91715f522952626 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 12 Feb 2025 00:32:22 +0000 Subject: [PATCH 07/92] doc: run license-builder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/56985 Reviewed-By: Yagiz Nizipli Reviewed-By: James M Snell Reviewed-By: Michaël Zasso Reviewed-By: Luigi Pinca Reviewed-By: Marco Ippolito --- LICENSE | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/LICENSE b/LICENSE index 2b4b47df13d0e2..69fb75c37766b3 100644 --- a/LICENSE +++ b/LICENSE @@ -106,16 +106,16 @@ The externally maintained libraries used by Node.js are: - cjs-module-lexer, located at deps/cjs-module-lexer, is licensed as follows: """ - MIT License - ----------- - - Copyright (C) 2018-2020 Guy Bedford - - 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. + MIT License + ----------- + + Copyright (C) 2018-2020 Guy Bedford + + 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. """ - ittapi, located at deps/v8/third_party/ittapi, is licensed as follows: @@ -1759,7 +1759,7 @@ The externally maintained libraries used by Node.js are: same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2018-2023 The simdjson authors + Copyright 2018-2025 The simdjson authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 35f89aa66f629542173471a13b365fcd17a23131 Mon Sep 17 00:00:00 2001 From: Cheng Date: Wed, 12 Feb 2025 09:58:31 +0900 Subject: [PATCH 08/92] build: fix GN build of uv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/56955 Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli Reviewed-By: Luigi Pinca Reviewed-By: Juan José Arboleda --- deps/uv/unofficial.gni | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/uv/unofficial.gni b/deps/uv/unofficial.gni index 864414475fa69c..0944d6ddd241b1 100644 --- a/deps/uv/unofficial.gni +++ b/deps/uv/unofficial.gni @@ -90,7 +90,7 @@ template("uv_gn_build") { ldflags = [ "-pthread" ] } if (is_linux) { - libs += [ + libs = [ "m", "dl", "rt", From 3bc6d626b40e20e9157cde8f9f42a1328b85b74f Mon Sep 17 00:00:00 2001 From: Yukihiro Hasegawa <49516827+y-hsgw@users.noreply.github.com> Date: Wed, 12 Feb 2025 17:33:06 +0900 Subject: [PATCH 09/92] doc: add `signal` to `filehandle.writeFile()` options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/56804 Reviewed-By: James M Snell Reviewed-By: Juan José Arboleda Reviewed-By: Luigi Pinca Reviewed-By: Harshitha K P --- doc/api/fs.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/api/fs.md b/doc/api/fs.md index 58c02dcf8a40d4..642cc8d55e4f2d 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -805,6 +805,7 @@ changes: * `options` {Object|string} * `encoding` {string|null} The expected character encoding when `data` is a string. **Default:** `'utf8'` + * `signal` {AbortSignal|undefined} allows aborting an in-progress writeFile. **Default:** `undefined` * Returns: {Promise} Asynchronously writes data to a file, replacing the file if it already exists. From 7e2dac9fcce819b02746c400f86becdf9a1429a6 Mon Sep 17 00:00:00 2001 From: Burkov Egor Date: Wed, 12 Feb 2025 16:13:02 +0300 Subject: [PATCH 10/92] src: add self-assigment memcpy checks Fixes: https://github.com/nodejs/node/issues/56718 PR-URL: https://github.com/nodejs/node/pull/56986 Reviewed-By: James M Snell Reviewed-By: Anna Henningsen --- src/node_sockaddr-inl.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/node_sockaddr-inl.h b/src/node_sockaddr-inl.h index 87ff9b62268657..475395e602b8f2 100644 --- a/src/node_sockaddr-inl.h +++ b/src/node_sockaddr-inl.h @@ -79,12 +79,16 @@ SocketAddress::SocketAddress(const SocketAddress& addr) { } SocketAddress& SocketAddress::operator=(const sockaddr* addr) { - memcpy(&address_, addr, GetLength(addr)); + if (reinterpret_cast(&address_) != addr) { + memcpy(&address_, addr, GetLength(addr)); + } return *this; } SocketAddress& SocketAddress::operator=(const SocketAddress& addr) { - memcpy(&address_, &addr.address_, addr.length()); + if (this != &addr) { + memcpy(&address_, &addr.address_, addr.length()); + } return *this; } From a6b7bce3a01430901a1d4226ab14a15786d001b4 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Wed, 12 Feb 2025 19:25:09 +0100 Subject: [PATCH 11/92] doc: move stability index after history section for consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/56997 Reviewed-By: Luigi Pinca Reviewed-By: Michaël Zasso Reviewed-By: Ulises Gascón --- doc/api/cli.md | 12 ++++++------ doc/api/inspector.md | 4 ++-- doc/api/url.md | 4 ++-- doc/api/util.md | 10 ++++------ 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index c70cdd3be45d4c..4684d2ea2491c6 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -586,14 +586,14 @@ Disable the ability of starting a debugging session by sending a ### `--disable-warning=code-or-type` -> Stability: 1.1 - Active development - +> Stability: 1.1 - Active development + Disable specific process warnings by `code` or `type`. Warnings emitted from [`process.emitWarning()`][emit_warning] may contain a @@ -795,19 +795,17 @@ node --entry-url 'data:text/javascript,console.log("Hello")' ### `--env-file-if-exists=config` -> Stability: 1.1 - Active development - +> Stability: 1.1 - Active development + Behavior is the same as [`--env-file`][], but an error is not thrown if the file does not exist. ### `--env-file=config` -> Stability: 1.1 - Active development - +> Stability: 1.1 - Active development + Loads environment variables from a file relative to the current directory, making them available to applications on `process.env`. The [environment variables which configure Node.js][environment_variables], such as `NODE_OPTIONS`, diff --git a/doc/api/inspector.md b/doc/api/inspector.md index 2892779403d256..a0852134511151 100644 --- a/doc/api/inspector.md +++ b/doc/api/inspector.md @@ -31,12 +31,12 @@ const inspector = require('node:inspector'); ## Promises API -> Stability: 1 - Experimental - +> Stability: 1 - Experimental + ### Class: `inspector.Session` * Extends: {EventEmitter} diff --git a/doc/api/url.md b/doc/api/url.md index b37076da9bd38e..a0b1e8ffbef86e 100644 --- a/doc/api/url.md +++ b/doc/api/url.md @@ -716,12 +716,12 @@ if `input` is not a valid. ### Class: `URLPattern` -> Stability: 1 - Experimental - +> Stability: 1 - Experimental + The `URLPattern` API provides an interface to match URLs or parts of URLs against a pattern. diff --git a/doc/api/util.md b/doc/api/util.md index 9d0cd21ca9b971..8ca762317a7099 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -366,8 +366,6 @@ util.formatWithOptions({ colors: true }, 'See object %O', { foo: 42 }); ## `util.getCallSites(frameCountOrOptions, [options])` -> Stability: 1.1 - Active development - +> Stability: 1.1 - Active development + * `frameCount` {number} Optional number of frames to capture as call site objects. **Default:** `10`. Allowable range is between 1 and 200. * `options` {Object} Optional @@ -1721,14 +1721,14 @@ $ node negate.js --no-logfile --logfile=test.log --color --no-color ## `util.parseEnv(content)` -> Stability: 1.1 - Active development - +> Stability: 1.1 - Active development + * `content` {string} The raw contents of a `.env` file. @@ -1922,8 +1922,6 @@ console.log(util.stripVTControlCharacters('\u001B[4mvalue\u001B[0m')); ## `util.styleText(format, text[, options])` -> Stability: 2 - Stable. - * `view` {TypedArray} The {TypedArray} to copy. -* `offset` {integer} The starting offset within `view`. **Default:**: `0`. +* `offset` {integer} The starting offset within `view`. **Default:** `0`. * `length` {integer} The number of elements from `view` to copy. **Default:** `view.length - offset`. * Returns: {Buffer} From 6951133e1ad11b2f16893011649f62cb8adf1011 Mon Sep 17 00:00:00 2001 From: Aditi <62544124+Aditi-1400@users.noreply.github.com> Date: Fri, 14 Feb 2025 20:57:33 +0530 Subject: [PATCH 19/92] doc: improve documentation on argument validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/56954 Reviewed-By: Joyee Cheung Reviewed-By: Ulises Gascón --- src/README.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/README.md b/src/README.md index 20453b405b312f..c8c647e872da3d 100644 --- a/src/README.md +++ b/src/README.md @@ -589,6 +589,68 @@ void InitializeHttpParser(Local target, } ``` +### Argument validation in public APIs vs. internal code + +#### Public API argument sanitization + +When arguments come directly from user code, Node.js will typically validate them at the +JavaScript layer and throws user-friendly +[errors](https://github.com/nodejs/node/blob/main/doc/contributing/using-internal-errors.md) +(e.g., `ERR_INVALID_*`), if they are invalid. This helps end users +quickly understand and fix mistakes in their own code. + +This approach ensures that the error message pinpoints which argument is wrong +and how it should be fixed. Additionally, problems in user code do not cause +mysterious crashes or hard-to-diagnose failures deeper in the engine. + +Example from `zlib.js`: + +```js +function crc32(data, value = 0) { + if (typeof data !== 'string' && !isArrayBufferView(data)) { + throw new ERR_INVALID_ARG_TYPE('data', ['Buffer', 'TypedArray', 'DataView','string'], data); + } + validateUint32(value, 'value'); + return crc32Native(data, value); +} +``` + +The corresponding C++ assertion code for the above example from it's binding `node_zlib.cc`: + +```cpp +CHECK(args[0]->IsArrayBufferView() || args[0]->IsString()); +CHECK(args[1]->IsUint32()); +``` + +#### Internal code and C++ binding checks + +Inside Node.js’s internal layers, especially the C++ [binding function][]s +typically assume their arguments have already been checked and sanitized +by the upper-level (JavaScript) callers. As a result, internal C++ code +often just uses `CHECK()` or similar assertions to confirm that the +types/values passed in are correct. If that assertion fails, Node.js will +crash or abort with an internal diagnostic message. This is to avoid +re-validating every internal function argument repeatedly which can slow +down the system. + +However, in a less common case where the API is implemented completely in +C++, the arguments would be validated directly in C++, with the errors +thrown using `THROW_ERR_INVALID_*` macros from `src/node_errors.h`. + +For example in `worker_threads.moveMessagePortToContext`: + +```cpp +void MessagePort::MoveToContext(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + if (!args[0]->IsObject() || + !env->message_port_constructor_template()->HasInstance(args[0])) { + return THROW_ERR_INVALID_ARG_TYPE(env, + "The \"port\" argument must be a MessagePort instance"); + } + // ... +} +``` + ### Exception handling From b369ad6e459df9039f4462bab9fdb2d8845daf0d Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Fri, 14 Feb 2025 17:19:20 +0000 Subject: [PATCH 20/92] test: remove unnecessary assert requiring from tests PR-URL: https://github.com/nodejs/node/pull/57008 Reviewed-By: Yagiz Nizipli Reviewed-By: Michael Dawson --- .../test_uv_threadpool_size/node-options.js | 2 +- test/parallel/test-dotenv-edge-cases.js | 21 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/test/node-api/test_uv_threadpool_size/node-options.js b/test/node-api/test_uv_threadpool_size/node-options.js index c558addd1bccd5..68351c6cbee8dd 100644 --- a/test/node-api/test_uv_threadpool_size/node-options.js +++ b/test/node-api/test_uv_threadpool_size/node-options.js @@ -16,7 +16,7 @@ const filePath = path.join(__dirname, `./build/${common.buildType}/test_uv_threa const code = ` const { test } = require(${JSON.stringify(filePath)}); const size = parseInt(process.env.UV_THREADPOOL_SIZE, 10); - require('assert').strictEqual(size, 4); + assert.strictEqual(size, 4); test(size); `.trim(); const child = spawnSync( diff --git a/test/parallel/test-dotenv-edge-cases.js b/test/parallel/test-dotenv-edge-cases.js index 99f6687d677b7c..68866d828d2889 100644 --- a/test/parallel/test-dotenv-edge-cases.js +++ b/test/parallel/test-dotenv-edge-cases.js @@ -14,7 +14,6 @@ const noFinalNewlineSingleQuotesEnvFilePath = '../fixtures/dotenv/no-final-newli describe('.env supports edge cases', () => { it('supports multiple declarations, including optional ones', async () => { const code = ` - const assert = require('assert'); assert.strictEqual(process.env.BASIC, 'basic'); assert.strictEqual(process.env.NODE_NO_WARNINGS, '1'); `.trim(); @@ -41,7 +40,7 @@ describe('.env supports edge cases', () => { it('supports absolute paths', async () => { const code = ` - require('assert').strictEqual(process.env.BASIC, 'basic'); + assert.strictEqual(process.env.BASIC, 'basic'); `.trim(); const child = await common.spawnPromisified( process.execPath, @@ -53,7 +52,7 @@ describe('.env supports edge cases', () => { it('supports a space instead of \'=\' for the flag ', async () => { const code = ` - require('assert').strictEqual(process.env.BASIC, 'basic'); + assert.strictEqual(process.env.BASIC, 'basic'); `.trim(); const child = await common.spawnPromisified( process.execPath, @@ -66,7 +65,7 @@ describe('.env supports edge cases', () => { it('should handle non-existent .env file', async () => { const code = ` - require('assert').strictEqual(1, 1) + assert.strictEqual(1, 1) `.trim(); const child = await common.spawnPromisified( process.execPath, @@ -79,7 +78,7 @@ describe('.env supports edge cases', () => { it('should handle non-existent optional .env file', async () => { const code = ` - require('assert').strictEqual(1,1); + assert.strictEqual(1,1); `.trim(); const child = await common.spawnPromisified( process.execPath, @@ -92,8 +91,8 @@ describe('.env supports edge cases', () => { it('should not override existing environment variables but introduce new vars', async () => { const code = ` - require('assert').strictEqual(process.env.BASIC, 'existing'); - require('assert').strictEqual(process.env.AFTER_LINE, 'after_line'); + assert.strictEqual(process.env.BASIC, 'existing'); + assert.strictEqual(process.env.AFTER_LINE, 'after_line'); `.trim(); const child = await common.spawnPromisified( process.execPath, @@ -124,8 +123,8 @@ describe('.env supports edge cases', () => { // Ref: https://github.com/nodejs/node/issues/52466 const code = ` process.loadEnvFile('./eof-without-value.env'); - require('assert').strictEqual(process.env.BASIC, 'value'); - require('assert').strictEqual(process.env.EMPTY, ''); + assert.strictEqual(process.env.BASIC, 'value'); + assert.strictEqual(process.env.EMPTY, ''); `.trim(); const child = await common.spawnPromisified( process.execPath, @@ -160,7 +159,7 @@ describe('.env supports edge cases', () => { const child = await common.spawnPromisified( process.execPath, [ - '--eval', `require('assert').strictEqual(process.env.BASIC, undefined);`, + '--eval', `assert.strictEqual(process.env.BASIC, undefined);`, '--', '--env-file', validEnvFilePath, ], { cwd: __dirname }, @@ -172,7 +171,7 @@ describe('.env supports edge cases', () => { it('should handle file without a final newline', async () => { const code = ` - require('assert').strictEqual(process.env.BASIC, 'basic'); + assert.strictEqual(process.env.BASIC, 'basic'); `.trim(); const child = await common.spawnPromisified( process.execPath, From ccb8c12712ae4a3b58acbe33e6c1828157340d2b Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Sat, 15 Feb 2025 10:08:02 +0100 Subject: [PATCH 21/92] test,crypto: make tests work for BoringSSL Ref https://github.com/nodejs/node/pull/56559 PR-URL: https://github.com/nodejs/node/pull/57021 Reviewed-By: James M Snell Reviewed-By: Luigi Pinca Reviewed-By: Richard Lau Reviewed-By: Michael Dawson --- test/parallel/test-crypto-prime.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/parallel/test-crypto-prime.js b/test/parallel/test-crypto-prime.js index a53ee4f3e0417a..6f43e3a0bfbf29 100644 --- a/test/parallel/test-crypto-prime.js +++ b/test/parallel/test-crypto-prime.js @@ -258,11 +258,11 @@ for (const checks of [-(2 ** 31), -1, 2 ** 31, 2 ** 32 - 1, 2 ** 32, 2 ** 50]) { bytes[0] = 0x1; assert.throws(() => checkPrime(bytes, common.mustNotCall()), { code: 'ERR_OSSL_BN_BIGNUM_TOO_LONG', - message: /bignum too long/ + message: /bignum[_ ]too[_ ]long/i }); assert.throws(() => checkPrimeSync(bytes), { code: 'ERR_OSSL_BN_BIGNUM_TOO_LONG', - message: /bignum too long/ + message: /bignum[_ ]too[_ ]long/i }); } From 5af35d18503a8fae9f2ac66bbb27b2d50f3ebf51 Mon Sep 17 00:00:00 2001 From: Cheng Date: Sat, 15 Feb 2025 21:11:49 +0900 Subject: [PATCH 22/92] build: fix GN build failure PR-URL: https://github.com/nodejs/node/pull/57013 Reviewed-By: Colin Ihrig Reviewed-By: Joyee Cheung --- deps/zstd/unofficial.gni | 1 + unofficial.gni | 2 ++ 2 files changed, 3 insertions(+) diff --git a/deps/zstd/unofficial.gni b/deps/zstd/unofficial.gni index 46504122764869..fe6c2b425eac2a 100644 --- a/deps/zstd/unofficial.gni +++ b/deps/zstd/unofficial.gni @@ -19,6 +19,7 @@ template("zstd_gn_build") { public_configs = [ ":zstd_config" ] sources = gypi_values.zstd_sources defines = [ "XXH_NAMESPACE=ZSTD_", "ZSTD_MULTITHREAD", "ZSTD_DISABLE_ASM" ] + cflags_c = [ "-Wno-unused-function" ] if (is_posix) { ldflags = [ "-pthread" ] diff --git a/unofficial.gni b/unofficial.gni index a7414ced8bd5c4..63cf55a1812336 100644 --- a/unofficial.gni +++ b/unofficial.gni @@ -74,6 +74,7 @@ template("node_gn_build") { "-Wno-implicit-fallthrough", "-Wno-macro-redefined", "-Wno-missing-braces", + "-Wno-range-loop-bind-reference", "-Wno-return-type", "-Wno-shadow", "-Wno-sometimes-uninitialized", @@ -160,6 +161,7 @@ template("node_gn_build") { "deps/postject", "deps/sqlite", "deps/uvwasi", + "deps/zstd", "//third_party/zlib", "$node_simdutf_path", "$node_v8_path:v8_libplatform", From 50ba04e2141873706f51290e9ff0cc5ac584b20e Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Sat, 15 Feb 2025 13:58:08 +0100 Subject: [PATCH 23/92] doc: recommend writing tests in new files and including comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous phrasing encouraged or did not discourage appending new test cases to existing files - a practice that can reduce the debuggability of the tests over time as they get bigger and bigger, some times thousands of lines long with hundreds of test cases, and make the CI output increasingly difficult to read when one of the test cases fail in a very long test. This patch updates the guideline to explicitly discourage appending test cases this way. Also recommend including an opening comment to describe what the test does to optimize the test towards the scenario when it fails. PR-URL: https://github.com/nodejs/node/pull/57028 Reviewed-By: James M Snell Reviewed-By: Richard Lau Reviewed-By: Michaël Zasso Reviewed-By: Ulises Gascón Reviewed-By: Luigi Pinca Reviewed-By: Jake Yuesong Li Reviewed-By: Chengzhong Wu Reviewed-By: Tierney Cyren --- doc/contributing/writing-tests.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/doc/contributing/writing-tests.md b/doc/contributing/writing-tests.md index 0a6af29bf04ac8..8c67ed2c9410c4 100644 --- a/doc/contributing/writing-tests.md +++ b/doc/contributing/writing-tests.md @@ -21,9 +21,22 @@ Add tests when: ## Test directory structure See [directory structure overview][] for outline of existing test and locations. -When deciding on whether to expand an existing test file or create a new one, -consider going through the files related to the subsystem. -For example, look for `test-streams` when writing a test for `lib/streams.js`. + +## How to write a good test + +A good test should be written in a style that is optimial for debugging +when it fails. + +In principle, when adding a new test, it should be placed in a new file. +Unless there is strong motivation to do so, refrain from appending +new test cases to an existing file. Similar to the reproductions we ask +for in the issue tracker, a good test should be as minimal and isolated as +possible to facilitate debugging. + +A good test should come with comments explaining what it tries to test, +so that when it fails, other contributors can fix it with the full context +of its intention, and be able to modify it in good confidence when the context +changes. ## Test structure From 6cdee545f62cc251beddd9fae67b84a6ba3ec4a5 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 15 Feb 2025 07:01:25 -0800 Subject: [PATCH 24/92] tools: do not run major-release workflow on forks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/57064 Reviewed-By: Jordan Harband Reviewed-By: Michaël Zasso Reviewed-By: Antoine du Hamel Reviewed-By: Richard Lau Reviewed-By: James M Snell --- .github/workflows/major-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/major-release.yml b/.github/workflows/major-release.yml index a90be1798fac85..dfe49605429583 100644 --- a/.github/workflows/major-release.yml +++ b/.github/workflows/major-release.yml @@ -9,6 +9,7 @@ permissions: jobs: create-issue: + if: github.repository == 'nodejs/node' runs-on: ubuntu-latest permissions: issues: write From a588066518c831d6706a454caf34a0446a5bc492 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:39:58 +0100 Subject: [PATCH 25/92] test: add case for unrecognised fields within pjson "exports" PR-URL: https://github.com/nodejs/node/pull/57026 Reviewed-By: Antoine du Hamel Reviewed-By: James M Snell --- .../packages/unrecognised-export-keys/index.js | 0 .../unrecognised-export-keys/package.json | 10 ++++++++++ test/parallel/test-find-package-json.js | 15 +++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 test/fixtures/packages/unrecognised-export-keys/index.js create mode 100644 test/fixtures/packages/unrecognised-export-keys/package.json diff --git a/test/fixtures/packages/unrecognised-export-keys/index.js b/test/fixtures/packages/unrecognised-export-keys/index.js new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/test/fixtures/packages/unrecognised-export-keys/package.json b/test/fixtures/packages/unrecognised-export-keys/package.json new file mode 100644 index 00000000000000..e3cef44544cc43 --- /dev/null +++ b/test/fixtures/packages/unrecognised-export-keys/package.json @@ -0,0 +1,10 @@ +{ + "name": "pkg-with-unrecognised-export-keys", + "exports": { + ".": { + "default": "./index.js", + "FtLcAG": "./whatever.ext", + "types": "./index.d.ts" + } + } +} diff --git a/test/parallel/test-find-package-json.js b/test/parallel/test-find-package-json.js index 5e55e81da1e7b8..fd9a1a85e77441 100644 --- a/test/parallel/test-find-package-json.js +++ b/test/parallel/test-find-package-json.js @@ -189,4 +189,19 @@ describe('findPackageJSON', () => { // Throws when no arguments are provided assert.ok(stdout.includes(foundPjsonPath), stdout); assert.strictEqual(code, 0); }); + + it('should work when unrecognised keys are specified within the export', async () => { + const specifierBase = './packages/unrecognised-export-keys'; + const target = fixtures.fileURL(specifierBase, 'index.js'); + const foundPjsonPath = path.toNamespacedPath(fixtures.path(specifierBase, 'package.json')); + + const { code, stderr, stdout } = await common.spawnPromisified(process.execPath, [ + '--print', + `require("node:module").findPackageJSON(${JSON.stringify(target)})`, + ]); + + assert.strictEqual(stderr, ''); + assert.ok(stdout.includes(foundPjsonPath), stdout); + assert.strictEqual(code, 0); + }); }); From 40f3a516bf8dc798d37271bf0586011d939159ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Arboleda?= Date: Mon, 10 Feb 2025 12:14:36 -0500 Subject: [PATCH 26/92] fs: handle UV_ENOTDIR in `fs.statSync` with `throwIfNoEntry` provided MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: https://github.com/nodejs/node/issues/56993 Signed-off-by: Juan José Arboleda PR-URL: https://github.com/nodejs/node/pull/56996 Reviewed-By: Yagiz Nizipli Reviewed-By: Anna Henningsen Reviewed-By: James M Snell --- src/node_file.cc | 11 +++++++++-- test/parallel/test-fs-stat.js | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/node_file.cc b/src/node_file.cc index 8e29bb39887625..3a51551aa184cd 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -1088,6 +1088,10 @@ constexpr bool is_uv_error_except_no_entry(int result) { return result < 0 && result != UV_ENOENT; } +constexpr bool is_uv_error_except_no_entry_dir(int result) { + return result < 0 && !(result == UV_ENOENT || result == UV_ENOTDIR); +} + static void Stat(const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); BindingData* binding_data = realm->GetBindingData(); @@ -1121,8 +1125,11 @@ static void Stat(const FunctionCallbackInfo& args) { FS_SYNC_TRACE_BEGIN(stat); int result; if (do_not_throw_if_no_entry) { - result = SyncCallAndThrowIf( - is_uv_error_except_no_entry, env, &req_wrap_sync, uv_fs_stat, *path); + result = SyncCallAndThrowIf(is_uv_error_except_no_entry_dir, + env, + &req_wrap_sync, + uv_fs_stat, + *path); } else { result = SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_stat, *path); } diff --git a/test/parallel/test-fs-stat.js b/test/parallel/test-fs-stat.js index b9d42b5b61bc83..cc7be77e53dadc 100644 --- a/test/parallel/test-fs-stat.js +++ b/test/parallel/test-fs-stat.js @@ -221,3 +221,8 @@ fs.lstat(__filename, undefined, common.mustCall()); }, ); } + +{ + // Test that the throwIfNoEntry option works and returns undefined + assert.ok(!(fs.statSync('./wont_exists', { throwIfNoEntry: false }))); +} From 5b2dfadd4063a155f08a94e5b4e9d6fd90cd41dc Mon Sep 17 00:00:00 2001 From: Aditi <62544124+Aditi-1400@users.noreply.github.com> Date: Sun, 16 Feb 2025 03:41:00 +0530 Subject: [PATCH 27/92] doc: add a note about `require('../common')` in testing documentation PR-URL: https://github.com/nodejs/node/pull/56953 Reviewed-By: Joyee Cheung Reviewed-By: Richard Lau --- test/common/README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/common/README.md b/test/common/README.md index 887dee2783ad72..720ff33fd60f36 100644 --- a/test/common/README.md +++ b/test/common/README.md @@ -1,6 +1,29 @@ # Node.js Core Test Common Modules This directory contains modules used to test the Node.js implementation. +All tests must begin by requiring the `common` module: + +```js +require('../common'); +``` + +This is not just a convenience for exporting helper functions etc; it also performs +several other tasks: + +* Verifies that no unintended globals have been leaked to ensure that tests + don't accidentally pollute the global namespace. + +* Some tests assume a default umask of `0o022`. To enforce this assumption, + the common module sets the unmask at startup. Tests that require a + different umask can override this setting after loading the module. + +* Some tests specify runtime flags (example, `--expose-internals`) via a + comment at the top of the file: `// Flags: --expose-internals`. + If the test is run without those flags, the common module automatically + spawns a child process with proper flags. This ensures that the tests + always run under the expected conditions. Because of this behaviour, the + common module must be loaded first so that any code below it is not + executed until the process has been re-spawned with the correct flags. ## Table of contents From b50fc42a992ee8ab3371fa37ca2d36a7f1c7b127 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Sat, 15 Feb 2025 23:54:31 +0100 Subject: [PATCH 28/92] crypto: support --use-system-ca on non-Windows and non-macOS On other platforms, load from the OpenSSL default certificate file and diretory. This is different from --use-openssl-ca in that it caches the certificates on first load, instead of always reading from disk every time a new root store is needed. When used together with the statically-linked OpenSSL, the default configuration usually leads to this behavior: - If SSL_CERT_FILE is used, load from SSL_CERT_FILE. Otherwise load from /etc/ssl/cert.pem - If SSL_CERT_DIR is used, load from all the files under SSL_CERT_DIR. Otherwise, load from all the files under /etc/ssl/certs PR-URL: https://github.com/nodejs/node/pull/57009 Reviewed-By: Richard Lau Reviewed-By: James M Snell --- doc/api/cli.md | 36 ++++++++++---- src/crypto/crypto_context.cc | 73 ++++++++++++++++++++++++++++- test/parallel/test-native-certs.mjs | 17 +++++-- 3 files changed, 111 insertions(+), 15 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index 4684d2ea2491c6..2ac8cfda33be8f 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -2838,12 +2838,15 @@ The following values are valid for `mode`: ### `--use-system-ca` Node.js uses the trusted CA certificates present in the system store along with -the `--use-bundled-ca`, `--use-openssl-ca` options. +the `--use-bundled-ca` option and the `NODE_EXTRA_CA_CERTS` environment variable. +On platforms other than Windows and macOS, this loads certificates from the directory +and file trusted by OpenSSL, similar to `--use-openssl-ca`, with the difference being +that it caches the certificates after first load. -This option is only supported on Windows and macOS, and the certificate trust policy -is planned to follow [Chromium's policy for locally trusted certificates][]: +On Windows and macOS, the certificate trust policy is planned to follow +[Chromium's policy for locally trusted certificates][]: -On macOS, the following certifcates are trusted: +On macOS, the following settings are respected: * Default and System Keychains * Trust: @@ -2853,8 +2856,8 @@ On macOS, the following certifcates are trusted: * Any certificate where the “When using this certificate” flag is set to “Never Trust” or * Any certificate where the “Secure Sockets Layer (SSL)” flag is set to “Never Trust.” -On Windows, the following certificates are currently trusted (unlike -Chromium's policy, distrust is not currently supported): +On Windows, the following settings are respected (unlike Chromium's policy, distrust +and intermediate CA are not currently supported): * Local Machine (accessed via `certlm.msc`) * Trust: @@ -2869,8 +2872,19 @@ Chromium's policy, distrust is not currently supported): * Trusted Root Certification Authorities * Enterprise Trust -> Group Policy -> Trusted Root Certification Authorities -On any supported system, Node.js would check that the certificate's key usage and extended key -usage are consistent with TLS use cases before using it for server authentication. +On Windows and macOS, Node.js would check that the user settings for the certificates +do not forbid them for TLS server authentication before using them. + +On other systems, Node.js loads certificates from the default certificate file +(typically `/etc/ssl/cert.pem`) and default certificate directory (typically +`/etc/ssl/certs`) that the version of OpenSSL that Node.js links to respects. +This typically works with the convention on major Linux distributions and other +Unix-like systems. If the overriding OpenSSL environment variables +(typically `SSL_CERT_FILE` and `SSL_CERT_DIR`, depending on the configuration +of the OpenSSL that Node.js links to) are set, the specified paths will be used to load +certificates instead. These environment variables can be used as workarounds +if the conventional paths used by the version of OpenSSL Node.js links to are +not consistent with the system configuration that the users have for some reason. ### `--v8-options` @@ -3512,7 +3526,8 @@ variable is ignored. added: v7.7.0 --> -If `--use-openssl-ca` is enabled, this overrides and sets OpenSSL's directory +If `--use-openssl-ca` is enabled, or if `--use-system-ca` is enabled on +platforms other than macOS and Windows, this overrides and sets OpenSSL's directory containing trusted certificates. Be aware that unless the child environment is explicitly set, this environment @@ -3525,7 +3540,8 @@ may cause them to trust the same CAs as node. added: v7.7.0 --> -If `--use-openssl-ca` is enabled, this overrides and sets OpenSSL's file +If `--use-openssl-ca` is enabled, or if `--use-system-ca` is enabled on +platforms other than macOS and Windows, this overrides and sets OpenSSL's file containing trusted certificates. Be aware that unless the child environment is explicitly set, this environment diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index 28dedf33b18154..3e4b517fa462ef 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -223,7 +223,7 @@ int SSL_CTX_use_certificate_chain(SSL_CTX* ctx, issuer); } -unsigned long LoadCertsFromFile( // NOLINT(runtime/int) +static unsigned long LoadCertsFromFile( // NOLINT(runtime/int) std::vector* certs, const char* file) { MarkPopErrorOnReturn mark_pop_error_on_return; @@ -645,6 +645,74 @@ void ReadWindowsCertificates( } #endif +static void LoadCertsFromDir(std::vector* certs, + std::string_view cert_dir) { + uv_fs_t dir_req; + auto cleanup = OnScopeLeave([&dir_req]() { uv_fs_req_cleanup(&dir_req); }); + int err = uv_fs_scandir(nullptr, &dir_req, cert_dir.data(), 0, nullptr); + if (err < 0) { + fprintf(stderr, + "Cannot open directory %s to load OpenSSL certificates.\n", + cert_dir.data()); + return; + } + + uv_fs_t stats_req; + auto cleanup_stats = + OnScopeLeave([&stats_req]() { uv_fs_req_cleanup(&stats_req); }); + for (;;) { + uv_dirent_t ent; + + int r = uv_fs_scandir_next(&dir_req, &ent); + if (r == UV_EOF) { + break; + } + if (r < 0) { + char message[64]; + uv_strerror_r(r, message, sizeof(message)); + fprintf(stderr, + "Cannot scan directory %s to load OpenSSL certificates.\n", + cert_dir.data()); + return; + } + + std::string file_path = std::string(cert_dir) + "/" + ent.name; + int stats_r = uv_fs_stat(nullptr, &stats_req, file_path.c_str(), nullptr); + if (stats_r == 0 && + (static_cast(stats_req.ptr)->st_mode & S_IFREG)) { + LoadCertsFromFile(certs, file_path.c_str()); + } + } +} + +// Loads CA certificates from the default certificate paths respected by +// OpenSSL. +void GetOpenSSLSystemCertificates(std::vector* system_store_certs) { + std::string cert_file; + // While configurable when OpenSSL is built, this is usually SSL_CERT_FILE. + if (!credentials::SafeGetenv(X509_get_default_cert_file_env(), &cert_file)) { + // This is usually /etc/ssl/cert.pem if we are using the OpenSSL statically + // linked and built with default configurations. + cert_file = X509_get_default_cert_file(); + } + + std::string cert_dir; + // While configurable when OpenSSL is built, this is usually SSL_CERT_DIR. + if (!credentials::SafeGetenv(X509_get_default_cert_dir_env(), &cert_dir)) { + // This is usually /etc/ssl/certs if we are using the OpenSSL statically + // linked and built with default configurations. + cert_dir = X509_get_default_cert_dir(); + } + + if (!cert_file.empty()) { + LoadCertsFromFile(system_store_certs, cert_file.c_str()); + } + + if (!cert_dir.empty()) { + LoadCertsFromDir(system_store_certs, cert_dir.c_str()); + } +} + static std::vector InitializeBundledRootCertificates() { // Read the bundled certificates in node_root_certs.h into // bundled_root_certs_vector. @@ -685,6 +753,9 @@ static std::vector InitializeSystemStoreCertificates() { #endif #ifdef _WIN32 ReadWindowsCertificates(&system_store_certs); +#endif +#if !defined(__APPLE__) && !defined(_WIN32) + GetOpenSSLSystemCertificates(&system_store_certs); #endif return system_store_certs; } diff --git a/test/parallel/test-native-certs.mjs b/test/parallel/test-native-certs.mjs index f27e1d81a4f05e..ed8769e92acb32 100644 --- a/test/parallel/test-native-certs.mjs +++ b/test/parallel/test-native-certs.mjs @@ -7,10 +7,6 @@ import fixtures from '../common/fixtures.js'; import { it, beforeEach, afterEach, describe } from 'node:test'; import { once } from 'events'; -if (!common.isMacOS && !common.isWindows) { - common.skip('--use-system-ca is only supported on macOS and Windows'); -} - if (!common.hasCrypto) { common.skip('requires crypto'); } @@ -34,6 +30,19 @@ if (!common.hasCrypto) { // $ $thumbprint = (Get-ChildItem -Path Cert:\CurrentUser\Root | \ // Where-Object { $_.Subject -match "StartCom Certification Authority" }).Thumbprint // $ Remove-Item -Path "Cert:\CurrentUser\Root\$thumbprint" +// +// On Debian/Ubuntu: +// 1. To add the certificate: +// $ sudo cp test/fixtures/keys/fake-startcom-root-cert.pem \ +// /usr/local/share/ca-certificates/fake-startcom-root-cert.crt +// $ sudo update-ca-certificates +// 2. To remove the certificate +// $ sudo rm /usr/local/share/ca-certificates/fake-startcom-root-cert.crt +// $ sudo update-ca-certificates --fresh +// +// For other Unix-like systems, consult their manuals, there are usually +// file-based processes similar to the Debian/Ubuntu one but with different +// file locations and update commands. const handleRequest = (req, res) => { const path = req.url; switch (path) { From fa26f83e5b0f844e696a63a54b98ed7cdb460dd2 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Sun, 16 Feb 2025 00:49:38 +0100 Subject: [PATCH 29/92] src: lock the isolate properly in IsolateData destructor Otherwise it may fail the DCHECK that uses the locked thread as a fast path to get the current thread. PR-URL: https://github.com/nodejs/node/pull/57031 Reviewed-By: Chengzhong Wu Reviewed-By: Yagiz Nizipli Reviewed-By: Anna Henningsen Reviewed-By: James M Snell Reviewed-By: Rafael Gonzaga --- src/env.cc | 1 + test/cctest/test_environment.cc | 3 +++ test/cctest/test_platform.cc | 1 + 3 files changed, 5 insertions(+) diff --git a/src/env.cc b/src/env.cc index cd7203ffda6e7c..7c1511794bd8e3 100644 --- a/src/env.cc +++ b/src/env.cc @@ -615,6 +615,7 @@ IsolateData::IsolateData(Isolate* isolate, IsolateData::~IsolateData() { if (cpp_heap_ != nullptr) { + v8::Locker locker(isolate_); // The CppHeap must be detached before being terminated. isolate_->DetachCppHeap(); cpp_heap_->Terminate(); diff --git a/test/cctest/test_environment.cc b/test/cctest/test_environment.cc index 14e82cc80ff730..008cda77b650dc 100644 --- a/test/cctest/test_environment.cc +++ b/test/cctest/test_environment.cc @@ -438,6 +438,7 @@ TEST_F(EnvironmentTest, InspectorMultipleEmbeddedEnvironments) { // This test sets a global variable in the child Environment, and reads it // back both through the inspector and inside the child Environment, and // makes sure that those correspond to the value that was originally set. + v8::Locker locker(isolate_); const v8::HandleScope handle_scope(isolate_); const Argv argv; Env env {handle_scope, argv}; @@ -507,6 +508,7 @@ TEST_F(EnvironmentTest, InspectorMultipleEmbeddedEnvironments) { CHECK_NOT_NULL(isolate); { + v8::Locker locker(isolate); v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handle_scope(isolate); @@ -630,6 +632,7 @@ TEST_F(NodeZeroIsolateTestFixture, CtrlCWithOnlySafeTerminationTest) { // Try creating Context + IsolateData + Environment. { + v8::Locker locker(isolate); v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handle_scope(isolate); diff --git a/test/cctest/test_platform.cc b/test/cctest/test_platform.cc index c2d78938130006..53644accf29749 100644 --- a/test/cctest/test_platform.cc +++ b/test/cctest/test_platform.cc @@ -76,6 +76,7 @@ TEST_F(NodeZeroIsolateTestFixture, IsolatePlatformDelegateTest) { // Try creating Context + IsolateData + Environment { + v8::Locker locker(isolate); v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handle_scope(isolate); From 286bb84188e3b3769e5e7843a907715e05b164dc Mon Sep 17 00:00:00 2001 From: Cheng Date: Sun, 16 Feb 2025 15:45:40 +0900 Subject: [PATCH 30/92] src: fix accessing empty string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/57014 Reviewed-By: Yagiz Nizipli Reviewed-By: Joyee Cheung Reviewed-By: Anna Henningsen Reviewed-By: Luigi Pinca Reviewed-By: Ulises Gascón Reviewed-By: James M Snell --- src/node_dotenv.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node_dotenv.cc b/src/node_dotenv.cc index fcad2f23f50557..0d0130b928e6bf 100644 --- a/src/node_dotenv.cc +++ b/src/node_dotenv.cc @@ -156,7 +156,7 @@ void Dotenv::ParseContent(const std::string_view input) { key = trim_spaces(key); // If the value is not present (e.g. KEY=) set is to an empty string - if (content.front() == '\n') { + if (content.empty() || content.front() == '\n') { store_.insert_or_assign(std::string(key), ""); continue; } From c02494f5fed23b9b15c34b32f463d610f04adf57 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Sun, 16 Feb 2025 13:48:56 +0100 Subject: [PATCH 31/92] doc: fix transpiler loader hooks documentation The loader hooks examples have been broken for a while: 1. The nextLoad() hook cannot be used on a .coffee file that ends up going to the default load step without an explict format, which would cause a ERR_UNKNOWN_FILE_EXTENSION. Mention adding a package.json with a type field to work around it in the example. 2. Pass the context parameter to the nextLoad() invocation and document that context.format is mandatory when module type is not explicitly inferrable from the module. 3. Correct the getPackageType() implementation which returns false instead of undefined in the absence of an explict format, which is not a valid type for format. PR-URL: https://github.com/nodejs/node/pull/57037 Refs: https://github.com/nodejs/node/issues/57030 Reviewed-By: Jacob Smith Reviewed-By: Antoine du Hamel --- doc/api/module.md | 102 ++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 54 deletions(-) diff --git a/doc/api/module.md b/doc/api/module.md index 7f9137a25f068e..5535da223f583d 100644 --- a/doc/api/module.md +++ b/doc/api/module.md @@ -1156,7 +1156,11 @@ changes: Node.js default `load` hook after the last user-supplied `load` hook * `url` {string} * `context` {Object|undefined} When omitted, defaults are provided. When provided, defaults are - merged in with preference to the provided properties. + merged in with preference to the provided properties. In the default `nextLoad`, if + the module pointed to by `url` does not have explicit module type information, + `context.format` is mandatory. + * Returns: {Object|Promise} The asynchronous version takes either an object containing the following properties, or a `Promise` that will resolve to such an object. The synchronous version only accepts an object returned synchronously. @@ -1354,36 +1358,32 @@ transpiler hooks should only be used for development and testing purposes. ```mjs // coffeescript-hooks.mjs import { readFile } from 'node:fs/promises'; -import { dirname, extname, resolve as resolvePath } from 'node:path'; -import { cwd } from 'node:process'; -import { fileURLToPath, pathToFileURL } from 'node:url'; +import { findPackageJSON } from 'node:module'; import coffeescript from 'coffeescript'; const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/; export async function load(url, context, nextLoad) { if (extensionsRegex.test(url)) { - // CoffeeScript files can be either CommonJS or ES modules, so we want any - // CoffeeScript file to be treated by Node.js the same as a .js file at the - // same location. To determine how Node.js would interpret an arbitrary .js - // file, search up the file system for the nearest parent package.json file - // and read its "type" field. - const format = await getPackageType(url); - - const { source: rawSource } = await nextLoad(url, { ...context, format }); + // CoffeeScript files can be either CommonJS or ES modules. Use a custom format + // to tell Node.js not to detect its module type. + const { source: rawSource } = await nextLoad(url, { ...context, format: 'coffee' }); // This hook converts CoffeeScript source code into JavaScript source code // for all imported CoffeeScript files. const transformedSource = coffeescript.compile(rawSource.toString(), url); + // To determine how Node.js would interpret the transpilation result, + // search up the file system for the nearest parent package.json file + // and read its "type" field. return { - format, + format: await getPackageType(url), shortCircuit: true, source: transformedSource, }; } // Let Node.js handle all other URLs. - return nextLoad(url); + return nextLoad(url, context); } async function getPackageType(url) { @@ -1394,25 +1394,12 @@ async function getPackageType(url) { // this simple truthy check for whether `url` contains a file extension will // work for most projects but does not cover some edge-cases (such as // extensionless files or a url ending in a trailing space) - const isFilePath = !!extname(url); - // If it is a file path, get the directory it's in - const dir = isFilePath ? - dirname(fileURLToPath(url)) : - url; - // Compose a file path to a package.json in the same directory, - // which may or may not exist - const packagePath = resolvePath(dir, 'package.json'); - // Try to read the possibly nonexistent package.json - const type = await readFile(packagePath, { encoding: 'utf8' }) - .then((filestring) => JSON.parse(filestring).type) - .catch((err) => { - if (err?.code !== 'ENOENT') console.error(err); - }); - // If package.json existed and contained a `type` field with a value, voilà - if (type) return type; - // Otherwise, (if not at the root) continue checking the next directory up - // If at the root, stop and return false - return dir.length > 1 && getPackageType(resolvePath(dir, '..')); + const pJson = findPackageJSON(url); + + return readFile(pJson, 'utf8') + .then(JSON.parse) + .then((json) => json?.type) + .catch(() => undefined); } ``` @@ -1420,46 +1407,38 @@ async function getPackageType(url) { ```mjs // coffeescript-sync-hooks.mjs -import { readFileSync } from 'node:fs/promises'; -import { registerHooks } from 'node:module'; -import { dirname, extname, resolve as resolvePath } from 'node:path'; -import { cwd } from 'node:process'; -import { fileURLToPath, pathToFileURL } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { registerHooks, findPackageJSON } from 'node:module'; import coffeescript from 'coffeescript'; const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/; function load(url, context, nextLoad) { if (extensionsRegex.test(url)) { - const format = getPackageType(url); - - const { source: rawSource } = nextLoad(url, { ...context, format }); + const { source: rawSource } = nextLoad(url, { ...context, format: 'coffee' }); const transformedSource = coffeescript.compile(rawSource.toString(), url); return { - format, + format: getPackageType(url), shortCircuit: true, source: transformedSource, }; } - return nextLoad(url); + return nextLoad(url, context); } function getPackageType(url) { - const isFilePath = !!extname(url); - const dir = isFilePath ? dirname(fileURLToPath(url)) : url; - const packagePath = resolvePath(dir, 'package.json'); - - let type; + const pJson = findPackageJSON(url); + if (!pJson) { + return undefined; + } try { - const filestring = readFileSync(packagePath, { encoding: 'utf8' }); - type = JSON.parse(filestring).type; - } catch (err) { - if (err?.code !== 'ENOENT') console.error(err); + const file = readFileSync(pJson, 'utf-8'); + return JSON.parse(file)?.type; + } catch { + return undefined; } - if (type) return type; - return dir.length > 1 && getPackageType(resolvePath(dir, '..')); } registerHooks({ load }); @@ -1481,6 +1460,21 @@ console.log "Brought to you by Node.js version #{version}" export scream = (str) -> str.toUpperCase() ``` +For the sake of running the example, add a `package.json` file containing the +module type of the CoffeeScript files. + +```json +{ + "type": "module" +} +``` + +This is only for running the example. In real world loaders, `getPackageType()` must be +able to return an `format` known to Node.js even in the absence of an explicit type in a +`package.json`, or otherwise the `nextLoad` call would throw `ERR_UNKNOWN_FILE_EXTENSION` +(if undefined) or `ERR_UNKNOWN_MODULE_FORMAT` (if it's not a known format listed in +the [load hook][] documentation). + With the preceding hooks modules, running `node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ./main.coffee` or `node --import ./coffeescript-sync-hooks.mjs ./main.coffee` From a7e5ef9e019c4c10ef7cebd600777940014ea286 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Sun, 16 Feb 2025 15:28:33 +0000 Subject: [PATCH 32/92] doc: fix wrong verb form PR-URL: https://github.com/nodejs/node/pull/57091 Reviewed-By: Jacob Smith Reviewed-By: Antoine du Hamel Reviewed-By: James M Snell --- doc/api/modules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/modules.md b/doc/api/modules.md index 204809f5b779b0..03d0a4677e9a06 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -203,7 +203,7 @@ regarding which files are parsed as ECMAScript modules. 3. The file has a `.js` extension, the closest `package.json` does not contain `"type": "commonjs"`, and the module contains ES module syntax. -If the ES Module being loaded meet the requirements, `require()` can load it and +If the ES Module being loaded meets the requirements, `require()` can load it and return the module namespace object. In this case it is similar to dynamic `import()` but is run synchronously and returns the name space object directly. From 9de45cbac9ac67e2e9c41a687baa58a8e48d7086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20=E2=80=9Cweej=E2=80=9D=20Jones?= Date: Sun, 16 Feb 2025 18:26:30 +0000 Subject: [PATCH 33/92] doc: `modules.md`: fix `distance` definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's somewhat esoteric at best to define distance in terms of squared length! PR-URL: https://github.com/nodejs/node/pull/57046 Reviewed-By: Rafael Gonzaga Reviewed-By: Juan José Arboleda Reviewed-By: Chengzhong Wu Reviewed-By: Joyee Cheung --- doc/api/modules.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/modules.md b/doc/api/modules.md index 03d0a4677e9a06..d0043cc264e474 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -212,7 +212,7 @@ With the following ES Modules: ```mjs // distance.mjs -export function distance(a, b) { return (b.x - a.x) ** 2 + (b.y - a.y) ** 2; } +export function distance(a, b) { return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2); } ``` ```mjs @@ -264,7 +264,7 @@ export default class Point { // `distance` is lost to CommonJS consumers of this module, unless it's // added to `Point` as a static property. -export function distance(a, b) { return (b.x - a.x) ** 2 + (b.y - a.y) ** 2; } +export function distance(a, b) { return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2); } export { Point as 'module.exports' } ``` @@ -288,7 +288,7 @@ named exports attached to it as properties. For example with the example above, ```mjs -export function distance(a, b) { return (b.x - a.x) ** 2 + (b.y - a.y) ** 2; } +export function distance(a, b) { return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2); } export default class Point { constructor(x, y) { this.x = x; this.y = y; } From f4a82fddb117f8943be9dbccde2583848391278e Mon Sep 17 00:00:00 2001 From: Elves Vieira Date: Sun, 16 Feb 2025 19:37:42 -0100 Subject: [PATCH 34/92] benchmark: add a warmup on bench-openSync PR-URL: https://github.com/nodejs/node/pull/57051 Reviewed-By: Rafael Gonzaga Reviewed-By: Raz Luvaton --- benchmark/fs/bench-openSync.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/benchmark/fs/bench-openSync.js b/benchmark/fs/bench-openSync.js index eaa56139dcbf3c..b17c0a1e67f8a7 100644 --- a/benchmark/fs/bench-openSync.js +++ b/benchmark/fs/bench-openSync.js @@ -10,6 +10,18 @@ const bench = common.createBenchmark(main, { n: [1e5], }); +function runBench({ n, path }) { + for (let i = 0; i < n; i++) { + try { + const fd = fs.openSync(path, 'r', 0o666); + fs.closeSync(fd); + } catch { + // do nothing + } + } +} + + function main({ n, type }) { let path; @@ -24,14 +36,9 @@ function main({ n, type }) { new Error('Invalid type'); } + runBench({ n, path }); + bench.start(); - for (let i = 0; i < n; i++) { - try { - const fd = fs.openSync(path, 'r', 0o666); - fs.closeSync(fd); - } catch { - // do nothing - } - } + runBench({ n, path }); bench.end(n); } From ed5671f1bc120619c1229392d2834f75862b7107 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Sun, 16 Feb 2025 22:31:15 +0000 Subject: [PATCH 35/92] doc: update `module.builtinModules` sentence updates the sentence saying that `module.builtinModule` only contains the modules that can be loaded without `node:` (since v23.5.0 also prefix-only modules are included in the list) PR-URL: https://github.com/nodejs/node/pull/57089 Refs: https://github.com/nodejs/node/pull/56185 Reviewed-By: Antoine du Hamel Reviewed-By: James M Snell Reviewed-By: Jordan Harband Reviewed-By: Colin Ihrig --- doc/api/modules.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/api/modules.md b/doc/api/modules.md index d0043cc264e474..d0016e2f2ed3bd 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -511,9 +511,11 @@ by that name. Some built-in modules are always preferentially loaded if their identifier is passed to `require()`. For instance, `require('http')` will always -return the built-in HTTP module, even if there is a file by that name. The list -of built-in modules that can be loaded without using the `node:` prefix is exposed -in [`module.builtinModules`][], listed without the prefix. +return the built-in HTTP module, even if there is a file by that name. + +The list of all the built-in modules can be retrieved from [`module.builtinModules`][]. +The modules being all listed without the `node:` prefix, except those that mandate such +prefix (as explained in the next section). ### Built-in modules with mandatory `node:` prefix From 78d4e52a52d452769a855950e8a985f9be943aee Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Mon, 17 Feb 2025 11:50:49 +0000 Subject: [PATCH 36/92] doc: fix wrong articles used to address modules PR-URL: https://github.com/nodejs/node/pull/57090 Reviewed-By: Jacob Smith Reviewed-By: James M Snell Reviewed-By: Colin Ihrig Reviewed-By: Luigi Pinca --- doc/api/modules.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/modules.md b/doc/api/modules.md index d0016e2f2ed3bd..4d31a8bf1c3daa 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -248,7 +248,7 @@ This property is experimental and can change in the future. It should only be us by tools converting ES modules into CommonJS modules, following existing ecosystem conventions. Code authored directly in CommonJS should avoid depending on it. -When a ES Module contains both named exports and a default export, the result returned by `require()` +When an ES Module contains both named exports and a default export, the result returned by `require()` is the module namespace object, which places the default export in the `.default` property, similar to the results returned by `import()`. To customize what should be returned by `require(esm)` directly, the ES Module can export the @@ -368,7 +368,7 @@ LOAD_AS_FILE(X) 1. MAYBE_DETECT_AND_LOAD(X.js) c. If the SCOPE/package.json contains "type" field, 1. If the "type" field is "module", load X.js as an ECMAScript module. STOP. - 2. If the "type" field is "commonjs", load X.js as an CommonJS module. STOP. + 2. If the "type" field is "commonjs", load X.js as a CommonJS module. STOP. d. MAYBE_DETECT_AND_LOAD(X.js) 3. If X.json is a file, load X.json to a JavaScript Object. STOP 4. If X.node is a file, load X.node as binary addon. STOP @@ -379,7 +379,7 @@ LOAD_INDEX(X) b. If no scope was found, load X/index.js as a CommonJS module. STOP. c. If the SCOPE/package.json contains "type" field, 1. If the "type" field is "module", load X/index.js as an ECMAScript module. STOP. - 2. Else, load X/index.js as an CommonJS module. STOP. + 2. Else, load X/index.js as a CommonJS module. STOP. 2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP 3. If X/index.node is a file, load X/index.node as binary addon. STOP From 4b02fdc72fb19071bf88b5fe2f40191f7125d63e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Mon, 17 Feb 2025 14:50:40 +0100 Subject: [PATCH 37/92] doc: update Xcode version used for arm64 and pkg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/57104 Reviewed-By: Ulises Gascón Reviewed-By: Jake Yuesong Li Reviewed-By: Luigi Pinca Reviewed-By: Richard Lau --- BUILDING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILDING.md b/BUILDING.md index 0c969a527f9010..2cb7bef16dbeff 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -163,7 +163,7 @@ Binaries at are produced on: | ----------------------- | ------------------------------------------------------------------------------------------------------------- | | aix-ppc64 | AIX 7.2 TL04 on PPC64BE with GCC 12[^5] | | darwin-x64 | macOS 13, Xcode 16 with -mmacosx-version-min=11.0 | -| darwin-arm64 (and .pkg) | macOS 13 (arm64), Xcode 14 with -mmacosx-version-min=11.0 | +| darwin-arm64 (and .pkg) | macOS 13 (arm64), Xcode 16 with -mmacosx-version-min=11.0 | | linux-arm64 | RHEL 8 with gcc-toolset-12[^6] | | linux-armv7l | Cross-compiled on RHEL 9 x64 with a [custom GCC toolchain](https://github.com/rvagg/rpi-newer-crosstools)[^7] | | linux-ppc64le | RHEL 8 with gcc-toolset-12[^6] | From 5414eb48b5f436b62523272d95a54bf3f602b6d4 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sat, 8 Feb 2025 09:55:45 -0800 Subject: [PATCH 38/92] src: improve error handling in multiple files PR-URL: https://github.com/nodejs/node/pull/56962 Reviewed-By: Yagiz Nizipli Reviewed-By: Chengzhong Wu --- src/node_i18n.cc | 34 +++++++---- src/node_messaging.cc | 14 ++--- src/node_modules.cc | 39 ++++++------ src/node_options.cc | 22 ++++--- src/node_process_methods.cc | 13 ++-- src/node_process_object.cc | 85 +++++++++++++------------- src/node_report.cc | 12 ++-- src/node_report_module.cc | 32 ++++++---- src/node_serdes.cc | 119 ++++++++++++++++++------------------ src/node_trace_events.cc | 11 ++-- src/node_v8.cc | 9 ++- 11 files changed, 212 insertions(+), 178 deletions(-) diff --git a/src/node_i18n.cc b/src/node_i18n.cc index 0bcf10a0b35acc..267c24f85ed15d 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -104,14 +104,15 @@ namespace { template MaybeLocal ToBufferEndian(Environment* env, MaybeStackBuffer* buf) { - MaybeLocal ret = Buffer::New(env, buf); - if (ret.IsEmpty()) - return ret; + Local ret; + if (!Buffer::New(env, buf).ToLocal(&ret)) { + return {}; + } static_assert(sizeof(T) == 1 || sizeof(T) == 2, "Currently only one- or two-byte buffers are supported"); if constexpr (sizeof(T) > 1 && IsBigEndian()) { - SPREAD_BUFFER_ARG(ret.ToLocalChecked(), retbuf); + SPREAD_BUFFER_ARG(ret, retbuf); CHECK(nbytes::SwapBytes16(retbuf_data, retbuf_length)); } @@ -317,19 +318,22 @@ void Transcode(const FunctionCallbackInfo&args) { status = U_ILLEGAL_ARGUMENT_ERROR; } - if (result.IsEmpty()) - return args.GetReturnValue().Set(status); + Local res; + if (result.ToLocal(&res)) { + return args.GetReturnValue().Set(res); + } - return args.GetReturnValue().Set(result.ToLocalChecked()); + return args.GetReturnValue().Set(status); } void ICUErrorName(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsInt32()); UErrorCode status = static_cast(args[0].As()->Value()); - args.GetReturnValue().Set( - String::NewFromUtf8(env->isolate(), - u_errorName(status)).ToLocalChecked()); + Local res; + if (String::NewFromUtf8(env->isolate(), u_errorName(status)).ToLocal(&res)) { + args.GetReturnValue().Set(res); + } } } // anonymous namespace @@ -390,7 +394,10 @@ void ConverterObject::Create(const FunctionCallbackInfo& args) { CHECK_GE(args.Length(), 2); Utf8Value label(env->isolate(), args[0]); - int flags = args[1]->Uint32Value(env->context()).ToChecked(); + uint32_t flags; + if (!args[1]->Uint32Value(env->context()).To(&flags)) { + return; + } bool fatal = (flags & CONVERTER_FLAGS_FATAL) == CONVERTER_FLAGS_FATAL; @@ -430,7 +437,10 @@ void ConverterObject::Decode(const FunctionCallbackInfo& args) { } ArrayBufferViewContents input(args[1]); - int flags = args[2]->Uint32Value(env->context()).ToChecked(); + uint32_t flags; + if (!args[2]->Uint32Value(env->context()).To(&flags)) { + return; + } CHECK(args[3]->IsString()); Local from_encoding = args[3].As(); diff --git a/src/node_messaging.cc b/src/node_messaging.cc index 73c0c38dc7bf45..bae1387da5d897 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -1150,16 +1150,16 @@ void MessagePort::ReceiveMessage(const FunctionCallbackInfo& args) { MessagePort* port = Unwrap(args[0].As()); if (port == nullptr) { // Return 'no messages' for a closed port. - args.GetReturnValue().Set( - Environment::GetCurrent(args)->no_message_symbol()); + args.GetReturnValue().Set(env->no_message_symbol()); return; } - MaybeLocal payload = - port->ReceiveMessage(port->object()->GetCreationContextChecked(), - MessageProcessingMode::kForceReadMessages); - if (!payload.IsEmpty()) - args.GetReturnValue().Set(payload.ToLocalChecked()); + Local payload; + if (port->ReceiveMessage(port->object()->GetCreationContextChecked(), + MessageProcessingMode::kForceReadMessages) + .ToLocal(&payload)) { + args.GetReturnValue().Set(payload); + } } void MessagePort::MoveToContext(const FunctionCallbackInfo& args) { diff --git a/src/node_modules.cc b/src/node_modules.cc index 38d2c65c7f3282..e362663053526f 100644 --- a/src/node_modules.cc +++ b/src/node_modules.cc @@ -398,9 +398,10 @@ void BindingData::GetNearestParentPackageJSONType( return; } - Local value = - ToV8Value(realm->context(), package_json->type).ToLocalChecked(); - args.GetReturnValue().Set(value); + Local value; + if (ToV8Value(realm->context(), package_json->type).ToLocal(&value)) { + args.GetReturnValue().Set(value); + } } void BindingData::GetPackageScopeConfig( @@ -462,12 +463,11 @@ void BindingData::GetPackageScopeConfig( auto package_json_url_as_path = url::FileURLToPath(realm->env(), *package_json_url); CHECK(package_json_url_as_path); - return args.GetReturnValue().Set( - String::NewFromUtf8(realm->isolate(), - package_json_url_as_path->c_str(), - NewStringType::kNormal, - package_json_url_as_path->size()) - .ToLocalChecked()); + Local ret; + if (ToV8Value(realm->context(), *package_json_url_as_path, realm->isolate()) + .ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } } void FlushCompileCache(const FunctionCallbackInfo& args) { @@ -499,11 +499,13 @@ void EnableCompileCache(const FunctionCallbackInfo& args) { } Utf8Value value(isolate, args[0]); CompileCacheEnableResult result = env->EnableCompileCache(*value); - Local values[] = { - v8::Integer::New(isolate, static_cast(result.status)), - ToV8Value(context, result.message).ToLocalChecked(), - ToV8Value(context, result.cache_directory).ToLocalChecked()}; - args.GetReturnValue().Set(Array::New(isolate, &values[0], arraysize(values))); + Local values[3]; + values[0] = v8::Integer::New(isolate, static_cast(result.status)); + if (ToV8Value(context, result.message).ToLocal(&values[1]) && + ToV8Value(context, result.cache_directory).ToLocal(&values[2])) { + args.GetReturnValue().Set( + Array::New(isolate, &values[0], arraysize(values))); + } } void GetCompileCacheDir(const FunctionCallbackInfo& args) { @@ -514,9 +516,12 @@ void GetCompileCacheDir(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(v8::String::Empty(isolate)); return; } - args.GetReturnValue().Set( - ToV8Value(context, env->compile_cache_handler()->cache_dir()) - .ToLocalChecked()); + + Local ret; + if (ToV8Value(context, env->compile_cache_handler()->cache_dir()) + .ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } } void GetCompileCacheEntry(const FunctionCallbackInfo& args) { diff --git a/src/node_options.cc b/src/node_options.cc index 9ab83d3a2f9098..906031b6fcc95a 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -1364,9 +1364,11 @@ void GetCLIOptionsValues(const FunctionCallbackInfo& args) { std::string negated_name = "--no" + item.first.substr(1, item.first.size()); Local negated_value = Boolean::New(isolate, !original_value); - Local negated_name_v8 = - ToV8Value(context, negated_name).ToLocalChecked().As(); - option_names.push_back(negated_name_v8); + Local negated_name_v8; + if (!ToV8Value(context, negated_name).ToLocal(&negated_name_v8)) { + return; + } + option_names.push_back(negated_name_v8.As()); option_values.push_back(negated_value); break; } @@ -1414,9 +1416,11 @@ void GetCLIOptionsValues(const FunctionCallbackInfo& args) { UNREACHABLE(); } CHECK(!value.IsEmpty()); - Local name = - ToV8Value(context, item.first).ToLocalChecked().As(); - option_names.push_back(name); + Local name; + if (!ToV8Value(context, item.first).ToLocal(&name)) { + return; + } + option_names.push_back(name.As()); option_values.push_back(value); } @@ -1455,10 +1459,10 @@ void GetCLIOptionsInfo(const FunctionCallbackInfo& args) { const auto& option_info = item.second; auto field = option_info.field; - Local name = - ToV8Value(context, item.first).ToLocalChecked().As(); + Local name; Local help_text; - if (!ToV8Value(context, option_info.help_text).ToLocal(&help_text)) { + if (!ToV8Value(context, item.first).ToLocal(&name) || + !ToV8Value(context, option_info.help_text).ToLocal(&help_text)) { return; } constexpr size_t kInfoSize = 4; diff --git a/src/node_process_methods.cc b/src/node_process_methods.cc index c67d1ac00a972b..bb55f4fcef5ea2 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -136,14 +136,15 @@ static void Cwd(const FunctionCallbackInfo& args) { char buf[PATH_MAX_BYTES]; size_t cwd_len = sizeof(buf); int err = uv_cwd(buf, &cwd_len); - if (err) + if (err) { return env->ThrowUVException(err, "uv_cwd"); + } - Local cwd = String::NewFromUtf8(env->isolate(), - buf, - NewStringType::kNormal, - cwd_len).ToLocalChecked(); - args.GetReturnValue().Set(cwd); + Local cwd; + if (String::NewFromUtf8(env->isolate(), buf, NewStringType::kNormal, cwd_len) + .ToLocal(&cwd)) { + args.GetReturnValue().Set(cwd); + } } static void Kill(const FunctionCallbackInfo& args) { diff --git a/src/node_process_object.cc b/src/node_process_object.cc index bbbc2403b6fa6d..3f1b276d10bdd0 100644 --- a/src/node_process_object.cc +++ b/src/node_process_object.cc @@ -22,21 +22,20 @@ using v8::Isolate; using v8::Local; using v8::MaybeLocal; using v8::Name; -using v8::NewStringType; using v8::None; using v8::Object; using v8::PropertyCallbackInfo; using v8::SideEffectType; -using v8::String; using v8::Value; static void ProcessTitleGetter(Local property, const PropertyCallbackInfo& info) { std::string title = GetProcessTitle("node"); - info.GetReturnValue().Set( - String::NewFromUtf8(info.GetIsolate(), title.data(), - NewStringType::kNormal, title.size()) - .ToLocalChecked()); + Local ret; + auto isolate = info.GetIsolate(); + if (ToV8Value(isolate->GetCurrentContext(), title, isolate).ToLocal(&ret)) { + info.GetReturnValue().Set(ret); + } } static void ProcessTitleSetter(Local property, @@ -196,28 +195,34 @@ void PatchProcessObject(const FunctionCallbackInfo& args) { .FromJust()); // process.argv - process->Set(context, - FIXED_ONE_BYTE_STRING(isolate, "argv"), - ToV8Value(context, env->argv()).ToLocalChecked()).Check(); + Local val; + if (!ToV8Value(context, env->argv()).ToLocal(&val) || + !process->Set(context, FIXED_ONE_BYTE_STRING(isolate, "argv"), val) + .IsJust()) { + return; + } // process.execArgv - process->Set(context, - FIXED_ONE_BYTE_STRING(isolate, "execArgv"), - ToV8Value(context, env->exec_argv()) - .ToLocalChecked()).Check(); + if (!ToV8Value(context, env->exec_argv()).ToLocal(&val) || + !process->Set(context, FIXED_ONE_BYTE_STRING(isolate, "execArgv"), val) + .IsJust()) { + return; + } READONLY_PROPERTY(process, "pid", Integer::New(isolate, uv_os_getpid())); - CHECK(process - ->SetNativeDataProperty(context, - FIXED_ONE_BYTE_STRING(isolate, "ppid"), - GetParentProcessId, - nullptr, - Local(), - None, - SideEffectType::kHasNoSideEffect) - .FromJust()); + if (!process + ->SetNativeDataProperty(context, + FIXED_ONE_BYTE_STRING(isolate, "ppid"), + GetParentProcessId, + nullptr, + Local(), + None, + SideEffectType::kHasNoSideEffect) + .IsJust()) { + return; + } // --security-revert flags #define V(code, _, __) \ @@ -230,27 +235,25 @@ void PatchProcessObject(const FunctionCallbackInfo& args) { #undef V // process.execPath - process - ->Set(context, - FIXED_ONE_BYTE_STRING(isolate, "execPath"), - String::NewFromUtf8(isolate, - env->exec_path().c_str(), - NewStringType::kInternalized, - env->exec_path().size()) - .ToLocalChecked()) - .Check(); + if (!ToV8Value(context, env->exec_path(), isolate).ToLocal(&val) || + !process->Set(context, FIXED_ONE_BYTE_STRING(isolate, "execPath"), val) + .IsJust()) { + return; + } // process.debugPort - CHECK(process - ->SetNativeDataProperty( - context, - FIXED_ONE_BYTE_STRING(isolate, "debugPort"), - DebugPortGetter, - env->owns_process_state() ? DebugPortSetter : nullptr, - Local(), - None, - SideEffectType::kHasNoSideEffect) - .FromJust()); + if (!process + ->SetNativeDataProperty( + context, + FIXED_ONE_BYTE_STRING(isolate, "debugPort"), + DebugPortGetter, + env->owns_process_state() ? DebugPortSetter : nullptr, + Local(), + None, + SideEffectType::kHasNoSideEffect) + .IsJust()) { + return; + } // process.versions Local versions = Object::New(isolate); diff --git a/src/node_report.cc b/src/node_report.cc index 9ab66162ec32a6..c64fc1a2d5f08f 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -439,10 +439,14 @@ static Maybe ErrorToString(Isolate* isolate, } else if (!error->IsObject()) { maybe_str = error->ToString(context); } else if (error->IsObject()) { - MaybeLocal stack = error.As()->Get( - context, FIXED_ONE_BYTE_STRING(isolate, "stack")); - if (!stack.IsEmpty() && stack.ToLocalChecked()->IsString()) { - maybe_str = stack.ToLocalChecked().As(); + Local stack; + if (!error.As() + ->Get(context, FIXED_ONE_BYTE_STRING(isolate, "stack")) + .ToLocal(&stack)) { + return Nothing(); + } + if (stack->IsString()) { + maybe_str = stack.As(); } } diff --git a/src/node_report_module.cc b/src/node_report_module.cc index 7a87a53203dcc7..105e53f81385e3 100644 --- a/src/node_report_module.cc +++ b/src/node_report_module.cc @@ -44,10 +44,12 @@ void WriteReport(const FunctionCallbackInfo& info) { else error = Local(); - filename = TriggerNodeReport(env, *message, *trigger, filename, error); // Return value is the report filename - info.GetReturnValue().Set( - String::NewFromUtf8(isolate, filename.c_str()).ToLocalChecked()); + filename = TriggerNodeReport(env, *message, *trigger, filename, error); + Local ret; + if (ToV8Value(env->context(), filename, env->isolate()).ToLocal(&ret)) { + info.GetReturnValue().Set(ret); + } } // External JavaScript API for returning a report @@ -67,8 +69,10 @@ void GetReport(const FunctionCallbackInfo& info) { GetNodeReport(env, "JavaScript API", __func__, error, out); // Return value is the contents of a report as a string. - info.GetReturnValue().Set( - String::NewFromUtf8(isolate, out.str().c_str()).ToLocalChecked()); + Local ret; + if (ToV8Value(env->context(), out.str(), env->isolate()).ToLocal(&ret)) { + info.GetReturnValue().Set(ret); + } } static void GetCompact(const FunctionCallbackInfo& info) { @@ -110,8 +114,10 @@ static void GetDirectory(const FunctionCallbackInfo& info) { Mutex::ScopedLock lock(per_process::cli_options_mutex); Environment* env = Environment::GetCurrent(info); std::string directory = per_process::cli_options->report_directory; - auto result = String::NewFromUtf8(env->isolate(), directory.c_str()); - info.GetReturnValue().Set(result.ToLocalChecked()); + Local ret; + if (ToV8Value(env->context(), directory, env->isolate()).ToLocal(&ret)) { + info.GetReturnValue().Set(ret); + } } static void SetDirectory(const FunctionCallbackInfo& info) { @@ -126,8 +132,10 @@ static void GetFilename(const FunctionCallbackInfo& info) { Mutex::ScopedLock lock(per_process::cli_options_mutex); Environment* env = Environment::GetCurrent(info); std::string filename = per_process::cli_options->report_filename; - auto result = String::NewFromUtf8(env->isolate(), filename.c_str()); - info.GetReturnValue().Set(result.ToLocalChecked()); + Local ret; + if (ToV8Value(env->context(), filename, env->isolate()).ToLocal(&ret)) { + info.GetReturnValue().Set(ret); + } } static void SetFilename(const FunctionCallbackInfo& info) { @@ -141,8 +149,10 @@ static void SetFilename(const FunctionCallbackInfo& info) { static void GetSignal(const FunctionCallbackInfo& info) { Environment* env = Environment::GetCurrent(info); std::string signal = env->isolate_data()->options()->report_signal; - auto result = String::NewFromUtf8(env->isolate(), signal.c_str()); - info.GetReturnValue().Set(result.ToLocalChecked()); + Local ret; + if (ToV8Value(env->context(), signal, env->isolate()).ToLocal(&ret)) { + info.GetReturnValue().Set(ret); + } } static void SetSignal(const FunctionCallbackInfo& info) { diff --git a/src/node_serdes.cc b/src/node_serdes.cc index 7a70997bc024ef..245a524907da1a 100644 --- a/src/node_serdes.cc +++ b/src/node_serdes.cc @@ -103,67 +103,69 @@ SerializerContext::SerializerContext(Environment* env, Local wrap) void SerializerContext::ThrowDataCloneError(Local message) { Local args[1] = { message }; - Local get_data_clone_error = - object()->Get(env()->context(), - env()->get_data_clone_error_string()) - .ToLocalChecked(); + Local get_data_clone_error; + if (!object() + ->Get(env()->context(), env()->get_data_clone_error_string()) + .ToLocal(&get_data_clone_error)) { + // A superseding error will have been thrown by v8. + return; + } CHECK(get_data_clone_error->IsFunction()); - MaybeLocal error = - get_data_clone_error.As()->Call(env()->context(), - object(), - arraysize(args), - args); - - if (error.IsEmpty()) return; - - env()->isolate()->ThrowException(error.ToLocalChecked()); + Local error; + if (get_data_clone_error.As() + ->Call(env()->context(), object(), arraysize(args), args) + .ToLocal(&error)) { + env()->isolate()->ThrowException(error); + } } Maybe SerializerContext::GetSharedArrayBufferId( Isolate* isolate, Local shared_array_buffer) { Local args[1] = { shared_array_buffer }; - Local get_shared_array_buffer_id = - object()->Get(env()->context(), - env()->get_shared_array_buffer_id_string()) - .ToLocalChecked(); + Local get_shared_array_buffer_id; + if (!object() + ->Get(env()->context(), env()->get_shared_array_buffer_id_string()) + .ToLocal(&get_shared_array_buffer_id)) { + return Nothing(); + } if (!get_shared_array_buffer_id->IsFunction()) { return ValueSerializer::Delegate::GetSharedArrayBufferId( isolate, shared_array_buffer); } - MaybeLocal id = - get_shared_array_buffer_id.As()->Call(env()->context(), - object(), - arraysize(args), - args); - - if (id.IsEmpty()) return Nothing(); + Local id; + if (!get_shared_array_buffer_id.As() + ->Call(env()->context(), object(), arraysize(args), args) + .ToLocal(&id)) { + return Nothing(); + } - return id.ToLocalChecked()->Uint32Value(env()->context()); + return id->Uint32Value(env()->context()); } Maybe SerializerContext::WriteHostObject(Isolate* isolate, Local input) { - MaybeLocal ret; Local args[1] = { input }; - Local write_host_object = - object()->Get(env()->context(), - env()->write_host_object_string()).ToLocalChecked(); + Local write_host_object; + if (!object() + ->Get(env()->context(), env()->write_host_object_string()) + .ToLocal(&write_host_object)) { + return Nothing(); + } if (!write_host_object->IsFunction()) { return ValueSerializer::Delegate::WriteHostObject(isolate, input); } - ret = write_host_object.As()->Call(env()->context(), - object(), - arraysize(args), - args); - - if (ret.IsEmpty()) + Local ret; + if (!write_host_object.As() + ->Call(env()->context(), object(), arraysize(args), args) + .ToLocal(&ret)) { return Nothing(); + } return Just(true); } @@ -209,12 +211,10 @@ void SerializerContext::ReleaseBuffer(const FunctionCallbackInfo& args) { // Note: Both ValueSerializer and this Buffer::New() variant use malloc() // as the underlying allocator. std::pair ret = ctx->serializer_.Release(); - auto buf = Buffer::New(ctx->env(), - reinterpret_cast(ret.first), - ret.second); - - if (!buf.IsEmpty()) { - args.GetReturnValue().Set(buf.ToLocalChecked()); + Local buf; + if (Buffer::New(ctx->env(), reinterpret_cast(ret.first), ret.second) + .ToLocal(&buf)) { + args.GetReturnValue().Set(buf); } } @@ -295,31 +295,31 @@ DeserializerContext::DeserializerContext(Environment* env, } MaybeLocal DeserializerContext::ReadHostObject(Isolate* isolate) { - Local read_host_object = - object()->Get(env()->context(), - env()->read_host_object_string()).ToLocalChecked(); + Local read_host_object; + if (!object() + ->Get(env()->context(), env()->read_host_object_string()) + .ToLocal(&read_host_object)) { + return {}; + } if (!read_host_object->IsFunction()) { return ValueDeserializer::Delegate::ReadHostObject(isolate); } Isolate::AllowJavascriptExecutionScope allow_js(isolate); - MaybeLocal ret = - read_host_object.As()->Call(env()->context(), - object(), - 0, - nullptr); - - if (ret.IsEmpty()) - return MaybeLocal(); + Local ret; + if (!read_host_object.As() + ->Call(env()->context(), object(), 0, nullptr) + .ToLocal(&ret)) { + return {}; + } - Local return_value = ret.ToLocalChecked(); - if (!return_value->IsObject()) { + if (!ret->IsObject()) { env()->ThrowTypeError("readHostObject must return an object"); - return MaybeLocal(); + return {}; } - return return_value.As(); + return ret.As(); } void DeserializerContext::New(const FunctionCallbackInfo& args) { @@ -350,9 +350,10 @@ void DeserializerContext::ReadValue(const FunctionCallbackInfo& args) { DeserializerContext* ctx; ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This()); - MaybeLocal ret = ctx->deserializer_.ReadValue(ctx->env()->context()); - - if (!ret.IsEmpty()) args.GetReturnValue().Set(ret.ToLocalChecked()); + Local ret; + if (ctx->deserializer_.ReadValue(ctx->env()->context()).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } } void DeserializerContext::TransferArrayBuffer( diff --git a/src/node_trace_events.cc b/src/node_trace_events.cc index 9787b14352753c..ef659f1c39f7ee 100644 --- a/src/node_trace_events.cc +++ b/src/node_trace_events.cc @@ -24,7 +24,6 @@ using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::Isolate; using v8::Local; -using v8::NewStringType; using v8::Object; using v8::String; using v8::Uint8Array; @@ -107,12 +106,10 @@ void GetEnabledCategories(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); std::string categories = GetTracingAgentWriter()->agent()->GetEnabledCategories(); - if (!categories.empty()) { - args.GetReturnValue().Set( - String::NewFromUtf8(env->isolate(), - categories.c_str(), - NewStringType::kNormal, - categories.size()).ToLocalChecked()); + Local ret; + if (!categories.empty() && + ToV8Value(env->context(), categories, env->isolate()).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); } } diff --git a/src/node_v8.cc b/src/node_v8.cc index a7f0ba7973498e..3458c16ef41e7d 100644 --- a/src/node_v8.cc +++ b/src/node_v8.cc @@ -409,11 +409,10 @@ void GCProfiler::Stop(const FunctionCallbackInfo& args) { profiler->writer()->json_end(); profiler->state = GCProfiler::GCProfilerState::kStopped; auto string = profiler->out_stream()->str(); - args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(), - string.data(), - v8::NewStringType::kNormal, - string.size()) - .ToLocalChecked()); + Local ret; + if (ToV8Value(env->context(), string, env->isolate()).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } } void Initialize(Local target, From 90875ba0ca2f5bdaf9ec7dfc89870fe5a7840860 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sat, 15 Feb 2025 12:46:06 -0800 Subject: [PATCH 39/92] src: improve error handling in node_blob PR-URL: https://github.com/nodejs/node/pull/57078 Reviewed-By: Yagiz Nizipli Reviewed-By: Chengzhong Wu --- src/node_blob.cc | 63 +++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/src/node_blob.cc b/src/node_blob.cc index 8afc6d3aa0d76f..bc17a5d3c20ee7 100644 --- a/src/node_blob.cc +++ b/src/node_blob.cc @@ -30,8 +30,10 @@ using v8::HandleScope; using v8::Int32; using v8::Isolate; using v8::Local; +using v8::NewStringType; using v8::Object; using v8::ObjectTemplate; +using v8::SnapshotCreator; using v8::String; using v8::Uint32; using v8::Undefined; @@ -58,7 +60,7 @@ void Concat(const FunctionCallbackInfo& args) { std::vector views; size_t total = 0; - std::vector> buffers; + std::vector> buffers; if (FromV8Array(context, array, &buffers).IsNothing()) { return; } @@ -108,17 +110,14 @@ void BlobFromFilePath(const FunctionCallbackInfo& args) { std::vector> entries; entries.push_back(std::move(entry)); - auto blob = - Blob::Create(env, DataQueue::CreateIdempotent(std::move(entries))); - - if (blob) { - auto array = Array::New(env->isolate(), 2); - USE(array->Set(env->context(), 0, blob->object())); - USE(array->Set(env->context(), - 1, - Uint32::NewFromUnsigned(env->isolate(), blob->length()))); - - args.GetReturnValue().Set(array); + if (auto blob = + Blob::Create(env, DataQueue::CreateIdempotent(std::move(entries)))) { + Local vals[2]{ + blob->object(), + Uint32::NewFromUnsigned(env->isolate(), blob->length()), + }; + args.GetReturnValue().Set( + Array::New(env->isolate(), &vals[0], arraysize(vals))); } } } // namespace @@ -159,7 +158,7 @@ Local Blob::GetConstructorTemplate(Environment* env) { return tmpl; } -bool Blob::HasInstance(Environment* env, v8::Local object) { +bool Blob::HasInstance(Environment* env, Local object) { return GetConstructorTemplate(env)->HasInstance(object); } @@ -188,7 +187,7 @@ void Blob::New(const FunctionCallbackInfo& args) { Local array = args[0].As(); std::vector> entries(array->Length()); - std::vector> sources; + std::vector> sources; if (FromV8Array(context, array, &sources).IsNothing()) { return; } @@ -197,12 +196,16 @@ void Blob::New(const FunctionCallbackInfo& args) { for (size_t i = 0; i < count; i++) { Local entry = sources[i].Get(isolate); - const auto entryFromArrayBuffer = [isolate](v8::Local buf, - size_t byte_length, - size_t byte_offset = 0) { + auto entryFromArrayBuffer = + [isolate](Local buf, + size_t byte_length, + size_t byte_offset = + 0) mutable -> std::unique_ptr { if (buf->IsDetachable()) { std::shared_ptr store = buf->GetBackingStore(); - USE(buf->Detach(Local())); + if (buf->Detach(Local()).IsNothing()) { + return nullptr; + } return DataQueue::CreateInMemoryEntryFromBackingStore( store, byte_offset, byte_length); } @@ -227,11 +230,15 @@ void Blob::New(const FunctionCallbackInfo& args) { // ensuring appropriate spec compliance. if (entry->IsArrayBuffer()) { Local buf = entry.As(); - entries[i] = entryFromArrayBuffer(buf, buf->ByteLength()); + auto ret = entryFromArrayBuffer(buf, buf->ByteLength()); + if (!ret) return; + entries[i] = std::move(ret); } else if (entry->IsArrayBufferView()) { Local view = entry.As(); - entries[i] = entryFromArrayBuffer( + auto ret = entryFromArrayBuffer( view->Buffer(), view->ByteLength(), view->ByteOffset()); + if (!ret) return; + entries[i] = std::move(ret); } else if (Blob::HasInstance(env, entry)) { Blob* blob; ASSIGN_OR_RETURN_UNWRAP(&blob, entry); @@ -279,14 +286,14 @@ BaseObjectPtr Blob::Slice(Environment* env, size_t start, size_t end) { } Blob::Blob(Environment* env, - v8::Local obj, + Local obj, std::shared_ptr data_queue) : BaseObject(env, obj), data_queue_(data_queue) { MakeWeak(); } Blob::Reader::Reader(Environment* env, - v8::Local obj, + Local obj, BaseObjectPtr strong_ptr) : AsyncWrap(env, obj, AsyncWrap::PROVIDER_BLOBREADER), inner_(strong_ptr->data_queue_->get_reader()), @@ -294,7 +301,7 @@ Blob::Reader::Reader(Environment* env, MakeWeak(); } -bool Blob::Reader::HasInstance(Environment* env, v8::Local value) { +bool Blob::Reader::HasInstance(Environment* env, Local value) { return GetConstructorTemplate(env)->HasInstance(value); } @@ -370,7 +377,7 @@ void Blob::Reader::Pull(const FunctionCallbackInfo& args) { for (size_t n = 0; n < count; n++) total += vecs[n].len; std::shared_ptr store = - v8::ArrayBuffer::NewBackingStore(env->isolate(), total); + ArrayBuffer::NewBackingStore(env->isolate(), total); auto ptr = static_cast(store->Data()); for (size_t n = 0; n < count; n++) { std::copy(vecs[n].base, vecs[n].base + vecs[n].len, ptr); @@ -415,7 +422,7 @@ std::unique_ptr Blob::CloneForMessaging() const { return std::make_unique(data_queue_); } -void Blob::StoreDataObject(const v8::FunctionCallbackInfo& args) { +void Blob::StoreDataObject(const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); CHECK(args[0]->IsString()); // ID key @@ -468,7 +475,7 @@ void Blob::RevokeObjectURL(const FunctionCallbackInfo& args) { } } -void Blob::GetDataObject(const v8::FunctionCallbackInfo& args) { +void Blob::GetDataObject(const FunctionCallbackInfo& args) { CHECK(args[0]->IsString()); Realm* realm = Realm::GetCurrent(args); BlobBindingData* binding_data = realm->GetBindingData(); @@ -482,7 +489,7 @@ void Blob::GetDataObject(const v8::FunctionCallbackInfo& args) { Local type; if (!String::NewFromUtf8(isolate, stored.type.c_str(), - v8::NewStringType::kNormal, + NewStringType::kNormal, static_cast(stored.type.length())) .ToLocal(&type)) { return; @@ -554,7 +561,7 @@ void BlobBindingData::Deserialize(Local context, } bool BlobBindingData::PrepareForSerialization(Local context, - v8::SnapshotCreator* creator) { + SnapshotCreator* creator) { // Stored blob objects are not actually persisted. // Return true because we need to maintain the reference to the binding from // JS land. From 46df14ddcba1ecabd15e196689b0637af31430fe Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 18 Feb 2025 02:29:22 +0100 Subject: [PATCH 40/92] doc: update clang-cl on Windows building guide - Mention that when the individual components cannot be found despite already being installed, try re-installing. - Mention that with newer version of Visual Studio, ccache should be copied to `clang-cl.exe` instead. - Mention how to use clang-cl with ccache PR-URL: https://github.com/nodejs/node/pull/57087 Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli --- BUILDING.md | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/BUILDING.md b/BUILDING.md index 2cb7bef16dbeff..16a52a536cb51f 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -31,6 +31,8 @@ file a new issue. * [Building a debug build](#building-a-debug-build) * [Building an ASan build](#building-an-asan-build) * [Speeding up frequent rebuilds when developing](#speeding-up-frequent-rebuilds-when-developing) + * [ccache](#ccache) + * [Loading JS files from disk instead of embedding](#loading-js-files-from-disk-instead-of-embedding) * [Troubleshooting Unix and macOS builds](#troubleshooting-unix-and-macos-builds) * [Windows](#windows) * [Windows Prerequisites](#windows-prerequisites) @@ -540,6 +542,8 @@ make test-only #### Speeding up frequent rebuilds when developing +##### ccache + Tips: The `ccache` utility is widely used and should generally work fine. If you encounter any difficulties, consider disabling `mold` as a troubleshooting step. @@ -575,17 +579,26 @@ export CXX="ccache c++" # add to ~/.zshrc or other shell config file On Windows: -Tips: follow , and you -should notice that obj file will be bigger the normal one. +Follow , and you +should notice that obj file will be bigger than the normal one. -First, install ccache, assume ccache install to c:\ccache, copy -c:\ccache\ccache.exe to c:\ccache\cl.exe with this command +First, install ccache. Assuming the installation of ccache is in `c:\ccache` +(where you can find `ccache.exe`), copy `c:\ccache\ccache.exe` to `c:\ccache\cl.exe` +with this command. ```powershell cp c:\ccache\ccache.exe c:\ccache\cl.exe ``` -When building Node.js provide a path to your ccache via the option +With newer version of Visual Studio, it may need the copy to be `clang-cl.exe` +instead. If the output of `vcbuild.bat` suggestion missing `clang-cl.exe`, copy +it differently: + +```powershell +cp c:\ccache\ccache.exe c:\ccache\clang-cl.exe +``` + +When building Node.js, provide a path to your ccache via the option: ```powershell .\vcbuild.bat ccache c:\ccache\ @@ -594,6 +607,14 @@ When building Node.js provide a path to your ccache via the option This will allow for near-instantaneous rebuilds when switching branches back and forth that were built with cache. +To use it with ClangCL, run this instead: + +```powershell +.\vcbuild.bat clang-cl ccache c:\ccache\ +``` + +##### Loading JS files from disk instead of embedding + When modifying only the JS layer in `lib`, it is possible to externally load it without modifying the executable: @@ -678,14 +699,19 @@ Optional requirements for compiling for Windows on ARM (ARM64): * Visual C++ ATL for ARM64 * Windows 10 SDK 10.0.17763.0 or newer -Optional requirements for compiling with ClangCL: +Optional requirements for compiling with ClangCL (search for `clang` in Visual Studio +Installer's "individual component" tab): -* Visual Studio optional components +* Visual Studio individual components * C++ Clang Compiler for Windows * MSBuild support for LLVM toolset NOTE: Currently we only support compiling with Clang that comes from Visual Studio. +When building with ClangCL, if the output from `vcbuild.bat` shows that the components are not installed +even when the Visual Studio Installer shows that they are installed, try removing the components +first and then reinstalling them again. + ##### Option 2: Automated install with WinGet [WinGet configuration files](https://github.com/nodejs/node/tree/main/.configurations) From fec2d503084b50f2dfd0c97699c4913289e89280 Mon Sep 17 00:00:00 2001 From: RafaelGSS Date: Thu, 6 Feb 2025 14:26:39 -0300 Subject: [PATCH 41/92] build: add skip_apidoc_files and include QUIC PR-URL: https://github.com/nodejs/node/pull/56941 Reviewed-By: James M Snell Reviewed-By: Michael Dawson --- Makefile | 4 +++- doc/api/cli.md | 9 --------- doc/contributing/internal-api.md | 4 ++++ src/node_options.cc | 2 +- test/doctool/test-make-doc.mjs | 2 ++ test/parallel/test-cli-node-options-docs.js | 3 ++- .../test-process-env-allowed-flags-are-documented.js | 2 ++ 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index ecee2fa5d9d2a3..70d7e3cbab97bf 100644 --- a/Makefile +++ b/Makefile @@ -780,7 +780,9 @@ test-v8 test-v8-intl test-v8-benchmarks test-v8-all: endif apidoc_dirs = out/doc out/doc/api out/doc/api/assets -apidoc_sources = $(wildcard doc/api/*.md) +skip_apidoc_files = doc/api/quic.md + +apidoc_sources = $(filter-out $(skip_apidoc_files), $(wildcard doc/api/*.md)) apidocs_html = $(addprefix out/,$(apidoc_sources:.md=.html)) apidocs_json = $(addprefix out/,$(apidoc_sources:.md=.json)) diff --git a/doc/api/cli.md b/doc/api/cli.md index 2ac8cfda33be8f..973eda8f500452 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -998,14 +998,6 @@ If the ES module being `require()`'d contains top-level `await`, this flag allows Node.js to evaluate the module, try to locate the top-level awaits, and print their location to help users find them. -### `--experimental-quic` - - - -Enables the experimental `node:quic` built-in module. - ### `--experimental-require-module` + + + +* `hostname` {string} +* `callback` {Function} + * `err` {Error} + * `records` {Object\[]} + + + +Uses the DNS protocol to resolve certificate associations (`TLSA` records) for +the `hostname`. The `records` argument passed to the `callback` function is an +array of objects with these properties: + +* `certUsage` +* `selector` +* `match` +* `data` + + + +```js +{ + certUsage: 3, + selector: 1, + match: 1, + data: [ArrayBuffer] +} +``` + ## `dns.resolveTxt(hostname, callback)` + +* `hostname` {string} + +Uses the DNS protocol to resolve certificate associations (`TLSA` records) for +the `hostname`. On success, the `Promise` is resolved with an array of objects +with these properties: + +* `certUsage` +* `selector` +* `match` +* `data` + + + +```js +{ + certUsage: 3, + selector: 1, + match: 1, + data: [ArrayBuffer] +} +``` + ### `dnsPromises.resolveTxt(hostname)` + * `payload` {Object} * `lineLengths` {number\[]} @@ -1714,6 +1721,12 @@ columnNumber)` #### `sourceMap.findOrigin(lineNumber, columnNumber)` + + * `lineNumber` {number} The 1-indexed line number of the call site in the generated source * `columnNumber` {number} The 1-indexed column number diff --git a/doc/api/modules.md b/doc/api/modules.md index 7f02e868fe5b30..47c9c93e571fa1 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -1266,9 +1266,9 @@ This section was moved to * `module.findSourceMap(path)` * Class: `module.SourceMap` - * `new SourceMap(payload)` + * `new SourceMap(payload)` * `sourceMap.payload` - * `sourceMap.findEntry(lineNumber, columnNumber)` + * `sourceMap.findEntry(lineNumber, columnNumber)` [Determining module system]: packages.md#determining-module-system [ECMAScript Modules]: esm.md From 56f9fe7514726d0c028d3be8bae66894248da613 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Tue, 18 Feb 2025 22:23:01 +0100 Subject: [PATCH 48/92] src: port `defineLazyProperties` to native code This allows us to have getters not observable from JS side. PR-URL: https://github.com/nodejs/node/pull/57081 Reviewed-By: James M Snell Reviewed-By: Chengzhong Wu Reviewed-By: Joyee Cheung --- .../bootstrap/web/exposed-wildcard.js | 14 +--- lib/internal/util.js | 41 +----------- src/node_messaging.cc | 36 ++++++++++ src/node_util.cc | 66 +++++++++++++++++++ test/common/wpt.js | 27 -------- test/wpt/test-domexception.js | 2 - 6 files changed, 105 insertions(+), 81 deletions(-) diff --git a/lib/internal/bootstrap/web/exposed-wildcard.js b/lib/internal/bootstrap/web/exposed-wildcard.js index d8f451edb01205..4b3d6be0701ec4 100644 --- a/lib/internal/bootstrap/web/exposed-wildcard.js +++ b/lib/internal/bootstrap/web/exposed-wildcard.js @@ -12,12 +12,11 @@ const { const { exposeInterface, - lazyDOMExceptionClass, exposeLazyInterfaces, - exposeGetterAndSetter, exposeNamespace, } = require('internal/util'); const config = internalBinding('config'); +const { exposeLazyDOMExceptionProperty } = internalBinding('messaging'); // https://console.spec.whatwg.org/#console-namespace exposeNamespace(globalThis, 'console', @@ -28,16 +27,7 @@ const { URL, URLSearchParams } = require('internal/url'); exposeInterface(globalThis, 'URL', URL); // https://url.spec.whatwg.org/#urlsearchparams exposeInterface(globalThis, 'URLSearchParams', URLSearchParams); -exposeGetterAndSetter(globalThis, - 'DOMException', - () => { - const DOMException = lazyDOMExceptionClass(); - exposeInterface(globalThis, 'DOMException', DOMException); - return DOMException; - }, - (value) => { - exposeInterface(globalThis, 'DOMException', value); - }); +exposeLazyDOMExceptionProperty(globalThis); // https://dom.spec.whatwg.org/#interface-abortcontroller // Lazy ones. diff --git a/lib/internal/util.js b/lib/internal/util.js index f0f2d3117d3504..de7e2aea355e8f 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -55,6 +55,7 @@ const { const { signals } = internalBinding('constants').os; const { guessHandleType: _guessHandleType, + defineLazyProperties, privateSymbols: { arrow_message_private_symbol, decorated_private_symbol, @@ -601,46 +602,6 @@ function exposeGetterAndSetter(target, name, getter, setter = undefined) { }); } -function defineLazyProperties(target, id, keys, enumerable = true) { - const descriptors = { __proto__: null }; - let mod; - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - let lazyLoadedValue; - function set(value) { - ObjectDefineProperty(target, key, { - __proto__: null, - writable: true, - value, - }); - } - ObjectDefineProperty(set, 'name', { - __proto__: null, - value: `set ${key}`, - }); - function get() { - mod ??= require(id); - if (lazyLoadedValue === undefined) { - lazyLoadedValue = mod[key]; - set(lazyLoadedValue); - } - return lazyLoadedValue; - } - ObjectDefineProperty(get, 'name', { - __proto__: null, - value: `get ${key}`, - }); - descriptors[key] = { - __proto__: null, - configurable: true, - enumerable, - get, - set, - }; - } - ObjectDefineProperties(target, descriptors); -} - function defineReplaceableLazyAttribute(target, id, keys, writable = true, check) { let mod; for (let i = 0; i < keys.length; i++) { diff --git a/src/node_messaging.cc b/src/node_messaging.cc index bae1387da5d897..9c854df7a3d1f3 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -1645,6 +1645,36 @@ static void BroadcastChannel(const FunctionCallbackInfo& args) { } } +static void ExposeLazyDOMExceptionPropertyGetter( + Local name, const v8::PropertyCallbackInfo& info) { + auto context = info.GetIsolate()->GetCurrentContext(); + Local domexception; + if (!GetDOMException(context).ToLocal(&domexception)) { + // V8 will have scheduled an error to be thrown. + return; + } + info.GetReturnValue().Set(domexception); +} +static void ExposeLazyDOMExceptionProperty( + const FunctionCallbackInfo& args) { + CHECK_GE(args.Length(), 1); + CHECK(args[0]->IsObject()); + + Isolate* isolate = args.GetIsolate(); + auto target = args[0].As(); + + if (target + ->SetLazyDataProperty(isolate->GetCurrentContext(), + FIXED_ONE_BYTE_STRING(isolate, "DOMException"), + ExposeLazyDOMExceptionPropertyGetter, + Local(), + v8::DontEnum) + .IsNothing()) { + // V8 will have scheduled an error to be thrown. + return; + } +} + static void CreatePerIsolateProperties(IsolateData* isolate_data, Local target) { Isolate* isolate = isolate_data->isolate(); @@ -1669,6 +1699,10 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data, isolate_data->message_port_constructor_string(), GetMessagePortConstructorTemplate(isolate_data)); + SetMethod(isolate, + target, + "exposeLazyDOMExceptionProperty", + ExposeLazyDOMExceptionProperty); // These are not methods on the MessagePort prototype, because // the browser equivalents do not provide them. SetMethod(isolate, target, "stopMessagePort", MessagePort::Stop); @@ -1714,6 +1748,8 @@ static void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(MessagePort::MoveToContext); registry->Register(SetDeserializerCreateObjectFunction); registry->Register(StructuredClone); + registry->Register(ExposeLazyDOMExceptionProperty); + registry->Register(ExposeLazyDOMExceptionPropertyGetter); } } // anonymous namespace diff --git a/src/node_util.cc b/src/node_util.cc index c855201d8938fd..844cdb0f440577 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -348,6 +348,69 @@ static void IsInsideNodeModules(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(result); } +static void DefineLazyPropertiesGetter( + Local name, const v8::PropertyCallbackInfo& info) { + Realm* realm = Realm::GetCurrent(info); + Isolate* isolate = realm->isolate(); + auto context = isolate->GetCurrentContext(); + Local arg = info.Data(); + Local require_result; + if (!realm->builtin_module_require() + ->Call(context, Null(isolate), 1, &arg) + .ToLocal(&require_result)) { + // V8 will have scheduled an error to be thrown. + return; + } + Local ret; + if (!require_result.As()->Get(context, name).ToLocal(&ret)) { + // V8 will have scheduled an error to be thrown. + return; + } + info.GetReturnValue().Set(ret); +} +static void DefineLazyProperties(const FunctionCallbackInfo& args) { + // target: object, id: string, keys: string[][, enumerable = true] + CHECK_GE(args.Length(), 3); + // target: Object where to define the lazy properties. + CHECK(args[0]->IsObject()); + // id: Internal module to lazy-load where the API to expose are implemented. + CHECK(args[1]->IsString()); + // keys: Keys to map from `require(id)` and `target`. + CHECK(args[2]->IsArray()); + // enumerable: Whether the property should be enumerable. + CHECK(args.Length() == 3 || args[3]->IsBoolean()); + + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + auto context = isolate->GetCurrentContext(); + + auto target = args[0].As(); + Local id = args[1]; + v8::PropertyAttribute attribute = + args.Length() == 3 || args[3]->IsTrue() ? v8::None : v8::DontEnum; + + const Local keys = args[2].As(); + size_t length = keys->Length(); + for (size_t i = 0; i < length; i++) { + Local key; + if (!keys->Get(context, i).ToLocal(&key)) { + // V8 will have scheduled an error to be thrown. + return; + } + CHECK(key->IsString()); + if (target + ->SetLazyDataProperty(context, + key.As(), + DefineLazyPropertiesGetter, + id, + attribute) + .IsNothing()) { + // V8 will have scheduled an error to be thrown. + return; + }; + } +} + void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(GetPromiseDetails); registry->Register(GetProxyDetails); @@ -364,6 +427,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(fast_guess_handle_type_.GetTypeInfo()); registry->Register(ParseEnv); registry->Register(IsInsideNodeModules); + registry->Register(DefineLazyProperties); + registry->Register(DefineLazyPropertiesGetter); } void Initialize(Local target, @@ -448,6 +513,7 @@ void Initialize(Local target, } SetMethod(context, target, "isInsideNodeModules", IsInsideNodeModules); + SetMethod(context, target, "defineLazyProperties", DefineLazyProperties); SetMethodNoSideEffect( context, target, "getPromiseDetails", GetPromiseDetails); SetMethodNoSideEffect(context, target, "getProxyDetails", GetProxyDetails); diff --git a/test/common/wpt.js b/test/common/wpt.js index 3ead9cb23b9c46..74b182e5b84296 100644 --- a/test/common/wpt.js +++ b/test/common/wpt.js @@ -595,7 +595,6 @@ class WPTRunner { switch (name) { case 'Window': { this.globalThisInitScripts.push('globalThis.Window = Object.getPrototypeOf(globalThis).constructor;'); - this.loadLazyGlobals(); break; } @@ -609,32 +608,6 @@ class WPTRunner { } } - loadLazyGlobals() { - const lazyProperties = [ - 'DOMException', - 'Performance', 'PerformanceEntry', 'PerformanceMark', 'PerformanceMeasure', - 'PerformanceObserver', 'PerformanceObserverEntryList', 'PerformanceResourceTiming', - 'Blob', 'atob', 'btoa', - 'MessageChannel', 'MessagePort', 'MessageEvent', - 'EventTarget', 'Event', - 'AbortController', 'AbortSignal', - 'performance', - 'TransformStream', 'TransformStreamDefaultController', - 'WritableStream', 'WritableStreamDefaultController', 'WritableStreamDefaultWriter', - 'ReadableStream', 'ReadableStreamDefaultReader', - 'ReadableStreamBYOBReader', 'ReadableStreamBYOBRequest', - 'ReadableByteStreamController', 'ReadableStreamDefaultController', - 'ByteLengthQueuingStrategy', 'CountQueuingStrategy', - 'TextEncoder', 'TextDecoder', 'TextEncoderStream', 'TextDecoderStream', - 'CompressionStream', 'DecompressionStream', - ]; - if (Boolean(process.versions.openssl) && !process.env.NODE_SKIP_CRYPTO) { - lazyProperties.push('crypto', 'Crypto', 'CryptoKey', 'SubtleCrypto'); - } - const script = lazyProperties.map((name) => `globalThis.${name};`).join('\n'); - this.globalThisInitScripts.push(script); - } - // TODO(joyeecheung): work with the upstream to port more tests in .html // to .js. async runJsTests() { diff --git a/test/wpt/test-domexception.js b/test/wpt/test-domexception.js index 7900d1ca9a79c6..8fa93bc59bdf44 100644 --- a/test/wpt/test-domexception.js +++ b/test/wpt/test-domexception.js @@ -4,6 +4,4 @@ const { WPTRunner } = require('../common/wpt'); const runner = new WPTRunner('webidl/ecmascript-binding/es-exceptions'); -runner.loadLazyGlobals(); - runner.runJsTests(); From e3127b89a20c9f997d505e79029911e6adffd9f6 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 18 Feb 2025 13:40:13 -0800 Subject: [PATCH 49/92] src: replace NewFromUtf8 with OneByteString where appropriate PR-URL: https://github.com/nodejs/node/pull/57096 Reviewed-By: Yagiz Nizipli Reviewed-By: Antoine du Hamel Reviewed-By: Matteo Collina Reviewed-By: Chengzhong Wu Reviewed-By: Luigi Pinca --- src/cares_wrap.cc | 6 ++---- src/node_binding.cc | 3 +-- src/node_buffer.cc | 17 +++++------------ src/node_i18n.cc | 5 +---- src/spawn_sync.cc | 8 ++++---- 5 files changed, 13 insertions(+), 26 deletions(-) diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index f43193c3ce0905..e58f164ca5124a 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -1660,11 +1660,9 @@ void CanonicalizeIP(const FunctionCallbackInfo& args) { uv_inet_pton(af = AF_INET6, *ip, result) != 0) return; - char canonical_ip[INET6_ADDRSTRLEN]; + char canonical_ip[INET6_ADDRSTRLEN]{}; CHECK_EQ(0, uv_inet_ntop(af, result, canonical_ip, sizeof(canonical_ip))); - Local val = String::NewFromUtf8(isolate, canonical_ip) - .ToLocalChecked(); - args.GetReturnValue().Set(val); + args.GetReturnValue().Set(OneByteString(isolate, canonical_ip)); } void ConvertIpv6StringToBuffer(const FunctionCallbackInfo& args) { diff --git a/src/node_binding.cc b/src/node_binding.cc index e93f3312cc6425..16231e4f9acf81 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -685,8 +685,7 @@ void GetLinkedBinding(const FunctionCallbackInfo& args) { Local module = Object::New(env->isolate()); Local exports = Object::New(env->isolate()); - Local exports_prop = - String::NewFromUtf8Literal(env->isolate(), "exports"); + Local exports_prop = env->exports_string(); module->Set(env->context(), exports_prop, exports).Check(); if (mod->nm_context_register_func != nullptr) { diff --git a/src/node_buffer.cc b/src/node_buffer.cc index e8eae4eff51144..8d58dc000e3f94 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -72,7 +72,6 @@ using v8::Just; using v8::Local; using v8::Maybe; using v8::MaybeLocal; -using v8::NewStringType; using v8::Nothing; using v8::Number; using v8::Object; @@ -1307,12 +1306,9 @@ static void Btoa(const FunctionCallbackInfo& args) { written = simdutf::binary_to_base64(*stack_buf, out_len, buffer.out()); } - auto value = - String::NewFromOneByte(env->isolate(), - reinterpret_cast(buffer.out()), - NewStringType::kNormal, - written) - .ToLocalChecked(); + auto value = OneByteString( + env->isolate(), reinterpret_cast(buffer.out()), written); + return args.GetReturnValue().Set(value); } @@ -1362,12 +1358,9 @@ static void Atob(const FunctionCallbackInfo& args) { } if (result.error == simdutf::error_code::SUCCESS) { - auto value = - String::NewFromOneByte(env->isolate(), + auto value = OneByteString(env->isolate(), reinterpret_cast(buffer.out()), - NewStringType::kNormal, - result.count) - .ToLocalChecked(); + result.count); return args.GetReturnValue().Set(value); } diff --git a/src/node_i18n.cc b/src/node_i18n.cc index 267c24f85ed15d..dbda7d2f653b38 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -330,10 +330,7 @@ void ICUErrorName(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsInt32()); UErrorCode status = static_cast(args[0].As()->Value()); - Local res; - if (String::NewFromUtf8(env->isolate(), u_errorName(status)).ToLocal(&res)) { - args.GetReturnValue().Set(res); - } + args.GetReturnValue().Set(OneByteString(env->isolate(), u_errorName(status))); } } // anonymous namespace diff --git a/src/spawn_sync.cc b/src/spawn_sync.cc index 6d8d5da686d446..df2fb4bd3adf8b 100644 --- a/src/spawn_sync.cc +++ b/src/spawn_sync.cc @@ -707,10 +707,10 @@ Local SyncProcessRunner::BuildResultObject() { } if (term_signal_ > 0) - js_result->Set(context, env()->signal_string(), - String::NewFromUtf8(env()->isolate(), - signo_string(term_signal_)) - .ToLocalChecked()) + js_result + ->Set(context, + env()->signal_string(), + OneByteString(env()->isolate(), signo_string(term_signal_))) .Check(); else js_result->Set(context, env()->signal_string(), From 2929fc6449e2ea7bc346d0958e80a797372ffa0f Mon Sep 17 00:00:00 2001 From: Carlos Espa <43477095+Ceres6@users.noreply.github.com> Date: Wed, 19 Feb 2025 07:34:08 +0100 Subject: [PATCH 50/92] test_runner: allow special characters in snapshot keys Fixes: https://github.com/nodejs/node/issues/56836 PR-URL: https://github.com/nodejs/node/pull/57017 Reviewed-By: Pietro Marchini Reviewed-By: Colin Ihrig Reviewed-By: Chemi Atlow --- lib/internal/test_runner/snapshot.js | 9 ++++++- .../snapshots/special-character.js | 19 ++++++++++++++ test/parallel/test-runner-snapshot-tests.js | 26 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/test-runner/snapshots/special-character.js diff --git a/lib/internal/test_runner/snapshot.js b/lib/internal/test_runner/snapshot.js index 642e84b2f13b33..ff3e3c8d0e6ee6 100644 --- a/lib/internal/test_runner/snapshot.js +++ b/lib/internal/test_runner/snapshot.js @@ -77,7 +77,7 @@ class SnapshotFile { } setSnapshot(id, value) { - this.snapshots[templateEscape(id)] = value; + this.snapshots[escapeSnapshotKey(id)] = value; } nextId(name) { @@ -290,6 +290,13 @@ function validateFunctionArray(fns, name) { } } +function escapeSnapshotKey(str) { + let result = JSONStringify(str, null, 2).slice(1, -1); + result = StringPrototypeReplaceAll(result, '`', '\\`'); + result = StringPrototypeReplaceAll(result, '${', '\\${'); + return result; +} + function templateEscape(str) { let result = String(str); result = StringPrototypeReplaceAll(result, '\\', '\\\\'); diff --git a/test/fixtures/test-runner/snapshots/special-character.js b/test/fixtures/test-runner/snapshots/special-character.js new file mode 100644 index 00000000000000..1c515fb0ba4598 --- /dev/null +++ b/test/fixtures/test-runner/snapshots/special-character.js @@ -0,0 +1,19 @@ +'use strict'; +const { snapshot, test } = require('node:test'); +const { basename, join } = require('node:path'); + +snapshot.setResolveSnapshotPath((testFile) => { + return join(process.cwd(), `${basename(testFile)}.snapshot`); +}); + +test('\r', (t) => { + t.assert.snapshot({ key: 'value' }); +}); + +test(String.fromCharCode(55296), (t) => { + t.assert.snapshot({ key: 'value' }); +}); + +test(String.fromCharCode(57343), (t) => { + t.assert.snapshot({ key: 'value' }); +}); diff --git a/test/parallel/test-runner-snapshot-tests.js b/test/parallel/test-runner-snapshot-tests.js index 011b1321e1e8c3..db0c4963613a80 100644 --- a/test/parallel/test-runner-snapshot-tests.js +++ b/test/parallel/test-runner-snapshot-tests.js @@ -158,6 +158,8 @@ suite('SnapshotManager', () => { file.setSnapshot('foo`${x}` 1', 'test'); t.assert.strictEqual(file.getSnapshot('foo\\`\\${x}\\` 1'), 'test'); + file.setSnapshot('\r 1', 'test'); + t.assert.strictEqual(file.getSnapshot('\\r 1'), 'test'); }); test('throws if snapshot file cannot be resolved', (t) => { @@ -340,6 +342,30 @@ test('t.assert.snapshot()', async (t) => { }); }); +test('special characters are allowed', async (t) => { + const fixture = fixtures.path( + 'test-runner', 'snapshots', 'special-character.js' + ); + + await common.spawnPromisified( + process.execPath, + ['--test-update-snapshots', fixture], + { cwd: tmpdir.path }, + ); + + const child = await common.spawnPromisified( + process.execPath, + [fixture], + { cwd: tmpdir.path }, + ); + + t.assert.strictEqual(child.code, 0); + t.assert.strictEqual(child.signal, null); + t.assert.match(child.stdout, /tests 3/); + t.assert.match(child.stdout, /pass 3/); + t.assert.match(child.stdout, /fail 0/); +}); + test('snapshots from multiple files (isolation=none)', async (t) => { tmpdir.refresh(); From b384baa073272fff72958d72732c55934d069f9b Mon Sep 17 00:00:00 2001 From: 1ilsang <1ilsang@naver.com> Date: Thu, 20 Feb 2025 01:11:03 +0900 Subject: [PATCH 51/92] build: print 'Formatting Markdown...' for long task markdown formatting PR-URL: https://github.com/nodejs/node/pull/57108 Reviewed-By: Yagiz Nizipli Reviewed-By: Jake Yuesong Li Reviewed-By: James M Snell --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 70d7e3cbab97bf..405e15b54cc2c2 100644 --- a/Makefile +++ b/Makefile @@ -1408,6 +1408,7 @@ lint-md: lint-js-doc | tools/.mdlintstamp ## Lint the markdown documents maintai run-format-md = tools/lint-md/lint-md.mjs --format $(LINT_MD_FILES) .PHONY: format-md format-md: tools/lint-md/node_modules/remark-parse/package.json ## Format the markdown documents maintained by us in the codebase. + $(info Formatting Markdown...) @$(call available-node,$(run-format-md)) From e26d4841d1252f7122d8e2b031ed3b7e1457e336 Mon Sep 17 00:00:00 2001 From: Carlos Espa <43477095+Ceres6@users.noreply.github.com> Date: Thu, 20 Feb 2025 11:11:01 +0100 Subject: [PATCH 52/92] cli: allow --cpu-prof* in NODE_OPTIONS Fixes: https://github.com/nodejs/node/issues/56944 PR-URL: https://github.com/nodejs/node/pull/57018 Reviewed-By: Joyee Cheung Reviewed-By: Stephen Belanger Reviewed-By: Chengzhong Wu --- doc/api/cli.md | 4 ++++ src/node_options.cc | 12 ++++++++---- .../test-process-env-allowed-flags-are-documented.js | 4 ++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index 973eda8f500452..b88342c3e6a799 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -3146,6 +3146,10 @@ one is included in the list below. * `--allow-wasi` * `--allow-worker` * `--conditions`, `-C` +* `--cpu-prof-dir` +* `--cpu-prof-interval` +* `--cpu-prof-name` +* `--cpu-prof` * `--diagnostic-dir` * `--disable-proto` * `--disable-sigusr1` diff --git a/src/node_options.cc b/src/node_options.cc index 7bd6f2f33fd77d..8b0d7b0fdfdcbe 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -614,19 +614,23 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "Start the V8 CPU profiler on start up, and write the CPU profile " "to disk before exit. If --cpu-prof-dir is not specified, write " "the profile to the current working directory.", - &EnvironmentOptions::cpu_prof); + &EnvironmentOptions::cpu_prof, + kAllowedInEnvvar); AddOption("--cpu-prof-name", "specified file name of the V8 CPU profile generated with " "--cpu-prof", - &EnvironmentOptions::cpu_prof_name); + &EnvironmentOptions::cpu_prof_name, + kAllowedInEnvvar); AddOption("--cpu-prof-interval", "specified sampling interval in microseconds for the V8 CPU " "profile generated with --cpu-prof. (default: 1000)", - &EnvironmentOptions::cpu_prof_interval); + &EnvironmentOptions::cpu_prof_interval, + kAllowedInEnvvar); AddOption("--cpu-prof-dir", "Directory where the V8 profiles generated by --cpu-prof will be " "placed. Does not affect --prof.", - &EnvironmentOptions::cpu_prof_dir); + &EnvironmentOptions::cpu_prof_dir, + kAllowedInEnvvar); AddOption("--experimental-network-inspection", "experimental network inspection support", &EnvironmentOptions::experimental_network_inspection); diff --git a/test/parallel/test-process-env-allowed-flags-are-documented.js b/test/parallel/test-process-env-allowed-flags-are-documented.js index 0d6d06be47c4bf..4706cc018220ea 100644 --- a/test/parallel/test-process-env-allowed-flags-are-documented.js +++ b/test/parallel/test-process-env-allowed-flags-are-documented.js @@ -93,6 +93,10 @@ const difference = (setA, setB) => { // Refs: https://github.com/nodejs/node/pull/54259#issuecomment-2308256647 if (!process.features.inspector) { [ + '--cpu-prof-dir', + '--cpu-prof-interval', + '--cpu-prof-name', + '--cpu-prof', '--heap-prof-dir', '--heap-prof-interval', '--heap-prof-name', From 73a8514305cecdab25ed37a33e5f93a4db847830 Mon Sep 17 00:00:00 2001 From: 1ilsang <1ilsang@naver.com> Date: Thu, 20 Feb 2025 12:39:56 +0900 Subject: [PATCH 53/92] tools: consolidate 'introduced_in' check for docs PR-URL: https://github.com/nodejs/node/pull/57109 Reviewed-By: Jake Yuesong Li Reviewed-By: Marco Ippolito Reviewed-By: Luigi Pinca --- doc/api/typescript.md | 2 ++ tools/lint-md/lint-md.mjs | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/doc/api/typescript.md b/doc/api/typescript.md index e398310d896a8b..26e4a2a5ff2147 100644 --- a/doc/api/typescript.md +++ b/doc/api/typescript.md @@ -10,6 +10,8 @@ changes: description: Added `--experimental-transform-types` flag. --> + + > Stability: 1.1 - Active development ## Enabling diff --git a/tools/lint-md/lint-md.mjs b/tools/lint-md/lint-md.mjs index fd5576d404c905..f8e29f313644f1 100644 --- a/tools/lint-md/lint-md.mjs +++ b/tools/lint-md/lint-md.mjs @@ -34,6 +34,14 @@ paths.forEach(async (path) => { const fileContents = file.toString(); const result = await linter.process(file); const isDifferent = fileContents !== result.toString(); + + if (path.startsWith('doc/api/')) { + if (!fileContents.includes('introduced_in')) { + console.error(`${path} is missing an 'introduced_in' version. Please add one.`); + process.exitCode = 1; + } + } + if (format) { if (isDifferent) { fs.writeFileSync(path, result.toString()); From fdc8aeb8a0f10d9455e033fac91d250397058343 Mon Sep 17 00:00:00 2001 From: 1ilsang <1ilsang@naver.com> Date: Thu, 20 Feb 2025 12:40:16 +0900 Subject: [PATCH 54/92] doc: fix 'introduced_in' version in typescript module PR-URL: https://github.com/nodejs/node/pull/57109 Reviewed-By: Jake Yuesong Li Reviewed-By: Marco Ippolito Reviewed-By: Luigi Pinca --- doc/api/typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/typescript.md b/doc/api/typescript.md index 26e4a2a5ff2147..57bbc838de0125 100644 --- a/doc/api/typescript.md +++ b/doc/api/typescript.md @@ -10,7 +10,7 @@ changes: description: Added `--experimental-transform-types` flag. --> - + > Stability: 1.1 - Active development From 7dd326e3a76edc70ed8c97718b8a3903ae5caec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Thu, 20 Feb 2025 13:29:04 +0100 Subject: [PATCH 55/92] src: move instead of copy shared pointer in node_blob Resolves a new warning reported by Coverity. PR-URL: https://github.com/nodejs/node/pull/57120 Reviewed-By: Marco Ippolito Reviewed-By: Yagiz Nizipli Reviewed-By: James M Snell Reviewed-By: Colin Ihrig --- src/node_blob.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node_blob.cc b/src/node_blob.cc index bc17a5d3c20ee7..de38873d0b1c07 100644 --- a/src/node_blob.cc +++ b/src/node_blob.cc @@ -207,7 +207,7 @@ void Blob::New(const FunctionCallbackInfo& args) { return nullptr; } return DataQueue::CreateInMemoryEntryFromBackingStore( - store, byte_offset, byte_length); + std::move(store), byte_offset, byte_length); } // If the ArrayBuffer is not detachable, we will copy from it instead. @@ -216,7 +216,7 @@ void Blob::New(const FunctionCallbackInfo& args) { uint8_t* ptr = static_cast(buf->Data()) + byte_offset; std::copy(ptr, ptr + byte_length, static_cast(store->Data())); return DataQueue::CreateInMemoryEntryFromBackingStore( - store, 0, byte_length); + std::move(store), 0, byte_length); }; // Every entry should be either an ArrayBuffer, ArrayBufferView, or Blob. From e0a91f631bfe02c3e36f77ed01daef206bf14594 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 19 Feb 2025 13:49:30 -0800 Subject: [PATCH 56/92] src: gate all quic behind disabled-by-default compile flag Due to https://github.com/quictls/openssl/commit/93ae85bd193cfe8d19cfbfb75159a8c8ae003b8d it is clear that we will need to revert back to using OpenSSL's official releases. This means we will be forced to re-implement at least part of the underlying QUIC implementation to use different crypto APIs. For that reason, this PR disables building any of the QUIC support by default and introduces a new compile time flag. PR-URL: https://github.com/nodejs/node/pull/57142 Reviewed-By: Yagiz Nizipli Reviewed-By: Jordan Harband Reviewed-By: Marco Ippolito Reviewed-By: Michael Dawson Reviewed-By: Chengzhong Wu Reviewed-By: Richard Lau Reviewed-By: Trivikram Kamat Reviewed-By: Stephen Belanger Reviewed-By: Matteo Collina Reviewed-By: Joyee Cheung --- configure.py | 17 +++++++------- node.gyp | 6 ++++- src/node_options.cc | 6 +++++ src/node_options.h | 2 ++ test/common/index.js | 2 +- ...rocess-env-allowed-flags-are-documented.js | 4 +++- tools/getsharedopensslhasquic.py | 23 ------------------- 7 files changed, 26 insertions(+), 34 deletions(-) delete mode 100644 tools/getsharedopensslhasquic.py diff --git a/configure.py b/configure.py index 8baf075384671e..f2dac8aab26555 100755 --- a/configure.py +++ b/configure.py @@ -38,7 +38,6 @@ sys.path.insert(0, 'tools') import getmoduleversion import getnapibuildversion -import getsharedopensslhasquic from gyp_node import run_gyp from utils import SearchFiles @@ -847,6 +846,12 @@ # End dummy list. +parser.add_argument('--with-quic', + action='store_true', + dest='quic', + default=None, + help='build with QUIC support') + parser.add_argument('--without-ssl', action='store_true', dest='without_ssl', @@ -1743,6 +1748,7 @@ def configure_openssl(o): variables['node_shared_ngtcp2'] = b(options.shared_ngtcp2) variables['node_shared_nghttp3'] = b(options.shared_nghttp3) variables['openssl_is_fips'] = b(options.openssl_is_fips) + variables['node_quic'] = b(options.quic) variables['node_fipsinstall'] = b(False) if options.openssl_no_asm: @@ -1804,13 +1810,8 @@ def without_ssl_error(option): if options.openssl_is_fips and not options.shared_openssl: variables['node_fipsinstall'] = b(True) - if options.shared_openssl: - has_quic = getsharedopensslhasquic.get_has_quic(options.__dict__['shared_openssl_includes']) - else: - has_quic = getsharedopensslhasquic.get_has_quic('deps/openssl/openssl/include') - - variables['openssl_quic'] = b(has_quic) - if has_quic: + variables['openssl_quic'] = b(options.quic) + if options.quic: o['defines'] += ['NODE_OPENSSL_HAS_QUIC'] configure_library('openssl', o) diff --git a/node.gyp b/node.gyp index ba1f50d7541cca..42a1160ad4af7b 100644 --- a/node.gyp +++ b/node.gyp @@ -927,12 +927,16 @@ [ 'node_use_openssl=="true"', { 'sources': [ '<@(node_crypto_sources)', - '<@(node_quic_sources)', ], 'dependencies': [ 'deps/ncrypto/ncrypto.gyp:ncrypto', ], }], + [ 'node_quic=="true"', { + 'sources': [ + '<@(node_quic_sources)', + ], + }], [ 'OS in "linux freebsd mac solaris" and ' 'target_arch=="x64" and ' 'node_target_type=="executable"', { diff --git a/src/node_options.cc b/src/node_options.cc index 8b0d7b0fdfdcbe..1d54b1ddf0987e 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -443,7 +443,13 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { true); AddOption("--experimental-quic", "" /* undocumented until its development */, +#ifdef NODE_OPENSSL_HAS_QUIC &EnvironmentOptions::experimental_quic, +#else + // Option is a no-op if the NODE_OPENSSL_HAS_QUIC + // compile flag is not enabled + NoOp{}, +#endif kAllowedInEnvvar); AddOption("--experimental-webstorage", "experimental Web Storage API", diff --git a/src/node_options.h b/src/node_options.h index 3c4d2f853f2b35..6b8e812321c1ae 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -127,7 +127,9 @@ class EnvironmentOptions : public Options { bool experimental_websocket = true; bool experimental_sqlite = true; bool experimental_webstorage = false; +#ifdef NODE_OPENSSL_HAS_QUIC bool experimental_quic = false; +#endif std::string localstorage_file; bool experimental_global_navigator = true; bool experimental_global_web_crypto = true; diff --git a/test/common/index.js b/test/common/index.js index e27603cda8741e..cf920fcc49d258 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -54,7 +54,7 @@ const noop = () => {}; const hasCrypto = Boolean(process.versions.openssl) && !process.env.NODE_SKIP_CRYPTO; -const hasQuic = hasCrypto && !!process.config.variables.openssl_quic; +const hasQuic = hasCrypto && !!process.config.variables.node_quic; function parseTestFlags(filename = process.argv[1]) { // The copyright notice is relatively big and the flags could come afterwards. diff --git a/test/parallel/test-process-env-allowed-flags-are-documented.js b/test/parallel/test-process-env-allowed-flags-are-documented.js index 4706cc018220ea..1522e5b865cbcd 100644 --- a/test/parallel/test-process-env-allowed-flags-are-documented.js +++ b/test/parallel/test-process-env-allowed-flags-are-documented.js @@ -130,7 +130,9 @@ assert(undocumented.delete('--no-verify-base-objects')); assert(undocumented.delete('--trace-promises')); assert(undocumented.delete('--no-trace-promises')); assert(undocumented.delete('--experimental-quic')); -assert(undocumented.delete('--no-experimental-quic')); +if (common.hasQuic) { + assert(undocumented.delete('--no-experimental-quic')); +} // Remove negated versions of the flags. for (const flag of undocumented) { diff --git a/tools/getsharedopensslhasquic.py b/tools/getsharedopensslhasquic.py deleted file mode 100644 index f4349285e2f125..00000000000000 --- a/tools/getsharedopensslhasquic.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import print_function -import os -import re - -def get_has_quic(include_path): - if include_path: - openssl_quic_h = os.path.join( - include_path, - 'openssl', - 'quic.h') - - try: - f = open(openssl_quic_h) - except OSError: - return False - - regex = r'^#\s*define OPENSSL_INFO_QUIC' - - for line in f: - if (re.match(regex, line)): - return True - - return False From d62299b0215822c94c1f10c274d8fb7ae0adb125 Mon Sep 17 00:00:00 2001 From: Colin Ihrig Date: Thu, 20 Feb 2025 10:44:26 -0500 Subject: [PATCH 57/92] meta: add CODEOWNERS for SQLite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/57147 Reviewed-By: James M Snell Reviewed-By: Michaël Zasso Reviewed-By: Marco Ippolito --- .github/CODEOWNERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f3f14061b00288..ba85d7acf4e149 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -200,3 +200,11 @@ /lib/url.js @nodejs/url /src/node_url.* @nodejs/url /test/fixtures/wpt/url @nodejs/url + +# SQLite +/deps/sqlite/ @nodejs/sqlite +/doc/api/sqlite.md @nodejs/sqlite +/src/node_sqlite.* @nodejs/sqlite +/test/parallel/test-sqlite* @nodejs/sqlite +/test/sqlite/ @nodejs/sqlite +/tools/dep_updaters/update-sqlite.sh @nodejs/sqlite From 90a4de02b65f8842f60fa7dbc6265de6f65641ca Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 18 Feb 2025 22:31:55 +0100 Subject: [PATCH 58/92] src: do not format single string argument for THROW_ERR_* If the macros are used as ERR_*(isolate, message) or THROW_ERR_*(isolate, message) with a single string argument, do run formatter on the message, and allow the caller to pass in a message directly with characters that would otherwise need escaping if used as format string unconditionally. PR-URL: https://github.com/nodejs/node/pull/57126 Refs: https://github.com/fisker/prettier-issue-17139 Reviewed-By: James M Snell Reviewed-By: Antoine du Hamel Reviewed-By: Chengzhong Wu --- src/node_errors.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/node_errors.h b/src/node_errors.h index 801b19fc91810b..4d2667fb4dfdce 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -115,11 +115,21 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details); V(ERR_WORKER_INIT_FAILED, Error) \ V(ERR_PROTO_ACCESS, Error) +// If the macros are used as ERR_*(isolate, message) or +// THROW_ERR_*(isolate, message) with a single string argument, do run +// formatter on the message, and allow the caller to pass in a message +// directly with characters that would otherwise need escaping if used +// as format string unconditionally. #define V(code, type) \ template \ inline v8::Local code( \ v8::Isolate* isolate, const char* format, Args&&... args) { \ - std::string message = SPrintF(format, std::forward(args)...); \ + std::string message; \ + if (sizeof...(Args) == 0) { \ + message = format; \ + } else { \ + message = SPrintF(format, std::forward(args)...); \ + } \ v8::Local js_code = FIXED_ONE_BYTE_STRING(isolate, #code); \ v8::Local js_msg = \ v8::String::NewFromUtf8(isolate, \ From ad3c5720271b947f356d42bdb32da470b331c4be Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 17 Feb 2025 19:44:50 +0100 Subject: [PATCH 59/92] module: improve error message from asynchronicity in require(esm) - Improve the error message that shows up when there is a race from doing require(esm) and import(esm) at the same time. - Improve error message of ERR_REQUIRE_ASYNC_MODULE by showing parent and target file names, if available. Drive-by: split the require(tla) tests since we are modifying the tests already. PR-URL: https://github.com/nodejs/node/pull/57126 Refs: https://github.com/fisker/prettier-issue-17139 Reviewed-By: James M Snell Reviewed-By: Antoine du Hamel Reviewed-By: Chengzhong Wu --- lib/internal/errors.js | 13 +++- lib/internal/modules/esm/loader.js | 25 ++++++-- lib/internal/modules/esm/module_job.js | 9 ++- src/module_wrap.cc | 4 +- src/node_errors.h | 26 ++++++-- test/common/index.js | 12 ++++ .../test-require-module-tla-execution.js | 26 ++++++++ .../test-require-module-tla-nested.js | 15 +++++ ...test-require-module-tla-print-execution.js | 29 +++++++++ .../test-require-module-tla-rejected.js | 15 +++++ .../test-require-module-tla-resolved.js | 15 +++++ .../test-require-module-tla-unresolved.js | 15 +++++ test/es-module/test-require-module-tla.js | 63 ------------------- 13 files changed, 189 insertions(+), 78 deletions(-) create mode 100644 test/es-module/test-require-module-tla-execution.js create mode 100644 test/es-module/test-require-module-tla-nested.js create mode 100644 test/es-module/test-require-module-tla-print-execution.js create mode 100644 test/es-module/test-require-module-tla-rejected.js create mode 100644 test/es-module/test-require-module-tla-resolved.js create mode 100644 test/es-module/test-require-module-tla-unresolved.js delete mode 100644 test/es-module/test-require-module-tla.js diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 47d4fbf677e5eb..a0a39de45b2a59 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1656,9 +1656,18 @@ E('ERR_QUIC_ENDPOINT_CLOSED', 'QUIC endpoint closed: %s (%d)', Error); E('ERR_QUIC_OPEN_STREAM_FAILED', 'Failed to open QUIC stream', Error); E('ERR_QUIC_TRANSPORT_ERROR', 'A QUIC transport error occurred. %d [%s]', Error); E('ERR_QUIC_VERSION_NEGOTIATION_ERROR', 'The QUIC session requires version negotiation', Error); -E('ERR_REQUIRE_ASYNC_MODULE', 'require() cannot be used on an ESM ' + +E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parentFilename) { + let message = 'require() cannot be used on an ESM ' + 'graph with top-level await. Use import() instead. To see where the' + - ' top-level await comes from, use --experimental-print-required-tla.', Error); + ' top-level await comes from, use --experimental-print-required-tla.'; + if (parentFilename) { + message += `\n From ${parentFilename} `; + } + if (filename) { + message += `\n Requiring ${filename} `; + } + return message; +}, Error); E('ERR_REQUIRE_CYCLE_MODULE', '%s', Error); E('ERR_REQUIRE_ESM', function(filename, hasEsmSyntax, parentPath = null, packageJsonPath = null) { diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index a3b437ade87c75..3823f73597aba2 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -338,20 +338,37 @@ class ModuleLoader { // TODO(joyeecheung): ensure that imported synchronous graphs are evaluated // synchronously so that any previously imported synchronous graph is already // evaluated at this point. + // TODO(joyeecheung): add something similar to CJS loader's requireStack to help + // debugging the the problematic links in the graph for import. if (job !== undefined) { mod[kRequiredModuleSymbol] = job.module; + const parentFilename = urlToFilename(parent?.filename); + // TODO(node:55782): this race may stop to happen when the ESM resolution and loading become synchronous. + if (!job.module) { + let message = `Cannot require() ES Module ${filename} because it is not yet fully loaded. `; + message += 'This may be caused by a race condition if the module is simultaneously dynamically '; + message += 'import()-ed via Promise.all(). Try await-ing the import() sequentially in a loop instead.'; + if (parentFilename) { + message += ` (from ${parentFilename})`; + } + assert(job.module, message); + } if (job.module.async) { - throw new ERR_REQUIRE_ASYNC_MODULE(); + throw new ERR_REQUIRE_ASYNC_MODULE(filename, parentFilename); } + // job.module may be undefined if it's asynchronously loaded. Which means + // there is likely a cycle. if (job.module.getStatus() !== kEvaluated) { - const parentFilename = urlToFilename(parent?.filename); let message = `Cannot require() ES Module ${filename} in a cycle.`; if (parentFilename) { message += ` (from ${parentFilename})`; } + message += 'A cycle involving require(esm) is disallowed to maintain '; + message += 'invariants madated by the ECMAScript specification'; + message += 'Try making at least part of the dependency in the graph lazily loaded.'; throw new ERR_REQUIRE_CYCLE_MODULE(message); } - return { wrap: job.module, namespace: job.module.getNamespaceSync() }; + return { wrap: job.module, namespace: job.module.getNamespaceSync(filename, parentFilename) }; } // TODO(joyeecheung): refactor this so that we pre-parse in C++ and hit the // cache here, or use a carrier object to carry the compiled module script @@ -363,7 +380,7 @@ class ModuleLoader { job = new ModuleJobSync(this, url, kEmptyObject, wrap, isMain, inspectBrk); this.loadCache.set(url, kImplicitTypeAttribute, job); mod[kRequiredModuleSymbol] = job.module; - return { wrap: job.module, namespace: job.runSync().namespace }; + return { wrap: job.module, namespace: job.runSync(parent).namespace }; } /** diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 846a336d27547e..9a8800c78407a4 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -36,6 +36,7 @@ const assert = require('internal/assert'); const resolvedPromise = PromiseResolve(); const { setHasStartedUserESMExecution, + urlToFilename, } = require('internal/modules/helpers'); const { getOptionValue } = require('internal/options'); const noop = FunctionPrototype; @@ -380,7 +381,7 @@ class ModuleJobSync extends ModuleJobBase { `Status = ${status}`); } - runSync() { + runSync(parent) { // TODO(joyeecheung): add the error decoration logic from the async instantiate. this.module.async = this.module.instantiateSync(); // If --experimental-print-required-tla is true, proceeds to evaluation even @@ -389,11 +390,13 @@ class ModuleJobSync extends ModuleJobBase { // TODO(joyeecheung): track the asynchroniticy using v8::Module::HasTopLevelAwait() // and we'll be able to throw right after compilation of the modules, using acron // to find and print the TLA. + const parentFilename = urlToFilename(parent?.filename); + const filename = urlToFilename(this.url); if (this.module.async && !getOptionValue('--experimental-print-required-tla')) { - throw new ERR_REQUIRE_ASYNC_MODULE(); + throw new ERR_REQUIRE_ASYNC_MODULE(filename, parentFilename); } setHasStartedUserESMExecution(); - const namespace = this.module.evaluateSync(); + const namespace = this.module.evaluateSync(filename, parentFilename); return { __proto__: null, module: this.module, namespace }; } } diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 649ec428e2dd6f..8ba5dc01228fef 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -710,7 +710,7 @@ void ModuleWrap::EvaluateSync(const FunctionCallbackInfo& args) { FPrintF(stderr, "%s\n", reason); } } - THROW_ERR_REQUIRE_ASYNC_MODULE(env); + THROW_ERR_REQUIRE_ASYNC_MODULE(env, args[0], args[1]); return; } @@ -740,7 +740,7 @@ void ModuleWrap::GetNamespaceSync(const FunctionCallbackInfo& args) { } if (module->IsGraphAsync()) { - return THROW_ERR_REQUIRE_ASYNC_MODULE(realm->env()); + return THROW_ERR_REQUIRE_ASYNC_MODULE(realm->env(), args[0], args[1]); } Local result = module->GetModuleNamespace(); args.GetReturnValue().Set(result); diff --git a/src/node_errors.h b/src/node_errors.h index 4d2667fb4dfdce..7fa0dbe3c707ad 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -215,10 +215,6 @@ ERRORS_WITH_CODE(V) "creating Workers") \ V(ERR_NON_CONTEXT_AWARE_DISABLED, \ "Loading non context-aware native addons has been disabled") \ - V(ERR_REQUIRE_ASYNC_MODULE, \ - "require() cannot be used on an ESM graph with top-level await. Use " \ - "import() instead. To see where the top-level await comes from, use " \ - "--experimental-print-required-tla.") \ V(ERR_SCRIPT_EXECUTION_INTERRUPTED, \ "Script execution was interrupted by `SIGINT`") \ V(ERR_TLS_PSK_SET_IDENTITY_HINT_FAILED, "Failed to set PSK identity hint") \ @@ -248,6 +244,28 @@ inline void THROW_ERR_SCRIPT_EXECUTION_TIMEOUT(Environment* env, THROW_ERR_SCRIPT_EXECUTION_TIMEOUT(env, message.str().c_str()); } +inline void THROW_ERR_REQUIRE_ASYNC_MODULE( + Environment* env, + v8::Local filename, + v8::Local parent_filename) { + static constexpr const char* prefix = + "require() cannot be used on an ESM graph with top-level await. Use " + "import() instead. To see where the top-level await comes from, use " + "--experimental-print-required-tla."; + std::string message = prefix; + if (!parent_filename.IsEmpty() && parent_filename->IsString()) { + Utf8Value utf8(env->isolate(), parent_filename); + message += "\n From "; + message += utf8.out(); + } + if (!filename.IsEmpty() && filename->IsString()) { + Utf8Value utf8(env->isolate(), filename); + message += "\n Requiring "; + message += +utf8.out(); + } + THROW_ERR_REQUIRE_ASYNC_MODULE(env, message.c_str()); +} + inline v8::Local ERR_BUFFER_TOO_LARGE(v8::Isolate* isolate) { char message[128]; snprintf(message, diff --git a/test/common/index.js b/test/common/index.js index cf920fcc49d258..b8f5416c625e4c 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -855,6 +855,17 @@ function expectRequiredModule(mod, expectation, checkESModule = true) { assert.deepStrictEqual(clone, { ...expectation }); } +function expectRequiredTLAError(err) { + const message = /require\(\) cannot be used on an ESM graph with top-level await/; + if (typeof err === 'string') { + assert.match(err, /ERR_REQUIRE_ASYNC_MODULE/); + assert.match(err, message); + } else { + assert.strictEqual(err.code, 'ERR_REQUIRE_ASYNC_MODULE'); + assert.match(err.message, message); + } +} + const common = { allowGlobals, buildType, @@ -864,6 +875,7 @@ const common = { escapePOSIXShell, expectsError, expectRequiredModule, + expectRequiredTLAError, expectWarning, getArrayBufferViews, getBufferSources, diff --git a/test/es-module/test-require-module-tla-execution.js b/test/es-module/test-require-module-tla-execution.js new file mode 100644 index 00000000000000..bdd222d1f8fa0b --- /dev/null +++ b/test/es-module/test-require-module-tla-execution.js @@ -0,0 +1,26 @@ +'use strict'; + +// Tests that require(esm) with top-level-await throws before execution starts +// if --experimental-print-required-tla is not enabled. + +const common = require('../common'); +const assert = require('assert'); +const { spawnSyncAndExit } = require('../common/child_process'); +const fixtures = require('../common/fixtures'); + +{ + spawnSyncAndExit(process.execPath, [ + fixtures.path('es-modules/tla/require-execution.js'), + ], { + signal: null, + status: 1, + stderr(output) { + assert.doesNotMatch(output, /I am executed/); + common.expectRequiredTLAError(output); + assert.match(output, /From .*require-execution\.js/); + assert.match(output, /Requiring .*execution\.mjs/); + return true; + }, + stdout: '' + }); +} diff --git a/test/es-module/test-require-module-tla-nested.js b/test/es-module/test-require-module-tla-nested.js new file mode 100644 index 00000000000000..583cd7cd0c95db --- /dev/null +++ b/test/es-module/test-require-module-tla-nested.js @@ -0,0 +1,15 @@ +'use strict'; + +// Tests that require(esm) throws for top-level-await in inner graphs. + +const common = require('../common'); +const assert = require('assert'); + +assert.throws(() => { + require('../fixtures/es-modules/tla/parent.mjs'); +}, (err) => { + common.expectRequiredTLAError(err); + assert.match(err.message, /From .*test-require-module-tla-nested\.js/); + assert.match(err.message, /Requiring .*parent\.mjs/); + return true; +}); diff --git a/test/es-module/test-require-module-tla-print-execution.js b/test/es-module/test-require-module-tla-print-execution.js new file mode 100644 index 00000000000000..40992ae32e0905 --- /dev/null +++ b/test/es-module/test-require-module-tla-print-execution.js @@ -0,0 +1,29 @@ +'use strict'; + +// Tests that require(esm) with top-level-await throws after execution +// if --experimental-print-required-tla is enabled. + +const common = require('../common'); +const assert = require('assert'); +const { spawnSyncAndExit } = require('../common/child_process'); +const fixtures = require('../common/fixtures'); + +{ + spawnSyncAndExit(process.execPath, [ + '--experimental-print-required-tla', + fixtures.path('es-modules/tla/require-execution.js'), + ], { + signal: null, + status: 1, + stderr(output) { + assert.match(output, /I am executed/); + common.expectRequiredTLAError(output); + assert.match(output, /Error: unexpected top-level await at.*execution\.mjs:3/); + assert.match(output, /await Promise\.resolve\('hi'\)/); + assert.match(output, /From .*require-execution\.js/); + assert.match(output, /Requiring .*execution\.mjs/); + return true; + }, + stdout: '' + }); +} diff --git a/test/es-module/test-require-module-tla-rejected.js b/test/es-module/test-require-module-tla-rejected.js new file mode 100644 index 00000000000000..0c1f8de2c307f6 --- /dev/null +++ b/test/es-module/test-require-module-tla-rejected.js @@ -0,0 +1,15 @@ +'use strict'; + +// Tests that require(esm) throws for rejected top-level await. + +const common = require('../common'); +const assert = require('assert'); + +assert.throws(() => { + require('../fixtures/es-modules/tla/rejected.mjs'); +}, (err) => { + common.expectRequiredTLAError(err); + assert.match(err.message, /From .*test-require-module-tla-rejected\.js/); + assert.match(err.message, /Requiring .*rejected\.mjs/); + return true; +}); diff --git a/test/es-module/test-require-module-tla-resolved.js b/test/es-module/test-require-module-tla-resolved.js new file mode 100644 index 00000000000000..f35bb68b7dc180 --- /dev/null +++ b/test/es-module/test-require-module-tla-resolved.js @@ -0,0 +1,15 @@ +'use strict'; + +// Tests that require(esm) throws for resolved top-level-await. + +const common = require('../common'); +const assert = require('assert'); + +assert.throws(() => { + require('../fixtures/es-modules/tla/resolved.mjs'); +}, (err) => { + common.expectRequiredTLAError(err); + assert.match(err.message, /From .*test-require-module-tla-resolved\.js/); + assert.match(err.message, /Requiring .*resolved\.mjs/); + return true; +}); diff --git a/test/es-module/test-require-module-tla-unresolved.js b/test/es-module/test-require-module-tla-unresolved.js new file mode 100644 index 00000000000000..35cf12c446129b --- /dev/null +++ b/test/es-module/test-require-module-tla-unresolved.js @@ -0,0 +1,15 @@ +'use strict'; + +// Tests that require(esm) throws for unresolved top-level-await. + +const common = require('../common'); +const assert = require('assert'); + +assert.throws(() => { + require('../fixtures/es-modules/tla/unresolved.mjs'); +}, (err) => { + common.expectRequiredTLAError(err); + assert.match(err.message, /From .*test-require-module-tla-unresolved\.js/); + assert.match(err.message, /Requiring .*unresolved\.mjs/); + return true; +}); diff --git a/test/es-module/test-require-module-tla.js b/test/es-module/test-require-module-tla.js deleted file mode 100644 index 9b38b1cab3fcb5..00000000000000 --- a/test/es-module/test-require-module-tla.js +++ /dev/null @@ -1,63 +0,0 @@ -// Flags: --experimental-require-module -'use strict'; - -require('../common'); -const assert = require('assert'); -const { spawnSyncAndExit } = require('../common/child_process'); -const fixtures = require('../common/fixtures'); - -const message = /require\(\) cannot be used on an ESM graph with top-level await/; -const code = 'ERR_REQUIRE_ASYNC_MODULE'; - -assert.throws(() => { - require('../fixtures/es-modules/tla/rejected.mjs'); -}, { message, code }); - -assert.throws(() => { - require('../fixtures/es-modules/tla/unresolved.mjs'); -}, { message, code }); - - -assert.throws(() => { - require('../fixtures/es-modules/tla/resolved.mjs'); -}, { message, code }); - -// Test TLA in inner graphs. -assert.throws(() => { - require('../fixtures/es-modules/tla/parent.mjs'); -}, { message, code }); - -{ - spawnSyncAndExit(process.execPath, [ - '--experimental-require-module', - fixtures.path('es-modules/tla/require-execution.js'), - ], { - signal: null, - status: 1, - stderr(output) { - assert.doesNotMatch(output, /I am executed/); - assert.match(output, message); - return true; - }, - stdout: '' - }); -} - -{ - spawnSyncAndExit(process.execPath, [ - '--experimental-require-module', - '--experimental-print-required-tla', - fixtures.path('es-modules/tla/require-execution.js'), - ], { - signal: null, - status: 1, - stderr(output) { - assert.match(output, /I am executed/); - assert.match(output, /Error: unexpected top-level await at.*execution\.mjs:3/); - assert.match(output, /await Promise\.resolve\('hi'\)/); - assert.match(output, message); - return true; - }, - stdout: '' - }); -} From 84210214275b47ab1380f8dffdf1033984a57756 Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Thu, 20 Feb 2025 13:10:41 -0500 Subject: [PATCH 60/92] deps: update ada to 3.1.0 PR-URL: https://github.com/nodejs/node/pull/57083 Reviewed-By: Yagiz Nizipli Reviewed-By: Rafael Gonzaga Reviewed-By: James M Snell --- deps/ada/ada.cpp | 2 +- deps/ada/ada.h | 123 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 115 insertions(+), 10 deletions(-) diff --git a/deps/ada/ada.cpp b/deps/ada/ada.cpp index c9fa03b1a7b8f0..a60102854930be 100644 --- a/deps/ada/ada.cpp +++ b/deps/ada/ada.cpp @@ -1,4 +1,4 @@ -/* auto-generated on 2025-01-30 18:48:55 -0500. Do not edit! */ +/* auto-generated on 2025-02-11 09:47:50 -0500. Do not edit! */ /* begin file src/ada.cpp */ #include "ada.h" /* begin file src/checkers.cpp */ diff --git a/deps/ada/ada.h b/deps/ada/ada.h index c32e3cfaa03201..c997f0981ce36b 100644 --- a/deps/ada/ada.h +++ b/deps/ada/ada.h @@ -1,4 +1,4 @@ -/* auto-generated on 2025-01-30 18:48:55 -0500. Do not edit! */ +/* auto-generated on 2025-02-11 09:47:50 -0500. Do not edit! */ /* begin file include/ada.h */ /** * @file ada.h @@ -1219,6 +1219,7 @@ constexpr ada::scheme::type get_scheme_type(std::string_view scheme) noexcept; #endif // ADA_SCHEME_H /* end file include/ada/scheme.h */ +#include #include namespace ada { @@ -1352,6 +1353,7 @@ struct url_base { #endif /* end file include/ada/url_base.h */ +#include #include #include @@ -4118,6 +4120,9 @@ void swap(expected &lhs, #ifndef ADA_URL_PATTERN_REGEX_H #define ADA_URL_PATTERN_REGEX_H +#include +#include + #ifdef ADA_USE_UNSAFE_STD_REGEX_PROVIDER #include #endif // ADA_USE_UNSAFE_STD_REGEX_PROVIDER @@ -4216,7 +4221,9 @@ concept url_pattern_encoding_callback = requires(F f, std::string_view sv) { // either a string or a URLPatternInit struct. If a string is given, // it will be parsed to create a URLPatternInit. The URLPatternInit // API is defined as part of the URLPattern specification. +// All provided strings must be valid UTF-8. struct url_pattern_init { + // All strings must be valid UTF-8. // @see https://urlpattern.spec.whatwg.org/#process-a-urlpatterninit static tl::expected process( url_pattern_init init, std::string_view type, @@ -4276,15 +4283,23 @@ struct url_pattern_init { #endif // ADA_TESTING bool operator==(const url_pattern_init&) const; - + // If present, must be valid UTF-8. std::optional protocol{}; + // If present, must be valid UTF-8. std::optional username{}; + // If present, must be valid UTF-8. std::optional password{}; + // If present, must be valid UTF-8. std::optional hostname{}; + // If present, must be valid UTF-8. std::optional port{}; + // If present, must be valid UTF-8. std::optional pathname{}; + // If present, must be valid UTF-8. std::optional search{}; + // If present, must be valid UTF-8. std::optional hash{}; + // If present, must be valid UTF-8. std::optional base_url{}; }; } // namespace ada @@ -4366,6 +4381,7 @@ tl::expected, errors> parse_url_pattern_impl( #ifndef ADA_IMPLEMENTATION_H #define ADA_IMPLEMENTATION_H +#include #include #include @@ -4379,6 +4395,7 @@ tl::expected, errors> parse_url_pattern_impl( #include #include +#include #include #include @@ -5040,7 +5057,9 @@ std::string href_from_file(std::string_view path); #endif // ADA_IMPLEMENTATION_H /* end file include/ada/implementation.h */ +#include #include +#include #include #include #include @@ -5219,6 +5238,8 @@ class url_pattern_component { bool has_regexp_groups = false; }; +// A URLPattern input can be either a string or a URLPatternInit object. +// If it is a string, it must be a valid UTF-8 string. using url_pattern_input = std::variant; // A struct providing the URLPattern matching results for all @@ -5251,12 +5272,16 @@ struct url_pattern_options { // defined in https://wicg.github.io/urlpattern. // More information about the URL Pattern syntax can be found at // https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API +// +// We require all strings to be valid UTF-8: it is the user's responsibility +// to ensure that the provided strings are valid UTF-8. template class url_pattern { public: url_pattern() = default; /** + * If non-null, base_url must pointer at a valid UTF-8 string. * @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-exec */ result> exec( @@ -5264,6 +5289,7 @@ class url_pattern { const std::string_view* base_url = nullptr); /** + * If non-null, base_url must pointer at a valid UTF-8 string. * @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-test */ result test(const url_pattern_input& input, @@ -5725,6 +5751,7 @@ std::string generate_segment_wildcard_regexp( #endif /* end file include/ada/url_pattern_helpers.h */ +#include #include #include @@ -6257,6 +6284,7 @@ ada_warn_unused std::string to_string(ada::state s); #include +#include #include /** @@ -6870,6 +6898,7 @@ namespace ada { #ifndef ADA_URL_AGGREGATOR_H #define ADA_URL_AGGREGATOR_H +#include #include #include #include @@ -7255,6 +7284,7 @@ ada_really_inline size_t percent_encode_index(const std::string_view input, /* end file include/ada/unicode-inl.h */ #include +#include #include namespace ada { @@ -8406,6 +8436,8 @@ using url_search_params_entries_iter = url_search_params_iter_type::ENTRIES>; /** + * We require all strings to be valid UTF-8. It is the user's responsibility to + * ensure that the provided strings are valid UTF-8. * @see https://url.spec.whatwg.org/#interface-urlsearchparams */ struct url_search_params { @@ -8428,6 +8460,7 @@ struct url_search_params { [[nodiscard]] inline size_t size() const noexcept; /** + * Both key and value must be valid UTF-8. * @see https://url.spec.whatwg.org/#dom-urlsearchparams-append */ inline void append(std::string_view key, std::string_view value); @@ -8455,6 +8488,7 @@ struct url_search_params { inline bool has(std::string_view key, std::string_view value) noexcept; /** + * Both key and value must be valid UTF-8. * @see https://url.spec.whatwg.org/#dom-urlsearchparams-set */ inline void set(std::string_view key, std::string_view value); @@ -8518,6 +8552,7 @@ struct url_search_params { std::vector params{}; /** + * The init parameter must be valid UTF-8. * @see https://url.spec.whatwg.org/#concept-urlencoded-parser */ void initialize(std::string_view init); @@ -8724,10 +8759,80 @@ inline void url_search_params::remove(const std::string_view key, } inline void url_search_params::sort() { - std::ranges::stable_sort( - params, [](const key_value_pair &lhs, const key_value_pair &rhs) { - return lhs.first < rhs.first; - }); + // We rely on the fact that the content is valid UTF-8. + std::ranges::stable_sort(params, [](const key_value_pair &lhs, + const key_value_pair &rhs) { + size_t i = 0, j = 0; + uint32_t low_surrogate1 = 0, low_surrogate2 = 0; + while ((i < lhs.first.size() || low_surrogate1 != 0) && + (j < rhs.first.size() || low_surrogate2 != 0)) { + uint32_t codePoint1 = 0, codePoint2 = 0; + + if (low_surrogate1 != 0) { + codePoint1 = low_surrogate1; + low_surrogate1 = 0; + } else { + uint8_t c1 = uint8_t(lhs.first[i]); + if (c1 <= 0x7F) { + codePoint1 = c1; + i++; + } else if (c1 <= 0xDF) { + codePoint1 = ((c1 & 0x1F) << 6) | (uint8_t(lhs.first[i + 1]) & 0x3F); + i += 2; + } else if (c1 <= 0xEF) { + codePoint1 = ((c1 & 0x0F) << 12) | + ((uint8_t(lhs.first[i + 1]) & 0x3F) << 6) | + (uint8_t(lhs.first[i + 2]) & 0x3F); + i += 3; + } else { + codePoint1 = ((c1 & 0x07) << 18) | + ((uint8_t(lhs.first[i + 1]) & 0x3F) << 12) | + ((uint8_t(lhs.first[i + 2]) & 0x3F) << 6) | + (uint8_t(lhs.first[i + 3]) & 0x3F); + i += 4; + + codePoint1 -= 0x10000; + uint16_t high_surrogate = uint16_t(0xD800 + (codePoint1 >> 10)); + low_surrogate1 = uint16_t(0xDC00 + (codePoint1 & 0x3FF)); + codePoint1 = high_surrogate; + } + } + + if (low_surrogate2 != 0) { + codePoint2 = low_surrogate2; + low_surrogate2 = 0; + } else { + uint8_t c2 = uint8_t(rhs.first[j]); + if (c2 <= 0x7F) { + codePoint2 = c2; + j++; + } else if (c2 <= 0xDF) { + codePoint2 = ((c2 & 0x1F) << 6) | (uint8_t(rhs.first[j + 1]) & 0x3F); + j += 2; + } else if (c2 <= 0xEF) { + codePoint2 = ((c2 & 0x0F) << 12) | + ((uint8_t(rhs.first[j + 1]) & 0x3F) << 6) | + (uint8_t(rhs.first[j + 2]) & 0x3F); + j += 3; + } else { + codePoint2 = ((c2 & 0x07) << 18) | + ((uint8_t(rhs.first[j + 1]) & 0x3F) << 12) | + ((uint8_t(rhs.first[j + 2]) & 0x3F) << 6) | + (uint8_t(rhs.first[j + 3]) & 0x3F); + j += 4; + codePoint2 -= 0x10000; + uint16_t high_surrogate = uint16_t(0xD800 + (codePoint2 >> 10)); + low_surrogate2 = uint16_t(0xDC00 + (codePoint2 & 0x3FF)); + codePoint2 = high_surrogate; + } + } + + if (codePoint1 != codePoint2) { + return (codePoint1 < codePoint2); + } + } + return (j < rhs.first.size() || low_surrogate2 != 0); + }); } inline url_search_params_keys_iter url_search_params::get_keys() { @@ -10330,14 +10435,14 @@ constructor_string_parser::parse(std::string_view input) { #ifndef ADA_ADA_VERSION_H #define ADA_ADA_VERSION_H -#define ADA_VERSION "3.0.1" +#define ADA_VERSION "3.1.0" namespace ada { enum { ADA_VERSION_MAJOR = 3, - ADA_VERSION_MINOR = 0, - ADA_VERSION_REVISION = 1, + ADA_VERSION_MINOR = 1, + ADA_VERSION_REVISION = 0, }; } // namespace ada From ccf496cff9c456b4b588ba83297fa27d55720331 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Fri, 21 Feb 2025 12:39:44 +0100 Subject: [PATCH 61/92] test: improve error output of test-http2-client-promisify-connect-error PR-URL: https://github.com/nodejs/node/pull/57135 Reviewed-By: Colin Ihrig Reviewed-By: Luigi Pinca --- .../test-http2-client-promisify-connect-error.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/test/parallel/test-http2-client-promisify-connect-error.js b/test/parallel/test-http2-client-promisify-connect-error.js index 44ff4a281812f6..b3c8bc980aac67 100644 --- a/test/parallel/test-http2-client-promisify-connect-error.js +++ b/test/parallel/test-http2-client-promisify-connect-error.js @@ -12,13 +12,10 @@ const server = http2.createServer(); server.listen(0, common.mustCall(() => { const port = server.address().port; - server.close(() => { + server.close(common.mustCall(() => { const connect = util.promisify(http2.connect); - connect(`http://localhost:${port}`) - .then(common.mustNotCall('Promise should not be resolved')) - .catch(common.mustCall((err) => { - assert(err instanceof Error); - assert.strictEqual(err.code, 'ECONNREFUSED'); - })); - }); + assert.rejects(connect(`http://localhost:${port}`), { + code: 'ECONNREFUSED' + }).then(common.mustCall()); + })); })); From c6ddfa52fb9f9a9cf37dee12104aa2ee27ffb00f Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Fri, 21 Feb 2025 15:14:02 +0100 Subject: [PATCH 62/92] process: add threadCpuUsage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/56467 Reviewed-By: Matteo Collina Reviewed-By: Juan José Arboleda Reviewed-By: James M Snell --- doc/api/process.md | 19 ++++ lib/internal/bootstrap/node.js | 1 + lib/internal/process/per_thread.js | 47 ++++++++++ src/node_process_methods.cc | 25 +++++ ...test-process-threadCpuUsage-main-thread.js | 87 ++++++++++++++++++ ...t-process-threadCpuUsage-worker-threads.js | 91 +++++++++++++++++++ typings/globals.d.ts | 2 + typings/internalBinding/process.d.ts | 15 +++ 8 files changed, 287 insertions(+) create mode 100644 test/parallel/test-process-threadCpuUsage-main-thread.js create mode 100644 test/parallel/test-process-threadCpuUsage-worker-threads.js create mode 100644 typings/internalBinding/process.d.ts diff --git a/doc/api/process.md b/doc/api/process.md index 5d3a1f2d214c83..a58d7cc03cfff0 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -4204,6 +4204,25 @@ Thrown: [DeprecationWarning: test] { name: 'DeprecationWarning' } ``` +## `process.threadCpuUsage([previousValue])` + + + +* `previousValue` {Object} A previous return value from calling + `process.cpuUsage()` +* Returns: {Object} + * `user` {integer} + * `system` {integer} + +The `process.threadCpuUsage()` method returns the user and system CPU time usage of +the current worker thread, in an object with properties `user` and `system`, whose +values are microsecond values (millionth of a second). + +The result of a previous call to `process.threadCpuUsage()` can be passed as the +argument to the function, to get a diff reading. + ## `process.title` * `count` {number} The number of assertions and subtests that are expected to run. +* `options` {Object} Additional options for the plan. + * `wait` {boolean|number} The wait time for the plan: + * If `true`, the plan waits indefinitely for all assertions and subtests to run. + * If `false`, the plan performs an immediate check after the test function completes, + without waiting for any pending assertions or subtests. + Any assertions or subtests that complete after this check will not be counted towards the plan. + * If a number, it specifies the maximum wait time in milliseconds + before timing out while waiting for expected assertions and subtests to be matched. + If the timeout is reached, the test will fail. + **Default:** `false`. This function is used to set the number of assertions and subtests that are expected to run within the test. If the number of assertions and subtests that run does not match the @@ -3449,6 +3463,26 @@ test('planning with streams', (t, done) => { }); ``` +When using the `wait` option, you can control how long the test will wait for the expected assertions. +For example, setting a maximum wait time ensures that the test will wait for asynchronous assertions +to complete within the specified timeframe: + +```js +test('plan with wait: 2000 waits for async assertions', (t) => { + t.plan(1, { wait: 2000 }); // Waits for up to 2 seconds for the assertion to complete. + + const asyncActivity = () => { + setTimeout(() => { + t.assert.ok(true, 'Async assertion completed within the wait time'); + }, 1000); // Completes after 1 second, within the 2-second wait time. + }; + + asyncActivity(); // The test will pass because the assertion is completed in time. +}); +``` + +Note: If a `wait` timeout is specified, it begins counting down only after the test function finishes executing. + ### `context.runOnly(shouldRunOnlyTests)` From 1a244177a3bb8aec36e3dd120b24a84c6027c577 Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Mon, 24 Feb 2025 10:29:24 +0900 Subject: [PATCH 85/92] test: add doAppendAndCancel test PR-URL: https://github.com/nodejs/node/pull/56972 Reviewed-By: Luigi Pinca --- ...est-fs-promises-file-handle-append-file.js | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/test/parallel/test-fs-promises-file-handle-append-file.js b/test/parallel/test-fs-promises-file-handle-append-file.js index 90bb6e392516f4..42b6dbee309cec 100644 --- a/test/parallel/test-fs-promises-file-handle-append-file.js +++ b/test/parallel/test-fs-promises-file-handle-append-file.js @@ -39,6 +39,21 @@ async function validateAppendString() { await fileHandle.close(); } -validateAppendBuffer() - .then(validateAppendString) - .then(common.mustCall()); +async function doAppendAndCancel() { + const filePathForHandle = path.resolve(tmpDir, 'dogs-running.txt'); + const fileHandle = await open(filePathForHandle, 'w+'); + const buffer = Buffer.from('dogs running'.repeat(512 * 1024), 'utf8'); + const controller = new AbortController(); + const { signal } = controller; + process.nextTick(() => controller.abort()); + await assert.rejects(fileHandle.appendFile(buffer, { signal }), { + name: 'AbortError' + }); + await fileHandle.close(); +} + +Promise.all([ + validateAppendBuffer(), + validateAppendString(), + doAppendAndCancel(), +]).then(common.mustCall()); From 455bf5a0a8b7cf6c186a6228e9bfd84f1176424b Mon Sep 17 00:00:00 2001 From: Hasegawa-Yukihiro Date: Mon, 24 Feb 2025 10:29:31 +0900 Subject: [PATCH 86/92] doc: update options to filehandle.appendFile() PR-URL: https://github.com/nodejs/node/pull/56972 Reviewed-By: Luigi Pinca --- doc/api/fs.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index 471ac9b84fee54..2b1a10653ada0f 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -199,8 +199,7 @@ changes: * `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Stream} * `options` {Object|string} * `encoding` {string|null} **Default:** `'utf8'` - * `flush` {boolean} If `true`, the underlying file descriptor is flushed - prior to closing it. **Default:** `false`. + * `signal` {AbortSignal|undefined} allows aborting an in-progress writeFile. **Default:** `undefined` * Returns: {Promise} Fulfills with `undefined` upon success. Alias of [`filehandle.writeFile()`][]. From e08d7d4e5366dd0ef2bee70f43f9027c2144441a Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sat, 22 Feb 2025 15:07:27 -0800 Subject: [PATCH 87/92] lib: fixup incorrect argument order in assertEncoding PR-URL: https://github.com/nodejs/node/pull/57177 Reviewed-By: Yagiz Nizipli Reviewed-By: Colin Ihrig Reviewed-By: Antoine du Hamel Reviewed-By: Chemi Atlow Reviewed-By: Luigi Pinca --- lib/internal/fs/utils.js | 2 +- test/parallel/test-fs-internal-assertencoding.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-fs-internal-assertencoding.js diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js index d54ab2112461aa..8d7f32a8925cc8 100644 --- a/lib/internal/fs/utils.js +++ b/lib/internal/fs/utils.js @@ -154,7 +154,7 @@ function lazyLoadFs() { function assertEncoding(encoding) { if (encoding && !Buffer.isEncoding(encoding)) { const reason = 'is invalid encoding'; - throw new ERR_INVALID_ARG_VALUE(encoding, 'encoding', reason); + throw new ERR_INVALID_ARG_VALUE('encoding', encoding, reason); } } diff --git a/test/parallel/test-fs-internal-assertencoding.js b/test/parallel/test-fs-internal-assertencoding.js new file mode 100644 index 00000000000000..ea88517b908861 --- /dev/null +++ b/test/parallel/test-fs-internal-assertencoding.js @@ -0,0 +1,15 @@ +'use strict'; + +// Tests to verify that a correctly formatted invalid encoding error +// is thrown. Originally the internal assertEncoding utility was +// reversing the arguments when constructing the ERR_INVALID_ARG_VALUE +// error. + +require('../common'); +const { opendirSync } = require('node:fs'); +const { throws } = require('node:assert'); + +throws(() => opendirSync('.', { encoding: 'no' }), { + code: 'ERR_INVALID_ARG_VALUE', + message: 'The argument \'encoding\' is invalid encoding. Received \'no\'', +}); From aa817853cda230adcdfb824a0704aea6408ad0f3 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sat, 22 Feb 2025 16:01:09 -0800 Subject: [PATCH 88/92] lib: fixup more incorrect ERR_INVALID_ARG_VALUE uses PR-URL: https://github.com/nodejs/node/pull/57177 Reviewed-By: Yagiz Nizipli Reviewed-By: Colin Ihrig Reviewed-By: Antoine du Hamel Reviewed-By: Chemi Atlow Reviewed-By: Luigi Pinca --- lib/internal/fs/recursive_watch.js | 2 +- lib/internal/fs/watchers.js | 2 +- lib/internal/quic/quic.js | 2 +- lib/internal/webstreams/adapters.js | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/internal/fs/recursive_watch.js b/lib/internal/fs/recursive_watch.js index 38c0505797c3ad..29d8c23fdfbe31 100644 --- a/lib/internal/fs/recursive_watch.js +++ b/lib/internal/fs/recursive_watch.js @@ -68,7 +68,7 @@ class FSWatcher extends EventEmitter { if (encoding != null) { // This is required since on macOS and Windows it throws ERR_INVALID_ARG_VALUE if (typeof encoding !== 'string') { - throw new ERR_INVALID_ARG_VALUE(encoding, 'options.encoding'); + throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding); } } diff --git a/lib/internal/fs/watchers.js b/lib/internal/fs/watchers.js index 411eab8136d595..d1d8bcf372631f 100644 --- a/lib/internal/fs/watchers.js +++ b/lib/internal/fs/watchers.js @@ -318,7 +318,7 @@ async function* watch(filename, options = kEmptyObject) { if (encoding && !isEncoding(encoding)) { const reason = 'is invalid encoding'; - throw new ERR_INVALID_ARG_VALUE(encoding, 'encoding', reason); + throw new ERR_INVALID_ARG_VALUE('encoding', encoding, reason); } if (signal?.aborted) diff --git a/lib/internal/quic/quic.js b/lib/internal/quic/quic.js index afe057de5bd951..5b96dbc80be92c 100644 --- a/lib/internal/quic/quic.js +++ b/lib/internal/quic/quic.js @@ -2069,7 +2069,7 @@ function processSessionOptions(options, forServer = false) { if (cc !== undefined) { validateString(cc, 'options.cc'); if (cc !== 'reno' || cc !== 'bbr' || cc !== 'cubic') { - throw new ERR_INVALID_ARG_VALUE(cc, 'options.cc'); + throw new ERR_INVALID_ARG_VALUE('options.cc', cc); } } diff --git a/lib/internal/webstreams/adapters.js b/lib/internal/webstreams/adapters.js index 4e99d56aa4be7e..662eba6fbdf409 100644 --- a/lib/internal/webstreams/adapters.js +++ b/lib/internal/webstreams/adapters.js @@ -529,7 +529,7 @@ function newStreamReadableFromReadableStream(readableStream, options = kEmptyObj } = options; if (encoding !== undefined && !Buffer.isEncoding(encoding)) - throw new ERR_INVALID_ARG_VALUE(encoding, 'options.encoding'); + throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding); validateBoolean(objectMode, 'options.objectMode'); const reader = readableStream.getReader(); @@ -686,7 +686,7 @@ function newStreamDuplexFromReadableWritablePair(pair = kEmptyObject, options = validateBoolean(objectMode, 'options.objectMode'); if (encoding !== undefined && !Buffer.isEncoding(encoding)) - throw new ERR_INVALID_ARG_VALUE(encoding, 'options.encoding'); + throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding); const writer = writableStream.getWriter(); const reader = readableStream.getReader(); From 2aceca15d69ed9452f589be3c0adb79005ed0d91 Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Mon, 24 Feb 2025 19:53:10 -0500 Subject: [PATCH 89/92] deps: update sqlite to 3.49.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/57178 Reviewed-By: Colin Ihrig Reviewed-By: Antoine du Hamel Reviewed-By: Ulises Gascón Reviewed-By: Rafael Gonzaga Reviewed-By: Zeyu "Alex" Yang --- deps/sqlite/sqlite3.c | 22 ++++++++++++---------- deps/sqlite/sqlite3.h | 6 +++--- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/deps/sqlite/sqlite3.c b/deps/sqlite/sqlite3.c index 8293fa99188633..37b534afb2787c 100644 --- a/deps/sqlite/sqlite3.c +++ b/deps/sqlite/sqlite3.c @@ -1,6 +1,6 @@ /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version 3.49.0. By combining all the individual C code files into this +** version 3.49.1. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements @@ -18,7 +18,7 @@ ** separate file. This file contains only code for the core SQLite library. ** ** The content in this amalgamation comes from Fossil check-in -** 4a7dd425dc2a0e5082a9049c9b4a9d4f199a with changes in files: +** 873d4e274b4988d260ba8354a9718324a1c2 with changes in files: ** ** */ @@ -465,9 +465,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.49.0" -#define SQLITE_VERSION_NUMBER 3049000 -#define SQLITE_SOURCE_ID "2025-02-06 11:55:18 4a7dd425dc2a0e5082a9049c9b4a9d4f199a71583d014c24b4cfe276c5a77cde" +#define SQLITE_VERSION "3.49.1" +#define SQLITE_VERSION_NUMBER 3049001 +#define SQLITE_SOURCE_ID "2025-02-18 13:38:58 873d4e274b4988d260ba8354a9718324a1c26187a4ab4c1cc0227c03d0f10e70" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -131067,7 +131067,7 @@ static void concatFuncCore( for(i=0; i65528 ) sz = 65528; if( cnt<0 ) cnt = 0; + szAlloc = (i64)sz*(i64)cnt; if( sz==0 || cnt==0 ){ sz = 0; pStart = 0; @@ -182339,10 +182341,10 @@ static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){ #ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE if( sz>=LOOKASIDE_SMALL*3 ){ nBig = szAlloc/(3*LOOKASIDE_SMALL+sz); - nSm = (szAlloc - sz*nBig)/LOOKASIDE_SMALL; + nSm = (szAlloc - (i64)sz*(i64)nBig)/LOOKASIDE_SMALL; }else if( sz>=LOOKASIDE_SMALL*2 ){ nBig = szAlloc/(LOOKASIDE_SMALL+sz); - nSm = (szAlloc - sz*nBig)/LOOKASIDE_SMALL; + nSm = (szAlloc - (i64)sz*(i64)nBig)/LOOKASIDE_SMALL; }else #endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ if( sz>0 ){ @@ -255872,7 +255874,7 @@ static void fts5SourceIdFunc( ){ assert( nArg==0 ); UNUSED_PARAM2(nArg, apUnused); - sqlite3_result_text(pCtx, "fts5: 2025-02-06 11:55:18 4a7dd425dc2a0e5082a9049c9b4a9d4f199a71583d014c24b4cfe276c5a77cde", -1, SQLITE_TRANSIENT); + sqlite3_result_text(pCtx, "fts5: 2025-02-18 13:38:58 873d4e274b4988d260ba8354a9718324a1c26187a4ab4c1cc0227c03d0f10e70", -1, SQLITE_TRANSIENT); } /* diff --git a/deps/sqlite/sqlite3.h b/deps/sqlite/sqlite3.h index 0153128ee20c54..082a9f9dc44e93 100644 --- a/deps/sqlite/sqlite3.h +++ b/deps/sqlite/sqlite3.h @@ -146,9 +146,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.49.0" -#define SQLITE_VERSION_NUMBER 3049000 -#define SQLITE_SOURCE_ID "2025-02-06 11:55:18 4a7dd425dc2a0e5082a9049c9b4a9d4f199a71583d014c24b4cfe276c5a77cde" +#define SQLITE_VERSION "3.49.1" +#define SQLITE_VERSION_NUMBER 3049001 +#define SQLITE_SOURCE_ID "2025-02-18 13:38:58 873d4e274b4988d260ba8354a9718324a1c26187a4ab4c1cc0227c03d0f10e70" /* ** CAPI3REF: Run-Time Library Version Numbers From 8644cf3e5a212c3d2511eab9acc8f5828414de54 Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Mon, 24 Feb 2025 19:53:20 -0500 Subject: [PATCH 90/92] deps: update ngtcp2 to 1.11.0 PR-URL: https://github.com/nodejs/node/pull/57179 Reviewed-By: Luigi Pinca Reviewed-By: Rafael Gonzaga --- .../crypto/includes/ngtcp2/ngtcp2_crypto.h | 4 +- deps/ngtcp2/ngtcp2/crypto/picotls/picotls.c | 47 +- .../ngtcp2/lib/includes/ngtcp2/ngtcp2.h | 8 +- .../ngtcp2/lib/includes/ngtcp2/version.h | 4 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.c | 114 ++ deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.h | 44 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_addr.c | 23 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_addr.h | 30 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c | 79 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h | 1 + deps/ngtcp2/ngtcp2/lib/ngtcp2_cc.c | 6 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.c | 4 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.h | 13 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c | 1300 ++++++----------- deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.h | 121 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.c | 497 +++++++ deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.h | 343 +++++ deps/ngtcp2/ngtcp2/lib/ngtcp2_frame_chain.c | 20 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_frame_chain.h | 44 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_gaptr.c | 27 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.c | 2 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_map.c | 6 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_mem.c | 8 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.c | 8 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.h | 10 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_pq.c | 16 - deps/ngtcp2/ngtcp2/lib/ngtcp2_pq.h | 11 - deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.c | 7 + deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.h | 18 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_range.c | 2 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_ringbuf.c | 4 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_ringbuf.h | 2 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.c | 11 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_rst.c | 11 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_rst.h | 6 + deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.c | 25 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.h | 4 - .../ngtcp2/lib/ngtcp2_transport_params.c | 9 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_vec.c | 34 +- deps/ngtcp2/ngtcp2/lib/ngtcp2_vec.h | 16 - 40 files changed, 1724 insertions(+), 1215 deletions(-) create mode 100644 deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.c create mode 100644 deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.h diff --git a/deps/ngtcp2/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto.h b/deps/ngtcp2/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto.h index 68093d18b71b14..4eaf615bd309f3 100644 --- a/deps/ngtcp2/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto.h +++ b/deps/ngtcp2/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto.h @@ -331,7 +331,7 @@ ngtcp2_crypto_hp_mask_cb(uint8_t *dest, const ngtcp2_crypto_cipher *hp, * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`) to * set negotiated AEAD and message digest algorithm. After the * successful call of this function, application can use - * `ngtcp2_conn_get_crypto_ctx` (or `ngtcp2_conn_get_early_crypto_ctx` + * `ngtcp2_conn_get_crypto_ctx` (or `ngtcp2_conn_get_0rtt_crypto_ctx` * if |level| == * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`) to * get :type:`ngtcp2_crypto_ctx`. @@ -378,7 +378,7 @@ NGTCP2_EXTERN int ngtcp2_crypto_derive_and_install_rx_key( * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`) to * set negotiated AEAD and message digest algorithm. After the * successful call of this function, application can use - * `ngtcp2_conn_get_crypto_ctx` (or `ngtcp2_conn_get_early_crypto_ctx` + * `ngtcp2_conn_get_crypto_ctx` (or `ngtcp2_conn_get_0rtt_crypto_ctx` * if |level| == * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`) to * get :type:`ngtcp2_crypto_ctx`. diff --git a/deps/ngtcp2/ngtcp2/crypto/picotls/picotls.c b/deps/ngtcp2/ngtcp2/crypto/picotls/picotls.c index c3f14f5d444bac..98ebf9e876c053 100644 --- a/deps/ngtcp2/ngtcp2/crypto/picotls/picotls.c +++ b/deps/ngtcp2/ngtcp2/crypto/picotls/picotls.c @@ -125,7 +125,7 @@ static int supported_cipher_suite(ptls_cipher_suite_t *cs) { #ifdef PTLS_OPENSSL_HAVE_CHACHA20_POLY1305 || cs->aead == &ptls_openssl_chacha20poly1305 #endif /* defined(PTLS_OPENSSL_HAVE_CHACHA20_POLY1305) */ - ; + ; } ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx, @@ -146,7 +146,7 @@ ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx, ctx->hp.native_handle = (void *)crypto_cipher_suite_get_hp(cs); ctx->max_encryption = crypto_cipher_suite_get_aead_max_encryption(cs); ctx->max_decryption_failure = - crypto_cipher_suite_get_aead_max_decryption_failure(cs); + crypto_cipher_suite_get_aead_max_decryption_failure(cs); return ctx; } @@ -329,19 +329,22 @@ int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, const uint8_t *nonce, size_t noncelen, const uint8_t *aad, size_t aadlen) { ptls_aead_context_t *actx = aead_ctx->native_handle; + size_t nwrite; (void)aead; ptls_aead_xor_iv(actx, nonce, noncelen); - if (ptls_aead_decrypt(actx, dest, ciphertext, ciphertextlen, 0, aad, - aadlen) == SIZE_MAX) { - return -1; - } + nwrite = + ptls_aead_decrypt(actx, dest, ciphertext, ciphertextlen, 0, aad, aadlen); /* zero-out static iv once again */ ptls_aead_xor_iv(actx, nonce, noncelen); + if (nwrite == SIZE_MAX) { + return -1; + } + return 0; } @@ -360,13 +363,13 @@ int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, } int ngtcp2_crypto_read_write_crypto_data( - ngtcp2_conn *conn, ngtcp2_encryption_level encryption_level, - const uint8_t *data, size_t datalen) { + ngtcp2_conn *conn, ngtcp2_encryption_level encryption_level, + const uint8_t *data, size_t datalen) { ngtcp2_crypto_picotls_ctx *cptls = ngtcp2_conn_get_tls_native_handle(conn); ptls_buffer_t sendbuf; size_t epoch_offsets[5] = {0}; size_t epoch = - ngtcp2_crypto_picotls_from_ngtcp2_encryption_level(encryption_level); + ngtcp2_crypto_picotls_from_ngtcp2_encryption_level(encryption_level); size_t epoch_datalen; size_t i; int rv; @@ -388,7 +391,7 @@ int ngtcp2_crypto_read_write_crypto_data( if (!ngtcp2_conn_is_server(conn) && cptls->handshake_properties.client.early_data_acceptance == - PTLS_EARLY_DATA_REJECTED) { + PTLS_EARLY_DATA_REJECTED) { rv = ngtcp2_conn_tls_early_data_rejected(conn); if (rv != 0) { rv = -1; @@ -405,8 +408,8 @@ int ngtcp2_crypto_read_write_crypto_data( assert(i != 1); if (ngtcp2_conn_submit_crypto_data( - conn, ngtcp2_crypto_picotls_from_epoch(i), - sendbuf.base + epoch_offsets[i], epoch_datalen) != 0) { + conn, ngtcp2_crypto_picotls_from_epoch(i), + sendbuf.base + epoch_offsets[i], epoch_datalen) != 0) { rv = -1; goto fin; } @@ -463,7 +466,7 @@ ngtcp2_encryption_level ngtcp2_crypto_picotls_from_epoch(size_t epoch) { } size_t ngtcp2_crypto_picotls_from_ngtcp2_encryption_level( - ngtcp2_encryption_level encryption_level) { + ngtcp2_encryption_level encryption_level) { switch (encryption_level) { case NGTCP2_ENCRYPTION_LEVEL_INITIAL: return 0; @@ -532,8 +535,8 @@ static int set_additional_extensions(ptls_handshake_properties_t *hsprops, } int ngtcp2_crypto_picotls_collect_extension( - ptls_t *ptls, struct st_ptls_handshake_properties_t *properties, - uint16_t type) { + ptls_t *ptls, struct st_ptls_handshake_properties_t *properties, + uint16_t type) { (void)ptls; (void)properties; @@ -541,8 +544,8 @@ int ngtcp2_crypto_picotls_collect_extension( } int ngtcp2_crypto_picotls_collected_extensions( - ptls_t *ptls, struct st_ptls_handshake_properties_t *properties, - ptls_raw_extension_t *extensions) { + ptls_t *ptls, struct st_ptls_handshake_properties_t *properties, + ptls_raw_extension_t *extensions) { ngtcp2_crypto_conn_ref *conn_ref; ngtcp2_conn *conn; int rv; @@ -558,7 +561,7 @@ int ngtcp2_crypto_picotls_collected_extensions( conn = conn_ref->get_conn(conn_ref); rv = ngtcp2_conn_decode_and_set_remote_transport_params( - conn, extensions->data.base, extensions->data.len); + conn, extensions->data.base, extensions->data.len); if (rv != 0) { ngtcp2_conn_set_tls_error(conn, rv); return -1; @@ -613,7 +616,7 @@ static int update_traffic_key_server_cb(ptls_update_traffic_key_t *self, } static ptls_update_traffic_key_t update_traffic_key_server = { - update_traffic_key_server_cb, + update_traffic_key_server_cb, }; static int update_traffic_key_cb(ptls_update_traffic_key_t *self, ptls_t *ptls, @@ -661,7 +664,7 @@ int ngtcp2_crypto_picotls_configure_client_context(ptls_context_t *ctx) { } int ngtcp2_crypto_picotls_configure_server_session( - ngtcp2_crypto_picotls_ctx *cptls) { + ngtcp2_crypto_picotls_ctx *cptls) { ptls_handshake_properties_t *hsprops = &cptls->handshake_properties; hsprops->collect_extension = ngtcp2_crypto_picotls_collect_extension; @@ -671,7 +674,7 @@ int ngtcp2_crypto_picotls_configure_server_session( } int ngtcp2_crypto_picotls_configure_client_session( - ngtcp2_crypto_picotls_ctx *cptls, ngtcp2_conn *conn) { + ngtcp2_crypto_picotls_ctx *cptls, ngtcp2_conn *conn) { ptls_handshake_properties_t *hsprops = &cptls->handshake_properties; hsprops->client.max_early_data_size = calloc(1, sizeof(size_t)); @@ -692,7 +695,7 @@ int ngtcp2_crypto_picotls_configure_client_session( } void ngtcp2_crypto_picotls_deconfigure_session( - ngtcp2_crypto_picotls_ctx *cptls) { + ngtcp2_crypto_picotls_ctx *cptls) { ptls_handshake_properties_t *hsprops; ptls_raw_extension_t *exts; diff --git a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h index 87976b1444b95d..d2a2fe1fe1be0d 100644 --- a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h +++ b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h @@ -167,8 +167,12 @@ typedef void *(*ngtcp2_realloc)(void *ptr, size_t size, void *user_data); * } * * void conn_new() { - * ngtcp2_mem mem = {NULL, my_malloc_cb, my_free_cb, my_calloc_cb, - * my_realloc_cb}; + * ngtcp2_mem mem = { + * .malloc = my_malloc_cb, + * .free = my_free_cb, + * .calloc = my_calloc_cb, + * .realloc = my_realloc_cb, + * }; * * ... * } diff --git a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h index 20c4ac24d2ebcd..bb983f5cf44be3 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.10.0" +#define NGTCP2_VERSION "1.11.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 0x010a00 +#define NGTCP2_VERSION_NUM 0x010b00 #endif /* !defined(NGTCP2_VERSION_H) */ diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.c index 0e5bfe069e9fab..776dc0c2c3ef1a 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.c @@ -25,6 +25,7 @@ #include "ngtcp2_acktr.h" #include +#include #include "ngtcp2_macro.h" #include "ngtcp2_tstamp.h" @@ -70,6 +71,9 @@ void ngtcp2_acktr_init(ngtcp2_acktr *acktr, ngtcp2_log *log, acktr->flags = NGTCP2_ACKTR_FLAG_NONE; acktr->first_unacked_ts = UINT64_MAX; acktr->rx_npkt = 0; + acktr->max_pkt_num = -1; + acktr->max_pkt_ts = UINT64_MAX; + memset(&acktr->ecn, 0, sizeof(acktr->ecn)); } void ngtcp2_acktr_free(ngtcp2_acktr *acktr) { @@ -180,6 +184,11 @@ int ngtcp2_acktr_add(ngtcp2_acktr *acktr, int64_t pkt_num, int active_ack, ngtcp2_acktr_entry_objalloc_del(delent, &acktr->objalloc); } + if (acktr->max_pkt_num < pkt_num) { + acktr->max_pkt_num = pkt_num; + acktr->max_pkt_ts = ts; + } + return 0; } @@ -323,3 +332,108 @@ int ngtcp2_acktr_require_active_ack(const ngtcp2_acktr *acktr, void ngtcp2_acktr_immediate_ack(ngtcp2_acktr *acktr) { acktr->flags |= NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK; } + +ngtcp2_frame *ngtcp2_acktr_create_ack_frame(ngtcp2_acktr *acktr, + ngtcp2_frame *fr, uint8_t type, + ngtcp2_tstamp ts, + ngtcp2_duration ack_delay, + uint64_t ack_delay_exponent) { + int64_t last_pkt_num; + ngtcp2_ack_range *range; + ngtcp2_ksl_it it; + ngtcp2_acktr_entry *rpkt; + ngtcp2_ack *ack = &fr->ack; + ngtcp2_tstamp largest_ack_ts; + size_t num_acks; + + if (acktr->flags & NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK) { + ack_delay = 0; + } + + if (!ngtcp2_acktr_require_active_ack(acktr, ack_delay, ts)) { + return NULL; + } + + it = ngtcp2_acktr_get(acktr); + if (ngtcp2_ksl_it_end(&it)) { + ngtcp2_acktr_commit_ack(acktr); + return NULL; + } + + num_acks = ngtcp2_ksl_len(&acktr->ents); + + if (acktr->ecn.ect0 || acktr->ecn.ect1 || acktr->ecn.ce) { + ack->type = NGTCP2_FRAME_ACK_ECN; + ack->ecn.ect0 = acktr->ecn.ect0; + ack->ecn.ect1 = acktr->ecn.ect1; + ack->ecn.ce = acktr->ecn.ce; + } else { + ack->type = NGTCP2_FRAME_ACK; + } + ack->rangecnt = 0; + + rpkt = ngtcp2_ksl_it_get(&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; + + ngtcp2_ksl_it_next(&it); + --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; + + ngtcp2_ksl_it_next(&it); + --num_acks; + } else { + 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 = ack->ack_delay_unscaled / NGTCP2_MICROSECONDS / + (1ULL << ack_delay_exponent); + } else { + ack->ack_delay_unscaled = 0; + ack->ack_delay = 0; + } + + num_acks = ngtcp2_min_size(num_acks, NGTCP2_MAX_ACK_RANGES); + + for (; ack->rangecnt < num_acks; ngtcp2_ksl_it_next(&it)) { + rpkt = ngtcp2_ksl_it_get(&it); + + range = &ack->ranges[ack->rangecnt++]; + range->gap = (uint64_t)(last_pkt_num - rpkt->pkt_num - 2); + range->len = rpkt->len - 1; + + last_pkt_num = rpkt->pkt_num - (int64_t)(rpkt->len - 1); + } + + return fr; +} + +void ngtcp2_acktr_increase_ecn_counts(ngtcp2_acktr *acktr, + const ngtcp2_pkt_info *pi) { + switch (pi->ecn & NGTCP2_ECN_MASK) { + case NGTCP2_ECN_ECT_0: + ++acktr->ecn.ect0; + break; + case NGTCP2_ECN_ECT_1: + ++acktr->ecn.ect1; + break; + case NGTCP2_ECN_CE: + ++acktr->ecn.ce; + break; + } +} diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.h index 16aee42f275f67..cf75a774db3e82 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_acktr.h @@ -128,6 +128,26 @@ typedef struct ngtcp2_acktr { /* rx_npkt is the number of ACK eliciting packets received without sending ACK. */ size_t rx_npkt; + /* max_pkt_num is the largest packet number received so far. */ + int64_t max_pkt_num; + /* max_pkt_ts is the timestamp when max_pkt_num packet is + received. */ + ngtcp2_tstamp max_pkt_ts; + + struct { + /* ect0, ect1, and ce are the number of QUIC packets received + with those markings. */ + size_t ect0; + size_t ect1; + size_t ce; + struct { + /* ect0, ect1, ce are the ECN counts received in the latest + ACK frame. */ + uint64_t ect0; + uint64_t ect1; + uint64_t ce; + } ack; + } ecn; } ngtcp2_acktr; /* @@ -165,7 +185,7 @@ int ngtcp2_acktr_add(ngtcp2_acktr *acktr, int64_t pkt_num, int active_ack, void ngtcp2_acktr_forget(ngtcp2_acktr *acktr, ngtcp2_acktr_entry *ent); /* - * ngtcp2_acktr_get returns the pointer to pointer to the entry which + * ngtcp2_acktr_get returns the iterator to pointer to the entry which * has the largest packet number to be acked. If there is no entry, * returned value satisfies ngtcp2_ksl_it_end(&it) != 0. */ @@ -213,4 +233,26 @@ int ngtcp2_acktr_require_active_ack(const ngtcp2_acktr *acktr, */ void ngtcp2_acktr_immediate_ack(ngtcp2_acktr *acktr); +/* + * ngtcp2_acktr_create_ack_frame creates ACK frame in the object + * pointed by |fr|, and returns |fr| if there are any received packets + * to acknowledge. If there are no packets to acknowledge, this + * function returns NULL. fr->ack.ranges must be able to contain at + * least NGTCP2_MAX_ACK_RANGES elements. + * + * Call ngtcp2_acktr_commit_ack after a created ACK frame is + * successfully serialized into a packet. + */ +ngtcp2_frame *ngtcp2_acktr_create_ack_frame(ngtcp2_acktr *acktr, + ngtcp2_frame *fr, uint8_t type, + ngtcp2_tstamp ts, + ngtcp2_duration ack_delay, + uint64_t ack_delay_exponent); + +/* + * ngtcp2_acktr_increase_ecn_counts increases ECN counts from |pi|. + */ +void ngtcp2_acktr_increase_ecn_counts(ngtcp2_acktr *acktr, + const ngtcp2_pkt_info *pi); + #endif /* !defined(NGTCP2_ACKTR_H) */ diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_addr.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_addr.c index f389abe76d71c8..1fb273d494e8e2 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_addr.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_addr.c @@ -51,8 +51,10 @@ void ngtcp2_addr_copy_byte(ngtcp2_addr *dest, const ngtcp2_sockaddr *addr, } } -static int sockaddr_eq(const ngtcp2_sockaddr *a, const ngtcp2_sockaddr *b) { - assert(a->sa_family == b->sa_family); +int ngtcp2_sockaddr_eq(const ngtcp2_sockaddr *a, const ngtcp2_sockaddr *b) { + if (a->sa_family != b->sa_family) { + return 0; + } switch (a->sa_family) { case NGTCP2_AF_INET: { @@ -73,17 +75,16 @@ static int sockaddr_eq(const ngtcp2_sockaddr *a, const ngtcp2_sockaddr *b) { } int ngtcp2_addr_eq(const ngtcp2_addr *a, const ngtcp2_addr *b) { - return a->addr->sa_family == b->addr->sa_family && - sockaddr_eq(a->addr, b->addr); + return ngtcp2_sockaddr_eq(a->addr, b->addr); } -uint32_t ngtcp2_addr_compare(const ngtcp2_addr *aa, const ngtcp2_addr *bb) { - uint32_t flags = NGTCP2_ADDR_COMPARE_FLAG_NONE; +uint32_t ngtcp2_addr_cmp(const ngtcp2_addr *aa, const ngtcp2_addr *bb) { + uint32_t flags = NGTCP2_ADDR_CMP_FLAG_NONE; const ngtcp2_sockaddr *a = aa->addr; const ngtcp2_sockaddr *b = bb->addr; if (a->sa_family != b->sa_family) { - return NGTCP2_ADDR_COMPARE_FLAG_FAMILY; + return NGTCP2_ADDR_CMP_FLAG_FAMILY; } switch (a->sa_family) { @@ -91,10 +92,10 @@ uint32_t ngtcp2_addr_compare(const ngtcp2_addr *aa, const ngtcp2_addr *bb) { const ngtcp2_sockaddr_in *ai = (const ngtcp2_sockaddr_in *)(void *)a, *bi = (const ngtcp2_sockaddr_in *)(void *)b; if (memcmp(&ai->sin_addr, &bi->sin_addr, sizeof(ai->sin_addr))) { - flags |= NGTCP2_ADDR_COMPARE_FLAG_ADDR; + flags |= NGTCP2_ADDR_CMP_FLAG_ADDR; } if (ai->sin_port != bi->sin_port) { - flags |= NGTCP2_ADDR_COMPARE_FLAG_PORT; + flags |= NGTCP2_ADDR_CMP_FLAG_PORT; } return flags; } @@ -102,10 +103,10 @@ uint32_t ngtcp2_addr_compare(const ngtcp2_addr *aa, const ngtcp2_addr *bb) { const ngtcp2_sockaddr_in6 *ai = (const ngtcp2_sockaddr_in6 *)(void *)a, *bi = (const ngtcp2_sockaddr_in6 *)(void *)b; if (memcmp(&ai->sin6_addr, &bi->sin6_addr, sizeof(ai->sin6_addr))) { - flags |= NGTCP2_ADDR_COMPARE_FLAG_ADDR; + flags |= NGTCP2_ADDR_CMP_FLAG_ADDR; } if (ai->sin6_port != bi->sin6_port) { - flags |= NGTCP2_ADDR_COMPARE_FLAG_PORT; + flags |= NGTCP2_ADDR_CMP_FLAG_PORT; } return flags; } diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_addr.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_addr.h index 65ee7cd9f3006b..c2224f85cd9c11 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_addr.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_addr.h @@ -45,22 +45,21 @@ void ngtcp2_addr_copy(ngtcp2_addr *dest, const ngtcp2_addr *src); */ int ngtcp2_addr_eq(const ngtcp2_addr *a, const ngtcp2_addr *b); -/* NGTCP2_ADDR_COMPARE_FLAG_NONE indicates that no flag set. */ -#define NGTCP2_ADDR_COMPARE_FLAG_NONE 0x0u -/* NGTCP2_ADDR_COMPARE_FLAG_ADDR indicates IP addresses do not +/* NGTCP2_ADDR_CMP_FLAG_NONE indicates that no flag set. */ +#define NGTCP2_ADDR_CMP_FLAG_NONE 0x0u +/* NGTCP2_ADDR_CMP_FLAG_ADDR indicates IP addresses do not match. */ +#define NGTCP2_ADDR_CMP_FLAG_ADDR 0x1u +/* NGTCP2_ADDR_CMP_FLAG_PORT indicates ports do not match. */ +#define NGTCP2_ADDR_CMP_FLAG_PORT 0x2u +/* NGTCP2_ADDR_CMP_FLAG_FAMILY indicates address families do not match. */ -#define NGTCP2_ADDR_COMPARE_FLAG_ADDR 0x1u -/* NGTCP2_ADDR_COMPARE_FLAG_PORT indicates ports do not match. */ -#define NGTCP2_ADDR_COMPARE_FLAG_PORT 0x2u -/* NGTCP2_ADDR_COMPARE_FLAG_FAMILY indicates address families do not - match. */ -#define NGTCP2_ADDR_COMPARE_FLAG_FAMILY 0x4u +#define NGTCP2_ADDR_CMP_FLAG_FAMILY 0x4u /* - * ngtcp2_addr_compare compares address and port between |a| and |b|, - * and returns zero or more of NGTCP2_ADDR_COMPARE_FLAG_*. + * ngtcp2_addr_cmp compares address and port between |a| and |b|, and + * returns zero or more of NGTCP2_ADDR_CMP_FLAG_*. */ -uint32_t ngtcp2_addr_compare(const ngtcp2_addr *a, const ngtcp2_addr *b); +uint32_t ngtcp2_addr_cmp(const ngtcp2_addr *a, const ngtcp2_addr *b); /* * ngtcp2_addr_empty returns nonzero if |addr| has zero length @@ -68,4 +67,11 @@ uint32_t ngtcp2_addr_compare(const ngtcp2_addr *a, const ngtcp2_addr *b); */ int ngtcp2_addr_empty(const ngtcp2_addr *addr); +/** + * @function + * + * `ngtcp2_sockaddr_eq` returns nonzero if |a| equals |b|. + */ +int ngtcp2_sockaddr_eq(const ngtcp2_sockaddr *a, const ngtcp2_sockaddr *b); + #endif /* !defined(NGTCP2_ADDR_H) */ diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c index 8777ca4c8a1632..a20f04521e36ca 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c @@ -39,7 +39,7 @@ #define NGTCP2_BBR_EXTRA_ACKED_FILTERLEN 10 #define NGTCP2_BBR_STARTUP_PACING_GAIN_H 277 -#define NGTCP2_BBR_DRAIN_PACING_GAIN_H 35 +#define NGTCP2_BBR_DRAIN_PACING_GAIN_H 50 #define NGTCP2_BBR_DEFAULT_CWND_GAIN_H 200 @@ -130,6 +130,8 @@ static void bbr_start_round(ngtcp2_cc_bbr *bbr); static int bbr_is_in_probe_bw_state(ngtcp2_cc_bbr *bbr); +static int bbr_is_probing_bw(ngtcp2_cc_bbr *bbr); + static void bbr_update_ack_aggregation(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack, @@ -137,8 +139,8 @@ static void bbr_update_ack_aggregation(ngtcp2_cc_bbr *bbr, static void bbr_enter_drain(ngtcp2_cc_bbr *bbr); -static void bbr_check_drain(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, - ngtcp2_tstamp ts); +static void bbr_check_drain_done(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); static void bbr_enter_probe_bw(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts); @@ -277,7 +279,8 @@ static void bbr_on_init(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, ngtcp2_window_filter_init(&bbr->extra_acked_filter, NGTCP2_BBR_EXTRA_ACKED_FILTERLEN); - bbr->min_rtt = UINT64_MAX; + bbr->min_rtt = + cstat->first_rtt_sample_ts == UINT64_MAX ? UINT64_MAX : cstat->smoothed_rtt; bbr->min_rtt_stamp = initial_ts; /* remark: Use UINT64_MAX instead of 0 for consistency. */ bbr->probe_rtt_done_stamp = UINT64_MAX; @@ -335,6 +338,8 @@ static void bbr_on_init(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, bbr->max_inflight = 0; bbr->congestion_recovery_start_ts = UINT64_MAX; + + bbr->bdp = 0; } static void bbr_reset_congestion_signals(ngtcp2_cc_bbr *bbr) { @@ -404,8 +409,10 @@ static void bbr_check_startup_high_loss(ngtcp2_cc_bbr *bbr) { } static void bbr_init_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { - cstat->pacing_interval = NGTCP2_MILLISECONDS * 100 / - NGTCP2_BBR_STARTUP_PACING_GAIN_H / bbr->initial_cwnd; + cstat->pacing_interval = + (cstat->first_rtt_sample_ts == UINT64_MAX ? NGTCP2_MILLISECONDS + : cstat->smoothed_rtt) * + 100 / NGTCP2_BBR_STARTUP_PACING_GAIN_H / bbr->initial_cwnd; } static void bbr_set_pacing_rate_with_gain(ngtcp2_cc_bbr *bbr, @@ -465,7 +472,7 @@ static void bbr_update_model_and_state(ngtcp2_cc_bbr *bbr, bbr_update_ack_aggregation(bbr, cstat, ack, ts); bbr_check_full_bw_reached(bbr, cstat); bbr_check_startup_done(bbr); - bbr_check_drain(bbr, cstat, ts); + bbr_check_drain_done(bbr, cstat, ts); bbr_update_probe_bw_cycle_phase(bbr, cstat, ack, ts); bbr_update_min_rtt(bbr, ack, ts); bbr_check_probe_rtt(bbr, cstat, ts); @@ -528,7 +535,7 @@ static void bbr_update_congestion_signals(ngtcp2_cc_bbr *bbr, static void bbr_adapt_lower_bounds_from_congestion(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { - if (bbr_is_in_probe_bw_state(bbr)) { + if (bbr_is_probing_bw(bbr)) { return; } @@ -607,6 +614,17 @@ static int bbr_is_in_probe_bw_state(ngtcp2_cc_bbr *bbr) { } } +static int bbr_is_probing_bw(ngtcp2_cc_bbr *bbr) { + switch (bbr->state) { + case NGTCP2_BBR_STATE_STARTUP: + case NGTCP2_BBR_STATE_PROBE_BW_REFILL: + case NGTCP2_BBR_STATE_PROBE_BW_UP: + return 1; + default: + return 0; + } +} + static void bbr_update_ack_aggregation(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack, @@ -622,8 +640,13 @@ static void bbr_update_ack_aggregation(ngtcp2_cc_bbr *bbr, } bbr->extra_acked_delivered += ack->bytes_delivered; - extra = bbr->extra_acked_delivered - expected_delivered; - extra = ngtcp2_min_uint64(extra, cstat->cwnd); + + if (bbr->extra_acked_delivered <= expected_delivered) { + extra = 0; + } else { + extra = bbr->extra_acked_delivered - expected_delivered; + extra = ngtcp2_min_uint64(extra, cstat->cwnd); + } if (bbr->full_bw_reached) { bbr->extra_acked_filter.window_length = NGTCP2_BBR_EXTRA_ACKED_FILTERLEN; @@ -645,8 +668,8 @@ static void bbr_enter_drain(ngtcp2_cc_bbr *bbr) { bbr->cwnd_gain_h = NGTCP2_BBR_DEFAULT_CWND_GAIN_H; } -static void bbr_check_drain(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, - ngtcp2_tstamp ts) { +static void bbr_check_drain_done(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { if (bbr->state == NGTCP2_BBR_STATE_DRAIN && cstat->bytes_in_flight <= bbr_inflight(bbr, cstat, 100)) { bbr_enter_probe_bw(bbr, ts); @@ -825,8 +848,7 @@ static void bbr_raise_inflight_hi_slope(ngtcp2_cc_bbr *bbr, << bbr->bw_probe_up_rounds; bbr->bw_probe_up_rounds = ngtcp2_min_size(bbr->bw_probe_up_rounds + 1, 30); - bbr->probe_up_cnt = ngtcp2_max_uint64(cstat->cwnd / growth_this_round, 1) * - cstat->max_tx_udp_payload_size; + bbr->probe_up_cnt = ngtcp2_max_uint64(cstat->cwnd / growth_this_round, 1); } static void bbr_probe_inflight_hi_upward(ngtcp2_cc_bbr *bbr, @@ -840,10 +862,12 @@ static void bbr_probe_inflight_hi_upward(ngtcp2_cc_bbr *bbr, bbr->bw_probe_up_acks += ack->bytes_delivered; - if (bbr->bw_probe_up_acks >= bbr->probe_up_cnt) { + 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_hi += delta * cstat->max_tx_udp_payload_size; + bbr->inflight_hi += delta; } if (bbr->round_start) { @@ -901,8 +925,7 @@ static void bbr_pick_probe_wait(ngtcp2_cc_bbr *bbr) { bbr->rand(&rand, 1, &bbr->rand_ctx); - bbr->bw_probe_wait = - 2 * NGTCP2_SECONDS + (ngtcp2_tstamp)(NGTCP2_SECONDS * rand / 255); + bbr->bw_probe_wait = 2 * NGTCP2_SECONDS + NGTCP2_SECONDS * rand / 255; } static int bbr_is_reno_coexistence_probe_time(ngtcp2_cc_bbr *bbr, @@ -915,9 +938,7 @@ static int bbr_is_reno_coexistence_probe_time(ngtcp2_cc_bbr *bbr, static uint64_t bbr_target_inflight(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { - uint64_t bdp = bbr_bdp_multiple(bbr, bbr->cwnd_gain_h); - - return ngtcp2_min_uint64(bdp, cstat->cwnd); + return ngtcp2_min_uint64(bbr->bdp, cstat->cwnd); } static int bbr_check_inflight_too_high(ngtcp2_cc_bbr *bbr, @@ -957,12 +978,11 @@ 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) { - return; + if (!bbr->loss_in_round) { + bbr->loss_round_delivered = bbr->rst->delivered; } bbr->loss_in_round = 1; - bbr->loss_round_delivered = bbr->rst->delivered; } static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, @@ -977,6 +997,7 @@ static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, rs.tx_in_flight = pkt->tx_in_flight; /* bbr->rst->lost is not incremented for pkt yet */ + assert(bbr->rst->lost + pkt->pktlen >= pkt->lost); rs.lost = bbr->rst->lost + pkt->pktlen - pkt->lost; rs.is_app_limited = pkt->is_app_limited; @@ -1143,15 +1164,13 @@ static void bbr_handle_restart_from_idle(ngtcp2_cc_bbr *bbr, } static uint64_t bbr_bdp_multiple(ngtcp2_cc_bbr *bbr, uint64_t gain_h) { - uint64_t bdp; - if (bbr->min_rtt == UINT64_MAX) { return bbr->initial_cwnd; } - bdp = ngtcp2_max_uint64(bbr->bw * bbr->min_rtt / NGTCP2_SECONDS, 1); + bbr->bdp = ngtcp2_max_uint64(bbr->bw * bbr->min_rtt / NGTCP2_SECONDS, 1); - return (uint64_t)(bdp * gain_h / 100); + return (uint64_t)(bbr->bdp * gain_h / 100); } static uint64_t min_pipe_cwnd(size_t max_udp_payload_size) { @@ -1324,10 +1343,6 @@ static void bbr_cc_on_pkt_lost(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) { ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc); - if (bbr->state == NGTCP2_BBR_STATE_STARTUP) { - return; - } - bbr_update_on_loss(bbr, cstat, pkt, ts); } diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h index f266ec5d71e4e4..74eb2d640bd3f2 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h @@ -131,6 +131,7 @@ typedef struct ngtcp2_cc_bbr { uint64_t round_count_at_recovery; uint64_t max_inflight; ngtcp2_tstamp congestion_recovery_start_ts; + uint64_t bdp; } ngtcp2_cc_bbr; void ngtcp2_cc_bbr_init(ngtcp2_cc_bbr *bbr, ngtcp2_log *log, diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_cc.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_cc.c index 1ff59f315c5b66..508a5d9ec1d8a7 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_cc.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_cc.c @@ -252,13 +252,15 @@ void ngtcp2_cc_cubic_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, uint64_t w_cubic, w_cubic_next, target, m; ngtcp2_duration rtt_thresh; int round_start; + int is_app_limited = + cubic->rst->rs.is_app_limited && !cubic->rst->is_cwnd_limited; if (in_congestion_recovery(cstat, ack->largest_pkt_sent_ts)) { return; } if (cubic->current.state == NGTCP2_CUBIC_STATE_CONGESTION_AVOIDANCE) { - if (cubic->rst->rs.is_app_limited && !cubic->rst->is_cwnd_limited) { + if (is_app_limited) { if (cubic->current.app_limited_start_ts == UINT64_MAX) { cubic->current.app_limited_start_ts = ts; } @@ -271,7 +273,7 @@ void ngtcp2_cc_cubic_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, ts - cubic->current.app_limited_start_ts; cubic->current.app_limited_start_ts = UINT64_MAX; } - } else if (cubic->rst->rs.is_app_limited && !cubic->rst->is_cwnd_limited) { + } else if (is_app_limited) { return; } diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.c index 181850cfcbc87a..8bff06c0c1f2da 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.c @@ -141,8 +141,10 @@ int ngtcp2_dcid_verify_uniqueness(const ngtcp2_dcid *dcid, uint64_t seq, } int ngtcp2_dcid_verify_stateless_reset_token(const ngtcp2_dcid *dcid, + const ngtcp2_path *path, const uint8_t *token) { - return (dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) && + return ngtcp2_path_eq(&dcid->ps.path, path) && + (dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) && ngtcp2_cmemeq(dcid->token, token, NGTCP2_STATELESS_RESET_TOKENLEN) ? 0 : NGTCP2_ERR_INVALID_ARGUMENT; diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.h index 6372ef113d6454..c6ab16831a38c4 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_cid.h @@ -149,7 +149,9 @@ void ngtcp2_dcid_copy(ngtcp2_dcid *dest, const ngtcp2_dcid *src); /* * ngtcp2_dcid_copy_cid_token behaves like ngtcp2_dcid_copy, but it - * only copies cid, seq, and token. + * only copies cid, seq, and token. dest->flags should be initialized + * before this call because NGTCP2_DCID_FLAG_TOKEN_PRESENT is set or + * unset. */ void ngtcp2_dcid_copy_cid_token(ngtcp2_dcid *dest, const ngtcp2_dcid *src); @@ -162,15 +164,16 @@ int ngtcp2_dcid_verify_uniqueness(const ngtcp2_dcid *dcid, uint64_t seq, /* * ngtcp2_dcid_verify_stateless_reset_token verifies stateless reset - * token |token| against the one included in |dcid|. Tokens are - * compared in constant time. This function returns 0 if the - * verification succeeds, or one of the following negative error - * codes: + * token |token| received on |path| against the one included in + * |dcid|. Tokens are compared in constant time. This function + * returns 0 if the verification succeeds, or one of the following + * negative error codes: * * NGTCP2_ERR_INVALID_ARGUMENT * Tokens do not match; or |dcid| does not contain a token. */ int ngtcp2_dcid_verify_stateless_reset_token(const ngtcp2_dcid *dcid, + const ngtcp2_path *path, const uint8_t *token); #endif /* !defined(NGTCP2_CID_H) */ diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c index 765e4a877e00d7..59eb90f6ea1afa 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c @@ -297,13 +297,13 @@ static int conn_call_path_validation(ngtcp2_conn *conn, const ngtcp2_pv *pv, flags |= NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR; } - if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) { + if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) { old_path = &pv->fallback_dcid.ps.path; } if (conn->server && old_path && - (ngtcp2_addr_compare(&pv->dcid.ps.path.remote, &old_path->remote) & - (NGTCP2_ADDR_COMPARE_FLAG_ADDR | NGTCP2_ADDR_COMPARE_FLAG_FAMILY))) { + (ngtcp2_addr_cmp(&pv->dcid.ps.path.remote, &old_path->remote) & + (NGTCP2_ADDR_CMP_FLAG_ADDR | NGTCP2_ADDR_CMP_FLAG_FAMILY))) { flags |= NGTCP2_PATH_VALIDATION_FLAG_NEW_TOKEN; } @@ -679,7 +679,6 @@ static void pktns_init(ngtcp2_pktns *pktns, ngtcp2_pktns_id pktns_id, pktns->tx.last_pkt_num = initial_pkt_num - 1; pktns->tx.non_ack_pkt_start_ts = UINT64_MAX; - pktns->rx.max_pkt_num = -1; pktns->rx.max_ack_eliciting_pkt_num = -1; pktns->id = pktns_id; @@ -1073,9 +1072,11 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid, assert(settings->max_tx_udp_payload_size); assert(settings->max_tx_udp_payload_size <= NGTCP2_HARD_MAX_UDP_PAYLOAD_SIZE); assert(settings->initial_pkt_num <= INT32_MAX); + assert(settings->initial_rtt); assert(params->active_connection_id_limit >= NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT); - assert(params->active_connection_id_limit <= NGTCP2_MAX_DCID_POOL_SIZE); + assert(params->active_connection_id_limit <= + NGTCP2_DCIDTR_MAX_UNUSED_DCID_SIZE); assert(params->initial_max_data <= NGTCP2_MAX_VARINT); assert(params->initial_max_stream_data_bidi_local <= NGTCP2_MAX_VARINT); assert(params->initial_max_stream_data_bidi_remote <= NGTCP2_MAX_VARINT); @@ -1157,11 +1158,7 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid, ngtcp2_objalloc_rtb_entry_init(&(*pconn)->rtb_entry_objalloc, 16, mem); ngtcp2_objalloc_strm_init(&(*pconn)->strm_objalloc, 16, mem); - ngtcp2_static_ringbuf_dcid_bound_init(&(*pconn)->dcid.bound); - - ngtcp2_static_ringbuf_dcid_unused_init(&(*pconn)->dcid.unused); - - ngtcp2_static_ringbuf_dcid_retired_init(&(*pconn)->dcid.retired); + ngtcp2_dcidtr_init(&(*pconn)->dcid.dtr); ngtcp2_gaptr_init(&(*pconn)->dcid.seqgap, mem); @@ -1218,6 +1215,11 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid, ngtcp2_dcid_init(&(*pconn)->dcid.current, 0, dcid, NULL); ngtcp2_dcid_set_path(&(*pconn)->dcid.current, path); + assert((size_t)path->local.addrlen <= sizeof((*pconn)->hs_local_addr)); + + memcpy(&(*pconn)->hs_local_addr, path->local.addr, + (size_t)path->local.addrlen); + rv = ngtcp2_gaptr_push(&(*pconn)->dcid.seqgap, 0, 1); if (rv != 0) { goto fail_seqgap_push; @@ -1380,10 +1382,12 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid, (*pconn)->mem = mem; (*pconn)->user_data = user_data; (*pconn)->idle_ts = settings->initial_ts; + (*pconn)->handshake_confirmed_ts = UINT64_MAX; (*pconn)->crypto.key_update.confirmed_ts = UINT64_MAX; (*pconn)->tx.last_max_data_ts = UINT64_MAX; (*pconn)->tx.pacing.next_ts = UINT64_MAX; (*pconn)->tx.last_blocked_offset = UINT64_MAX; + (*pconn)->rx.preferred_addr.pkt_num = -1; (*pconn)->early.discard_started_ts = UINT64_MAX; conn_reset_ecn_validation_state(*pconn); @@ -1608,7 +1612,6 @@ void ngtcp2_conn_del(ngtcp2_conn *conn) { ngtcp2_idtr_free(&conn->remote.uni.idtr); ngtcp2_idtr_free(&conn->remote.bidi.idtr); - ngtcp2_mem_free(conn->mem, conn->tx.ack); ngtcp2_pq_free(&conn->tx.strmq); ngtcp2_map_each(&conn->strms, delete_strms_each, (void *)conn); ngtcp2_map_free(&conn->strms); @@ -1625,40 +1628,6 @@ void ngtcp2_conn_del(ngtcp2_conn *conn) { ngtcp2_mem_free(conn->mem, conn); } -/* - * conn_ensure_ack_ranges makes sure that conn->tx.ack->ack.ranges can - * contain at least |n| additional ngtcp2_ack_range. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGTCP2_ERR_NOMEM - * Out of memory. - */ -static int conn_ensure_ack_ranges(ngtcp2_conn *conn, size_t n) { - ngtcp2_frame *fr; - size_t max = conn->tx.max_ack_ranges; - - if (n <= max) { - return 0; - } - - max *= 2; - - assert(max >= n); - - fr = ngtcp2_mem_realloc(conn->mem, conn->tx.ack, - sizeof(ngtcp2_ack) + sizeof(ngtcp2_ack_range) * max); - if (fr == NULL) { - return NGTCP2_ERR_NOMEM; - } - - conn->tx.ack = fr; - conn->tx.max_ack_ranges = max; - - return 0; -} - /* * conn_compute_ack_delay computes ACK delay for outgoing protected * ACK. @@ -1669,124 +1638,6 @@ static ngtcp2_duration conn_compute_ack_delay(ngtcp2_conn *conn) { ngtcp2_max_uint64(conn->cstat.smoothed_rtt / 8, NGTCP2_NANOSECONDS)); } -int ngtcp2_conn_create_ack_frame(ngtcp2_conn *conn, ngtcp2_frame **pfr, - ngtcp2_pktns *pktns, uint8_t type, - ngtcp2_tstamp ts, ngtcp2_duration ack_delay, - uint64_t ack_delay_exponent) { - /* TODO Measure an actual size of ACK blocks to find the best - default value. */ - const size_t initial_max_ack_ranges = 8; - int64_t last_pkt_num; - ngtcp2_acktr *acktr = &pktns->acktr; - ngtcp2_ack_range *range; - ngtcp2_ksl_it it; - ngtcp2_acktr_entry *rpkt; - ngtcp2_ack *ack; - size_t range_idx; - ngtcp2_tstamp largest_ack_ts; - int rv; - - if (acktr->flags & NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK) { - ack_delay = 0; - } - - if (!ngtcp2_acktr_require_active_ack(acktr, ack_delay, ts)) { - return 0; - } - - it = ngtcp2_acktr_get(acktr); - if (ngtcp2_ksl_it_end(&it)) { - ngtcp2_acktr_commit_ack(acktr); - return 0; - } - - if (conn->tx.ack == NULL) { - conn->tx.ack = ngtcp2_mem_malloc( - conn->mem, - sizeof(ngtcp2_ack) + sizeof(ngtcp2_ack_range) * initial_max_ack_ranges); - if (conn->tx.ack == NULL) { - return NGTCP2_ERR_NOMEM; - } - conn->tx.max_ack_ranges = initial_max_ack_ranges; - } - - ack = &conn->tx.ack->ack; - - if (pktns->rx.ecn.ect0 || pktns->rx.ecn.ect1 || pktns->rx.ecn.ce) { - ack->type = NGTCP2_FRAME_ACK_ECN; - ack->ecn.ect0 = pktns->rx.ecn.ect0; - ack->ecn.ect1 = pktns->rx.ecn.ect1; - ack->ecn.ce = pktns->rx.ecn.ce; - } else { - ack->type = NGTCP2_FRAME_ACK; - } - ack->rangecnt = 0; - - rpkt = ngtcp2_ksl_it_get(&it); - - if (rpkt->pkt_num == pktns->rx.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; - - ngtcp2_ksl_it_next(&it); - } else if (rpkt->pkt_num + 1 == pktns->rx.max_pkt_num) { - last_pkt_num = rpkt->pkt_num - (int64_t)(rpkt->len - 1); - largest_ack_ts = pktns->rx.max_pkt_ts; - ack->largest_ack = pktns->rx.max_pkt_num; - ack->first_ack_range = rpkt->len; - - ngtcp2_ksl_it_next(&it); - } else { - assert(rpkt->pkt_num < pktns->rx.max_pkt_num); - - last_pkt_num = pktns->rx.max_pkt_num; - largest_ack_ts = pktns->rx.max_pkt_ts; - ack->largest_ack = pktns->rx.max_pkt_num; - ack->first_ack_range = 0; - } - - if (type == NGTCP2_PKT_1RTT) { - ack->ack_delay_unscaled = ts - largest_ack_ts; - ack->ack_delay = ack->ack_delay_unscaled / NGTCP2_MICROSECONDS / - (1ULL << ack_delay_exponent); - } else { - ack->ack_delay_unscaled = 0; - ack->ack_delay = 0; - } - - for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) { - if (ack->rangecnt == NGTCP2_MAX_ACK_RANGES) { - break; - } - - rpkt = ngtcp2_ksl_it_get(&it); - - range_idx = ack->rangecnt++; - rv = conn_ensure_ack_ranges(conn, ack->rangecnt); - if (rv != 0) { - return rv; - } - ack = &conn->tx.ack->ack; - range = &ack->ranges[range_idx]; - range->gap = (uint64_t)(last_pkt_num - rpkt->pkt_num - 2); - range->len = rpkt->len - 1; - - last_pkt_num = rpkt->pkt_num - (int64_t)(rpkt->len - 1); - } - - /* TODO Just remove entries which cannot fit into a single ACK frame - for now. */ - if (!ngtcp2_ksl_it_end(&it)) { - ngtcp2_acktr_forget(acktr, ngtcp2_ksl_it_get(&it)); - } - - *pfr = conn->tx.ack; - - return 0; -} - /* * conn_ppe_write_frame writes |fr| to |ppe|. If |hd_logged| is not * NULL and |*hd_logged| is zero, packet header is logged, and 1 is @@ -1894,22 +1745,13 @@ static size_t pktns_select_pkt_numlen(ngtcp2_pktns *pktns) { return 1; } -/* - * conn_get_cwnd returns cwnd for the current path. - */ -static uint64_t conn_get_cwnd(ngtcp2_conn *conn) { - return conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) - ? ngtcp2_cc_compute_initcwnd(conn->cstat.max_tx_udp_payload_size) - : conn->cstat.cwnd; -} - /* * conn_cwnd_is_zero returns nonzero if the number of bytes the local * endpoint can sent at this time is zero. */ static int conn_cwnd_is_zero(ngtcp2_conn *conn) { uint64_t bytes_in_flight = conn->cstat.bytes_in_flight; - uint64_t cwnd = conn_get_cwnd(conn); + uint64_t cwnd = conn->cstat.cwnd; if (bytes_in_flight >= cwnd) { ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_LDC, @@ -2145,33 +1987,36 @@ void ngtcp2_conn_set_keep_alive_timeout(ngtcp2_conn *conn, conn->keep_alive.timeout = timeout; } -/* - * NGTCP2_PKT_PACING_OVERHEAD defines overhead of userspace event - * loop. Packet pacing might require sub milliseconds packet spacing, - * but userspace event loop might not offer such precision. - * Typically, if delay is 0.5 microseconds, the actual delay after - * which we can send packet is well over 1 millisecond when event loop - * is involved (which includes other stuff, like reading packets etc - * in a typical single threaded use case). - */ -#define NGTCP2_PKT_PACING_OVERHEAD NGTCP2_MILLISECONDS - static void conn_cancel_expired_pkt_tx_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts) { if (conn->tx.pacing.next_ts == UINT64_MAX) { return; } - if (conn->tx.pacing.next_ts > ts + NGTCP2_PKT_PACING_OVERHEAD) { + if (conn->tx.pacing.next_ts > ts) { return; } + if (ts > conn->tx.pacing.next_ts) { + conn->tx.pacing.compensation += ts - conn->tx.pacing.next_ts; + } + conn->tx.pacing.next_ts = UINT64_MAX; } static int conn_pacing_pkt_tx_allowed(ngtcp2_conn *conn, ngtcp2_tstamp ts) { - return conn->tx.pacing.next_ts == UINT64_MAX || - conn->tx.pacing.next_ts <= ts + NGTCP2_PKT_PACING_OVERHEAD; + if (conn->tx.pacing.next_ts == UINT64_MAX) { + return 1; + } + + if (conn->tx.pacing.next_ts > ts) { + return 0; + } + + conn->tx.pacing.compensation += ts - conn->tx.pacing.next_ts; + conn->tx.pacing.next_ts = UINT64_MAX; + + return 1; } static uint8_t conn_pkt_flags(ngtcp2_conn *conn) { @@ -2225,6 +2070,7 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, ngtcp2_pkt_hd hd; ngtcp2_frame_chain *frq = NULL, **pfrc = &frq; ngtcp2_frame_chain *nfrc; + ngtcp2_max_frame mfr; ngtcp2_frame *ackfr = NULL, lfr; ngtcp2_ssize spktlen; ngtcp2_crypto_cc cc; @@ -2280,7 +2126,7 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, ngtcp2_pkt_hd_init( &hd, conn_pkt_flags_long(conn), type, &conn->dcid.current.cid, &conn->oscid, - pktns->tx.last_pkt_num + 1, pktns_select_pkt_numlen(pktns), version, 0); + pktns->tx.last_pkt_num + 1, pktns_select_pkt_numlen(pktns), version); if (!conn->server && type == NGTCP2_PKT_INITIAL && conn->local.settings.tokenlen) { @@ -2300,14 +2146,9 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, return 0; } - rv = ngtcp2_conn_create_ack_frame(conn, &ackfr, pktns, type, ts, - /* ack_delay = */ 0, - NGTCP2_DEFAULT_ACK_DELAY_EXPONENT); - if (rv != 0) { - ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, conn->mem); - return rv; - } - + ackfr = ngtcp2_acktr_create_ack_frame(&pktns->acktr, &mfr.fr, type, ts, + /* ack_delay = */ 0, + NGTCP2_DEFAULT_ACK_DELAY_EXPONENT); if (ackfr) { rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, ackfr); if (rv != 0) { @@ -2530,12 +2371,12 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, static ngtcp2_ssize conn_write_ack_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, uint8_t type, ngtcp2_tstamp ts) { - int rv; ngtcp2_frame *ackfr; ngtcp2_pktns *pktns; ngtcp2_duration ack_delay; uint64_t ack_delay_exponent; ngtcp2_ssize spktlen; + ngtcp2_max_frame mfr; assert(!(conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING)); @@ -2564,13 +2405,8 @@ static ngtcp2_ssize conn_write_ack_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, return 0; } - ackfr = NULL; - rv = ngtcp2_conn_create_ack_frame(conn, &ackfr, pktns, type, ts, ack_delay, - ack_delay_exponent); - if (rv != 0) { - return rv; - } - + ackfr = ngtcp2_acktr_create_ack_frame(&pktns->acktr, &mfr.fr, type, ts, + ack_delay, ack_delay_exponent); if (!ackfr) { return 0; } @@ -3035,6 +2871,10 @@ static int conn_enqueue_new_connection_id(ngtcp2_conn *conn) { return 0; } +static int dcidtr_on_deactivate(const ngtcp2_dcid *dcid, void *user_data) { + return conn_call_deactivate_dcid(user_data, dcid); +} + /* * conn_remove_retired_connection_id removes the already retired * connection ID. It waits PTO before actually removing a connection @@ -3054,7 +2894,6 @@ static int conn_remove_retired_connection_id(ngtcp2_conn *conn, ngtcp2_tstamp ts) { ngtcp2_duration timeout = pto; ngtcp2_scid *scid; - ngtcp2_dcid *dcid; int rv; for (; !ngtcp2_pq_empty(&conn->scid.used);) { @@ -3079,18 +2918,10 @@ static int conn_remove_retired_connection_id(ngtcp2_conn *conn, --conn->scid.num_retired; } - for (; ngtcp2_ringbuf_len(&conn->dcid.retired.rb);) { - dcid = ngtcp2_ringbuf_get(&conn->dcid.retired.rb, 0); - if (dcid->retired_ts + timeout >= ts) { - break; - } - - rv = conn_call_deactivate_dcid(conn, dcid); - if (rv != 0) { - return rv; - } - - ngtcp2_ringbuf_pop_front(&conn->dcid.retired.rb); + rv = ngtcp2_dcidtr_remove_stale_retired_dcid(&conn->dcid.dtr, timeout, ts, + dcidtr_on_deactivate, conn); + if (rv != 0) { + return rv; } return 0; @@ -3197,6 +3028,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, ngtcp2_crypto_cc *cc = &conn->pkt.cc; ngtcp2_ppe *ppe = &conn->pkt.ppe; ngtcp2_pkt_hd *hd = &conn->pkt.hd; + ngtcp2_max_frame mfr; ngtcp2_frame *ackfr = NULL, lfr; ngtcp2_ssize nwrite; ngtcp2_frame_chain **pfrc, *nfrc, *frc; @@ -3348,35 +3180,9 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, nfrc->fr.max_data.max_data; } - if (stream_blocked && conn_should_send_max_data(conn)) { - rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); - if (rv != 0) { - return rv; - } - - nfrc->fr.type = NGTCP2_FRAME_DATA_BLOCKED; - nfrc->fr.data_blocked.offset = conn->tx.max_offset; - nfrc->next = pktns->tx.frq; - pktns->tx.frq = nfrc; - - conn->tx.last_blocked_offset = conn->tx.max_offset; - } - - if (stream_blocked && !ngtcp2_strm_is_tx_queued(vmsg->stream.strm) && - strm_should_send_stream_data_blocked(vmsg->stream.strm)) { - assert(vmsg); - assert(vmsg->type == NGTCP2_VMSG_TYPE_STREAM); - - vmsg->stream.strm->cycle = conn_tx_strmq_first_cycle(conn); - rv = ngtcp2_conn_tx_strmq_push(conn, vmsg->stream.strm); - if (rv != 0) { - return rv; - } - } - ngtcp2_pkt_hd_init(hd, hd_flags, type, &conn->dcid.current.cid, scid, pktns->tx.last_pkt_num + 1, - pktns_select_pkt_numlen(pktns), version, 0); + pktns_select_pkt_numlen(pktns), version); ngtcp2_ppe_init(ppe, dest, destlen, dgram_offset, cc); @@ -3415,14 +3221,9 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, } } - rv = ngtcp2_conn_create_ack_frame( - conn, &ackfr, pktns, type, ts, conn_compute_ack_delay(conn), + ackfr = ngtcp2_acktr_create_ack_frame( + &pktns->acktr, &mfr.fr, type, ts, conn_compute_ack_delay(conn), conn->local.transport_params.ack_delay_exponent); - if (rv != 0) { - assert(ngtcp2_err_is_fatal(rv)); - return rv; - } - if (ackfr) { rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, ackfr); if (rv != 0) { @@ -3907,7 +3708,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, left = ngtcp2_ppe_left(ppe); - if (*pfrc == NULL && send_stream && + if (*pfrc == NULL && send_stream && ngtcp2_pq_empty(&conn->tx.strmq) && (ndatalen = ngtcp2_pkt_stream_max_datalen( vmsg->stream.strm->stream_id, vmsg->stream.strm->tx.offset, ndatalen, left)) != (size_t)-1 && @@ -4342,7 +4143,7 @@ ngtcp2_ssize ngtcp2_conn_write_single_frame_pkt( ngtcp2_pkt_hd_init(&hd, hd_flags, type, dcid, scid, pktns->tx.last_pkt_num + 1, pktns_select_pkt_numlen(pktns), - version, 0); + version); ngtcp2_ppe_init(&ppe, dest, destlen, 0, &cc); @@ -4521,31 +4322,20 @@ static int conn_handshake_remnants_left(ngtcp2_conn *conn) { } /* - * conn_retire_dcid_seq retires destination connection ID denoted by - * |seq|. + * conn_enqueue_retire_connection_id enqueues RETIRE_CONNECTION_ID + * frame with |seq|. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGTCP2_ERR_NOMEM * Out of memory. - * NGTCP2_ERR_CONNECTION_ID_LIMIT - * The number of unacknowledged retirement exceeds the limit. */ -static int conn_retire_dcid_seq(ngtcp2_conn *conn, uint64_t seq) { +static int conn_enqueue_retire_connection_id(ngtcp2_conn *conn, uint64_t seq) { ngtcp2_pktns *pktns = &conn->pktns; ngtcp2_frame_chain *nfrc; int rv; - if (ngtcp2_conn_check_retired_dcid_tracked(conn, seq)) { - return 0; - } - - rv = ngtcp2_conn_track_retired_dcid_seq(conn, seq); - if (rv != 0) { - return rv; - } - rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); if (rv != 0) { return rv; @@ -4559,8 +4349,12 @@ static int conn_retire_dcid_seq(ngtcp2_conn *conn, uint64_t seq) { return 0; } +static int dcidtr_on_retire(const ngtcp2_dcid *dcid, void *user_data) { + return conn_enqueue_retire_connection_id(user_data, dcid->seq); +} + /* - * conn_retire_dcid retires |dcid|. + * conn_retire_active_dcid retires the activated |dcid|. * * This function returns 0 if it succeeds, or one of the following * negative error codes: @@ -4568,29 +4362,19 @@ static int conn_retire_dcid_seq(ngtcp2_conn *conn, uint64_t seq) { * NGTCP2_ERR_NOMEM * Out of memory */ -static int conn_retire_dcid(ngtcp2_conn *conn, const ngtcp2_dcid *dcid, - ngtcp2_tstamp ts) { - ngtcp2_ringbuf *rb = &conn->dcid.retired.rb; - ngtcp2_dcid *dest, *stale_dcid; +static int conn_retire_active_dcid(ngtcp2_conn *conn, const ngtcp2_dcid *dcid, + ngtcp2_tstamp ts) { int rv; assert(dcid->cid.datalen); - if (ngtcp2_ringbuf_full(rb)) { - stale_dcid = ngtcp2_ringbuf_get(rb, 0); - rv = conn_call_deactivate_dcid(conn, stale_dcid); - if (rv != 0) { - return rv; - } - - ngtcp2_ringbuf_pop_front(rb); + rv = ngtcp2_dcidtr_retire_active_dcid(&conn->dcid.dtr, dcid, ts, + dcidtr_on_deactivate, conn); + if (rv != 0) { + return rv; } - dest = ngtcp2_ringbuf_push_back(rb); - ngtcp2_dcid_copy(dest, dcid); - dest->retired_ts = ts; - - return conn_retire_dcid_seq(conn, dcid->seq); + return conn_enqueue_retire_connection_id(conn, dcid->seq); } /* @@ -4609,61 +4393,28 @@ static int conn_retire_dcid(ngtcp2_conn *conn, const ngtcp2_dcid *dcid, */ static int conn_bind_dcid(ngtcp2_conn *conn, ngtcp2_dcid **pdcid, const ngtcp2_path *path, ngtcp2_tstamp ts) { - ngtcp2_dcid *dcid, *ndcid; - ngtcp2_cid cid; - size_t i, len; - int rv; - assert(!ngtcp2_path_eq(&conn->dcid.current.ps.path, path)); assert(!conn->pv || !ngtcp2_path_eq(&conn->pv->dcid.ps.path, path)); - assert(!conn->pv || !(conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) || + assert(!conn->pv || !(conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) || !ngtcp2_path_eq(&conn->pv->fallback_dcid.ps.path, path)); - len = ngtcp2_ringbuf_len(&conn->dcid.bound.rb); - for (i = 0; i < len; ++i) { - dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); - - if (ngtcp2_path_eq(&dcid->ps.path, path)) { - *pdcid = dcid; - return 0; - } + *pdcid = ngtcp2_dcidtr_find_bound_dcid(&conn->dcid.dtr, path); + if (*pdcid) { + return 0; } if (conn->dcid.current.cid.datalen == 0) { - ndcid = ngtcp2_ringbuf_push_back(&conn->dcid.bound.rb); - ngtcp2_cid_zero(&cid); - ngtcp2_dcid_init(ndcid, ++conn->dcid.zerolen_seq, &cid, NULL); - ngtcp2_dcid_set_path(ndcid, path); - - *pdcid = ndcid; + *pdcid = ngtcp2_dcidtr_bind_zerolen_dcid(&conn->dcid.dtr, path); return 0; } - if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb) == 0) { + if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) { return NGTCP2_ERR_CONN_ID_BLOCKED; } - if (ngtcp2_ringbuf_full(&conn->dcid.bound.rb)) { - dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, 0); - rv = conn_retire_dcid(conn, dcid, ts); - if (rv != 0) { - return rv; - } - } - - dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); - ndcid = ngtcp2_ringbuf_push_back(&conn->dcid.bound.rb); - - ngtcp2_dcid_copy(ndcid, dcid); - ndcid->bound_ts = ts; - ngtcp2_dcid_set_path(ndcid, path); - - ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); - - *pdcid = ndcid; - - return 0; + return ngtcp2_dcidtr_bind_dcid(&conn->dcid.dtr, pdcid, path, ts, + dcidtr_on_retire, conn); } static int conn_start_pmtud(ngtcp2_conn *conn) { @@ -4773,17 +4524,17 @@ static int conn_stop_pv(ngtcp2_conn *conn, ngtcp2_tstamp ts) { } if (pv->dcid.cid.datalen && pv->dcid.seq != conn->dcid.current.seq) { - rv = conn_retire_dcid(conn, &pv->dcid, ts); + rv = conn_retire_active_dcid(conn, &pv->dcid, ts); if (rv != 0) { goto fin; } } - if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) && pv->fallback_dcid.cid.datalen && pv->fallback_dcid.seq != conn->dcid.current.seq && pv->fallback_dcid.seq != pv->dcid.seq) { - rv = conn_retire_dcid(conn, &pv->fallback_dcid, ts); + rv = conn_retire_active_dcid(conn, &pv->fallback_dcid, ts); if (rv != 0) { goto fin; } @@ -4874,7 +4625,7 @@ static int conn_on_path_validation_failed(ngtcp2_conn *conn, ngtcp2_pv *pv, } } - if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) { + if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) { ngtcp2_dcid_copy(&conn->dcid.current, &pv->fallback_dcid); conn_reset_congestion_state(conn, ts); } @@ -5024,7 +4775,7 @@ static ngtcp2_ssize conn_write_path_response(ngtcp2_conn *conn, dcid = &pv->dcid; break; } - if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) && ngtcp2_path_eq(&pv->fallback_dcid.ps.path, &pcent->ps.path)) { dcid = &pv->fallback_dcid; break; @@ -5377,8 +5128,15 @@ static int conn_recv_ack(ngtcp2_conn *conn, ngtcp2_pktns *pktns, ngtcp2_ack *fr, */ static void assign_recved_ack_delay_unscaled(ngtcp2_ack *fr, uint64_t ack_delay_exponent) { - fr->ack_delay_unscaled = - fr->ack_delay * (1ULL << ack_delay_exponent) * NGTCP2_MICROSECONDS; + const ngtcp2_tstamp max_ack_delay = ((1 << 14) - 1) * NGTCP2_MILLISECONDS; + uint64_t exp = (1ULL << ack_delay_exponent) * NGTCP2_MICROSECONDS; + + if (fr->ack_delay > max_ack_delay / exp) { + fr->ack_delay_unscaled = max_ack_delay; + return; + } + + fr->ack_delay_unscaled = fr->ack_delay * exp; } /* @@ -5482,6 +5240,17 @@ static void conn_recv_max_data(ngtcp2_conn *conn, const ngtcp2_max_data *fr) { conn->tx.max_offset = ngtcp2_max_uint64(conn->tx.max_offset, fr->max_data); } +/* + * should_buffer_1rtt_pkt returns nonzero if 1RTT packet |pkt| of + * length |pktlen| should be buffered. + */ +static int should_buffer_1rtt_pkt(const uint8_t *pkt, size_t pktlen) { + /* A packet starting with 21 bytes zeros are most likely padding + bytes. */ + return pktlen >= NGTCP2_MIN_QUIC_PKTLEN && + (pkt[0] != 0 || memcmp(pkt, pkt + 1, NGTCP2_MIN_QUIC_PKTLEN - 1) != 0); +} + /* * conn_buffer_pkt buffers |pkt| of length |pktlen|, chaining it from * |*ppc|. @@ -5786,15 +5555,52 @@ static void conn_reset_congestion_state(ngtcp2_conn *conn, ngtcp2_tstamp ts) { conn->hs_pktns->tx.last_pkt_num + 1); } ngtcp2_rtb_reset_cc_state(&conn->pktns.rtb, conn->pktns.tx.last_pkt_num + 1); - ngtcp2_rst_init(&conn->rst); + ngtcp2_rst_reset(&conn->rst); conn->tx.pacing.next_ts = UINT64_MAX; + conn->tx.pacing.compensation = 0; } -static int conn_recv_path_response(ngtcp2_conn *conn, ngtcp2_path_response *fr, - ngtcp2_tstamp ts) { +/* + * conn_server_preferred_addr_migration returns nonzero if + * |local_addr| equals to one of the preferred addresses. + */ +static int conn_server_preferred_addr_migration(const ngtcp2_conn *conn, + const ngtcp2_addr *local_addr) { + const ngtcp2_preferred_addr *paddr; + + assert(conn->server); + + if (!conn->local.transport_params.preferred_addr_present) { + return 0; + } + + paddr = &conn->local.transport_params.preferred_addr; + + switch (local_addr->addr->sa_family) { + case NGTCP2_AF_INET: + if (!paddr->ipv4_present) { + return 0; + } + + return ngtcp2_sockaddr_eq((const ngtcp2_sockaddr *)&paddr->ipv4, + local_addr->addr); + case NGTCP2_AF_INET6: + if (!paddr->ipv6_present) { + return 0; + } + + return ngtcp2_sockaddr_eq((const ngtcp2_sockaddr *)&paddr->ipv6, + local_addr->addr); + } + + return 0; +} + +static int conn_recv_path_response(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, + ngtcp2_path_response *fr, ngtcp2_tstamp ts) { int rv; - ngtcp2_pv *pv = conn->pv, *npv; + ngtcp2_pv *pv = conn->pv, *npv = NULL; uint8_t ent_flags; if (!pv) { @@ -5809,18 +5615,26 @@ static int conn_recv_path_response(ngtcp2_conn *conn, ngtcp2_path_response *fr, } if (!(pv->flags & NGTCP2_PV_FLAG_DONT_CARE)) { - if (pv->dcid.seq != conn->dcid.current.seq) { - assert(!conn->server); - assert(conn->dcid.current.cid.datalen); - - rv = conn_retire_dcid(conn, &conn->dcid.current, ts); - if (rv != 0) { - return rv; + if (pv->dcid.seq != conn->dcid.current.seq || + !ngtcp2_path_eq(&pv->dcid.ps.path, &conn->dcid.current.ps.path)) { + if (conn->dcid.current.cid.datalen && + pv->dcid.seq != conn->dcid.current.seq) { + rv = conn_retire_active_dcid(conn, &conn->dcid.current, ts); + if (rv != 0) { + return rv; + } } + ngtcp2_dcid_copy(&conn->dcid.current, &pv->dcid); conn_reset_congestion_state(conn, ts); conn_reset_ecn_validation_state(conn); + + if (conn->server && conn->rx.preferred_addr.pkt_num == -1 && + conn_server_preferred_addr_migration( + conn, &conn->dcid.current.ps.path.local)) { + conn->rx.preferred_addr.pkt_num = hd->pkt_num; + } } assert(ngtcp2_path_eq(&pv->dcid.ps.path, &conn->dcid.current.ps.path)); @@ -5847,46 +5661,45 @@ static int conn_recv_path_response(ngtcp2_conn *conn, ngtcp2_path_response *fr, } } - if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) { - if (ent_flags & NGTCP2_PV_ENTRY_FLAG_UNDERSIZED) { - assert(conn->server); - - /* Validate path again */ - rv = ngtcp2_pv_new(&npv, &pv->dcid, conn_compute_pv_timeout(conn), - NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE, &conn->log, - conn->mem); - if (rv != 0) { - return rv; - } + if (ent_flags & NGTCP2_PV_ENTRY_FLAG_UNDERSIZED) { + assert(conn->server); - npv->dcid.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED; - ngtcp2_dcid_copy(&npv->fallback_dcid, &pv->fallback_dcid); - npv->fallback_pto = pv->fallback_pto; - } else { - rv = ngtcp2_pv_new(&npv, &pv->fallback_dcid, - conn_compute_pv_timeout_pto(conn, pv->fallback_pto), - NGTCP2_PV_FLAG_DONT_CARE, &conn->log, conn->mem); - if (rv != 0) { - return rv; - } + /* Validate path again */ + rv = ngtcp2_pv_new(&npv, &pv->dcid, conn_compute_pv_timeout(conn), + NGTCP2_PV_FLAG_NONE, &conn->log, conn->mem); + if (rv != 0) { + return rv; } - /* Unset the flag bit so that conn_stop_pv does not retire - DCID. */ - pv->flags &= (uint8_t)~NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE; + npv->dcid.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED; - rv = conn_stop_pv(conn, ts); + if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) { + ngtcp2_pv_set_fallback(npv, &pv->fallback_dcid, pv->fallback_pto); + } + } else if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) { + rv = ngtcp2_pv_new(&npv, &pv->fallback_dcid, + conn_compute_pv_timeout_pto(conn, pv->fallback_pto), + NGTCP2_PV_FLAG_DONT_CARE, &conn->log, conn->mem); if (rv != 0) { - ngtcp2_pv_del(npv); return rv; } + } - conn->pv = npv; + if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) { + /* Unset the flag bit so that conn_stop_pv does not retire + DCID. */ + pv->flags &= (uint8_t)~NGTCP2_PV_FLAG_FALLBACK_PRESENT; + } - return 0; + rv = conn_stop_pv(conn, ts); + if (rv != 0) { + ngtcp2_pv_del(npv); + return rv; } - return conn_stop_pv(conn, ts); + conn->pv = npv; + + return 0; } /* @@ -5899,12 +5712,24 @@ static int pktns_pkt_num_is_duplicate(ngtcp2_pktns *pktns, int64_t pkt_num) { /* * pktns_commit_recv_pkt_num marks packet number |pkt_num| as - * received. + * received. It stores |pkt_num| and its reception timestamp |ts| in + * order to send its ACK. It also increase ECN counts from |pi|. + * |require_ack| is nonzero if the received packet is ack-eliciting. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_PROTO + * Same packet number has already been added. */ static int pktns_commit_recv_pkt_num(ngtcp2_pktns *pktns, int64_t pkt_num, - int ack_eliciting, ngtcp2_tstamp ts) { - int rv; + const ngtcp2_pkt_info *pi, int require_ack, + ngtcp2_tstamp ts) { + ngtcp2_acktr *acktr = &pktns->acktr; ngtcp2_range r; + int rv; rv = ngtcp2_gaptr_push(&pktns->rx.pngap, (uint64_t)pkt_num, 1); if (rv != 0) { @@ -5915,7 +5740,7 @@ static int pktns_commit_recv_pkt_num(ngtcp2_pktns *pktns, int64_t pkt_num, ngtcp2_gaptr_drop_first_gap(&pktns->rx.pngap); } - if (ack_eliciting) { + if (require_ack) { if (pktns->rx.max_ack_eliciting_pkt_num != -1) { if (pkt_num < pktns->rx.max_ack_eliciting_pkt_num) { ngtcp2_acktr_immediate_ack(&pktns->acktr); @@ -5934,9 +5759,12 @@ static int pktns_commit_recv_pkt_num(ngtcp2_pktns *pktns, int64_t pkt_num, } } - if (pktns->rx.max_pkt_num < pkt_num) { - pktns->rx.max_pkt_num = pkt_num; - pktns->rx.max_pkt_ts = ts; + ngtcp2_acktr_increase_ecn_counts(acktr, pi); + + rv = ngtcp2_acktr_add(acktr, pkt_num, require_ack, ts); + if (rv != 0) { + assert(rv != NGTCP2_ERR_INVALID_ARGUMENT); + return rv; } return 0; @@ -5954,21 +5782,6 @@ static int verify_token(const uint8_t *token, size_t tokenlen, return NGTCP2_ERR_PROTO; } -static void pktns_increase_ecn_counts(ngtcp2_pktns *pktns, - const ngtcp2_pkt_info *pi) { - switch (pi->ecn & NGTCP2_ECN_MASK) { - case NGTCP2_ECN_ECT_0: - ++pktns->rx.ecn.ect0; - break; - case NGTCP2_ECN_ECT_1: - ++pktns->rx.ecn.ect1; - break; - case NGTCP2_ECN_CE: - ++pktns->rx.ecn.ce; - break; - } -} - /* * vneg_available_versions_includes returns nonzero if * |available_versions| of length |available_versionslen| includes @@ -6111,14 +5924,11 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, int invalid_reserved_bits = 0; size_t num_ack_processed = 0; - if (pktlen == 0) { - return 0; - } - if (!(pkt[0] & NGTCP2_HEADER_FORM_BIT)) { - if (conn->state == NGTCP2_CS_SERVER_INITIAL) { - /* Ignore 1RTT packet unless server's first Handshake packet has - been transmitted. */ + /* Ignore 1RTT packet unless server's first Handshake packet has + been transmitted. */ + if (conn->state == NGTCP2_CS_SERVER_INITIAL || + !should_buffer_1rtt_pkt(pkt, pktlen)) { return (ngtcp2_ssize)pktlen; } @@ -6432,8 +6242,8 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, payload = pkt + hdpktlen; payloadlen = hd.len - hd.pkt_numlen; - hd.pkt_num = - ngtcp2_pkt_adjust_pkt_num(pktns->rx.max_pkt_num, hd.pkt_num, hd.pkt_numlen); + hd.pkt_num = ngtcp2_pkt_adjust_pkt_num(pktns->acktr.max_pkt_num, hd.pkt_num, + hd.pkt_numlen); if (hd.pkt_num > NGTCP2_MAX_PKT_NUM) { ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, "pkn=%" PRId64 " is greater than maximum pkn", hd.pkt_num); @@ -6623,20 +6433,13 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, ngtcp2_qlog_pkt_received_end(&conn->qlog, &hd, pktlen); - rv = pktns_commit_recv_pkt_num(pktns, hd.pkt_num, require_ack, pkt_ts); + rv = pktns_commit_recv_pkt_num(pktns, hd.pkt_num, pi, require_ack, pkt_ts); if (rv != 0) { return rv; } - pktns_increase_ecn_counts(pktns, pi); - /* Initial and Handshake are always acknowledged without delay. No need to call ngtcp2_acktr_immediate_ack(). */ - rv = - ngtcp2_conn_sched_ack(conn, &pktns->acktr, hd.pkt_num, require_ack, pkt_ts); - if (rv != 0) { - return rv; - } conn_restart_timer_on_read(conn, ts); @@ -6710,7 +6513,7 @@ static ngtcp2_ssize conn_recv_handshake_cpkt(ngtcp2_conn *conn, and the current connection state might wrongly discard valid packet and prevent the handshake from completing. */ - if (conn->in_pktns && conn->in_pktns->rx.max_pkt_num == -1) { + if (conn->in_pktns && conn->in_pktns->acktr.max_pkt_num == -1) { return NGTCP2_ERR_DROP_CONN; } @@ -7516,9 +7319,8 @@ static int conn_recv_stop_sending(ngtcp2_conn *conn, static int check_stateless_reset(const ngtcp2_dcid *dcid, const ngtcp2_path *path, const ngtcp2_pkt_stateless_reset *sr) { - return ngtcp2_path_eq(&dcid->ps.path, path) && - ngtcp2_dcid_verify_stateless_reset_token( - dcid, sr->stateless_reset_token) == 0; + return ngtcp2_dcid_verify_stateless_reset_token( + dcid, path, sr->stateless_reset_token) == 0; } /* @@ -7540,11 +7342,9 @@ static int check_stateless_reset(const ngtcp2_dcid *dcid, */ static int conn_on_stateless_reset(ngtcp2_conn *conn, const ngtcp2_path *path, const uint8_t *payload, size_t payloadlen) { - int rv = 1; + int rv; ngtcp2_pv *pv = conn->pv; - ngtcp2_dcid *dcid; ngtcp2_pkt_stateless_reset sr; - size_t len, i; rv = ngtcp2_pkt_decode_stateless_reset(&sr, payload, payloadlen); if (rv != 0) { @@ -7553,28 +7353,12 @@ static int conn_on_stateless_reset(ngtcp2_conn *conn, const ngtcp2_path *path, if (!check_stateless_reset(&conn->dcid.current, path, &sr) && (!pv || (!check_stateless_reset(&pv->dcid, path, &sr) && - (!(pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) || + (!(pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) || !check_stateless_reset(&pv->fallback_dcid, path, &sr))))) { - len = ngtcp2_ringbuf_len(&conn->dcid.retired.rb); - for (i = 0; i < len; ++i) { - dcid = ngtcp2_ringbuf_get(&conn->dcid.retired.rb, i); - if (check_stateless_reset(dcid, path, &sr)) { - break; - } - } - - if (i == len) { - len = ngtcp2_ringbuf_len(&conn->dcid.bound.rb); - for (i = 0; i < len; ++i) { - dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); - if (check_stateless_reset(dcid, path, &sr)) { - break; - } - } - - if (i == len) { - return NGTCP2_ERR_INVALID_ARGUMENT; - } + rv = ngtcp2_dcidtr_verify_stateless_reset(&conn->dcid.dtr, path, + sr.stateless_reset_token); + if (rv != 0) { + return rv; } } @@ -7624,42 +7408,6 @@ static int conn_recv_max_streams(ngtcp2_conn *conn, return 0; } -static int conn_retire_dcid_prior_to(ngtcp2_conn *conn, ngtcp2_ringbuf *rb, - uint64_t retire_prior_to) { - size_t i; - ngtcp2_dcid *dcid, *last; - int rv; - - for (i = 0; i < ngtcp2_ringbuf_len(rb);) { - dcid = ngtcp2_ringbuf_get(rb, i); - if (dcid->seq >= retire_prior_to) { - ++i; - continue; - } - - rv = conn_retire_dcid_seq(conn, dcid->seq); - if (rv != 0) { - return rv; - } - - if (i == 0) { - ngtcp2_ringbuf_pop_front(rb); - continue; - } - - if (i == ngtcp2_ringbuf_len(rb) - 1) { - ngtcp2_ringbuf_pop_back(rb); - break; - } - - last = ngtcp2_ringbuf_get(rb, ngtcp2_ringbuf_len(rb) - 1); - ngtcp2_dcid_copy(dcid, last); - ngtcp2_ringbuf_pop_back(rb); - } - - return 0; -} - /* * conn_recv_new_connection_id processes the incoming * NEW_CONNECTION_ID frame |fr|. @@ -7673,8 +7421,7 @@ static int conn_retire_dcid_prior_to(ngtcp2_conn *conn, ngtcp2_ringbuf *rb, */ static int conn_recv_new_connection_id(ngtcp2_conn *conn, const ngtcp2_new_connection_id *fr) { - size_t i, len; - ngtcp2_dcid *dcid; + size_t len; ngtcp2_pv *pv = conn->pv; int rv; int found = 0; @@ -7693,60 +7440,32 @@ static int conn_recv_new_connection_id(ngtcp2_conn *conn, if (rv != 0) { return rv; } - if (ngtcp2_cid_eq(&conn->dcid.current.cid, &fr->cid)) { - found = 1; - } - - if (pv) { - rv = ngtcp2_dcid_verify_uniqueness(&pv->dcid, fr->seq, &fr->cid, - fr->stateless_reset_token); - if (rv != 0) { - return rv; - } - if (ngtcp2_cid_eq(&pv->dcid.cid, &fr->cid)) { - found = 1; - } + if (ngtcp2_cid_eq(&conn->dcid.current.cid, &fr->cid)) { + found = 1; } - len = ngtcp2_ringbuf_len(&conn->dcid.bound.rb); - - for (i = 0; i < len; ++i) { - dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); - rv = ngtcp2_dcid_verify_uniqueness(dcid, fr->seq, &fr->cid, + if (pv) { + rv = ngtcp2_dcid_verify_uniqueness(&pv->dcid, fr->seq, &fr->cid, fr->stateless_reset_token); if (rv != 0) { - return NGTCP2_ERR_PROTO; + return rv; } - if (ngtcp2_cid_eq(&dcid->cid, &fr->cid)) { + if (ngtcp2_cid_eq(&pv->dcid.cid, &fr->cid)) { found = 1; } } - len = ngtcp2_ringbuf_len(&conn->dcid.unused.rb); - - for (i = 0; i < len; ++i) { - dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, i); - rv = ngtcp2_dcid_verify_uniqueness(dcid, fr->seq, &fr->cid, - fr->stateless_reset_token); - if (rv != 0) { - return NGTCP2_ERR_PROTO; - } - if (ngtcp2_cid_eq(&dcid->cid, &fr->cid)) { - found = 1; - } + rv = ngtcp2_dcidtr_verify_token_uniqueness( + &conn->dcid.dtr, &found, fr->seq, &fr->cid, fr->stateless_reset_token); + if (rv != 0) { + return rv; } if (conn->dcid.retire_prior_to < fr->retire_prior_to) { conn->dcid.retire_prior_to = fr->retire_prior_to; - rv = conn_retire_dcid_prior_to(conn, &conn->dcid.bound.rb, - fr->retire_prior_to); - if (rv != 0) { - return rv; - } - - rv = conn_retire_dcid_prior_to(conn, &conn->dcid.unused.rb, - conn->dcid.retire_prior_to); + rv = ngtcp2_dcidtr_retire_inactive_dcid_prior_to( + &conn->dcid.dtr, fr->retire_prior_to, dcidtr_on_retire, conn); if (rv != 0) { return rv; } @@ -7759,7 +7478,16 @@ static int conn_recv_new_connection_id(ngtcp2_conn *conn, For example, a peer might send seq = 50000 and retire_prior_to = 50000. Then send NEW_CONNECTION_ID frames with seq < 50000. */ - return conn_retire_dcid_seq(conn, fr->seq); + if (ngtcp2_dcidtr_check_retired_seq_tracked(&conn->dcid.dtr, fr->seq)) { + return 0; + } + + rv = ngtcp2_dcidtr_track_retired_seq(&conn->dcid.dtr, fr->seq); + if (rv != 0) { + return rv; + } + + return conn_enqueue_retire_connection_id(conn, fr->seq); } if (found) { @@ -7779,7 +7507,7 @@ static int conn_recv_new_connection_id(ngtcp2_conn *conn, ngtcp2_gaptr_drop_first_gap(&conn->dcid.seqgap); } - len = ngtcp2_ringbuf_len(&conn->dcid.unused.rb); + len = ngtcp2_dcidtr_inactive_len(&conn->dcid.dtr); if (conn->dcid.current.seq >= conn->dcid.retire_prior_to) { ++extra_dcid; @@ -7789,7 +7517,7 @@ static int conn_recv_new_connection_id(ngtcp2_conn *conn, pv->dcid.seq >= conn->dcid.retire_prior_to) { ++extra_dcid; } - if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) && pv->fallback_dcid.seq != conn->dcid.current.seq && pv->fallback_dcid.seq >= conn->dcid.retire_prior_to) { ++extra_dcid; @@ -7801,13 +7529,13 @@ static int conn_recv_new_connection_id(ngtcp2_conn *conn, return NGTCP2_ERR_CONNECTION_ID_LIMIT; } - if (len >= NGTCP2_MAX_DCID_POOL_SIZE) { + if (len >= NGTCP2_DCIDTR_MAX_UNUSED_DCID_SIZE) { ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "too many connection ID"); return 0; } - dcid = ngtcp2_ringbuf_push_back(&conn->dcid.unused.rb); - ngtcp2_dcid_init(dcid, fr->seq, &fr->cid, fr->stateless_reset_token); + ngtcp2_dcidtr_push_unused(&conn->dcid.dtr, fr->seq, &fr->cid, + fr->stateless_reset_token); return 0; } @@ -7827,32 +7555,32 @@ static int conn_recv_new_connection_id(ngtcp2_conn *conn, static int conn_post_process_recv_new_connection_id(ngtcp2_conn *conn, ngtcp2_tstamp ts) { ngtcp2_pv *pv = conn->pv; - ngtcp2_dcid *dcid; + ngtcp2_dcid dcid; int rv; if (conn->dcid.current.seq < conn->dcid.retire_prior_to) { - if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb) == 0) { + if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) { return 0; } - rv = conn_retire_dcid(conn, &conn->dcid.current, ts); + rv = conn_retire_active_dcid(conn, &conn->dcid.current, ts); if (rv != 0) { return rv; } - dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + ngtcp2_dcidtr_pop_unused_cid_token(&conn->dcid.dtr, &dcid); + if (pv) { if (conn->dcid.current.seq == pv->dcid.seq) { - ngtcp2_dcid_copy_cid_token(&pv->dcid, dcid); + ngtcp2_dcid_copy_cid_token(&pv->dcid, &dcid); } - if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) && conn->dcid.current.seq == pv->fallback_dcid.seq) { - ngtcp2_dcid_copy_cid_token(&pv->fallback_dcid, dcid); + ngtcp2_dcid_copy_cid_token(&pv->fallback_dcid, &dcid); } } - ngtcp2_dcid_copy_cid_token(&conn->dcid.current, dcid); - ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + ngtcp2_dcid_copy_cid_token(&conn->dcid.current, &dcid); rv = conn_call_activate_dcid(conn, &conn->dcid.current); if (rv != 0) { @@ -7862,54 +7590,52 @@ static int conn_post_process_recv_new_connection_id(ngtcp2_conn *conn, if (pv) { if (pv->dcid.seq < conn->dcid.retire_prior_to) { - if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb)) { - rv = conn_retire_dcid(conn, &pv->dcid, ts); - if (rv != 0) { - return rv; - } - - dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); - - if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && - pv->dcid.seq == pv->fallback_dcid.seq) { - ngtcp2_dcid_copy_cid_token(&pv->fallback_dcid, dcid); - } - - ngtcp2_dcid_copy_cid_token(&pv->dcid, dcid); - ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); - - rv = conn_call_activate_dcid(conn, &pv->dcid); - if (rv != 0) { - return rv; - } - } else { + if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) { ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PTV, "path migration is aborted because connection ID is" "retired and no unused connection ID is available"); return conn_abort_pv(conn, ts); } - } - if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && - pv->fallback_dcid.seq < conn->dcid.retire_prior_to) { - if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb)) { - rv = conn_retire_dcid(conn, &pv->fallback_dcid, ts); - if (rv != 0) { - return rv; - } - dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); - ngtcp2_dcid_copy_cid_token(&pv->fallback_dcid, dcid); - ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + rv = conn_retire_active_dcid(conn, &pv->dcid, ts); + if (rv != 0) { + return rv; + } - rv = conn_call_activate_dcid(conn, &pv->fallback_dcid); - if (rv != 0) { - return rv; - } - } else { + ngtcp2_dcidtr_pop_unused_cid_token(&conn->dcid.dtr, &dcid); + + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) && + pv->dcid.seq == pv->fallback_dcid.seq) { + ngtcp2_dcid_copy_cid_token(&pv->fallback_dcid, &dcid); + } + + ngtcp2_dcid_copy_cid_token(&pv->dcid, &dcid); + + rv = conn_call_activate_dcid(conn, &pv->dcid); + if (rv != 0) { + return rv; + } + } + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) && + pv->fallback_dcid.seq < conn->dcid.retire_prior_to) { + if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) { /* Now we have no fallback dcid. */ return conn_abort_pv(conn, ts); } + + rv = conn_retire_active_dcid(conn, &pv->fallback_dcid, ts); + if (rv != 0) { + return rv; + } + + ngtcp2_dcidtr_pop_unused_cid_token(&conn->dcid.dtr, &dcid); + ngtcp2_dcid_copy_cid_token(&pv->fallback_dcid, &dcid); + + rv = conn_call_activate_dcid(conn, &pv->fallback_dcid); + if (rv != 0) { + return rv; + } } } @@ -8185,9 +7911,9 @@ static int conn_select_preferred_addr(ngtcp2_conn *conn) { ngtcp2_path_storage ps; int rv; ngtcp2_pv *pv; - ngtcp2_dcid *dcid; + ngtcp2_dcid dcid; - if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb) == 0) { + if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) { return 0; } @@ -8199,24 +7925,22 @@ static int conn_select_preferred_addr(ngtcp2_conn *conn) { return rv; } - if (ps.path.remote.addrlen == 0 || + if (ngtcp2_addr_empty(&ps.path.remote) || ngtcp2_addr_eq(&conn->dcid.current.ps.path.remote, &ps.path.remote)) { return 0; } assert(conn->pv == NULL); - dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); - ngtcp2_dcid_set_path(dcid, &ps.path); + ngtcp2_dcidtr_pop_unused(&conn->dcid.dtr, &dcid); + ngtcp2_dcid_set_path(&dcid, &ps.path); - rv = ngtcp2_pv_new(&pv, dcid, conn_compute_pv_timeout(conn), + rv = ngtcp2_pv_new(&pv, &dcid, conn_compute_pv_timeout(conn), NGTCP2_PV_FLAG_PREFERRED_ADDR, &conn->log, conn->mem); if (rv != 0) { - /* TODO Call ngtcp2_dcid_free here if it is introduced */ return rv; } - ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); conn->pv = pv; return conn_call_activate_dcid(conn, &pv->dcid); @@ -8250,7 +7974,7 @@ static int conn_recv_handshake_done(ngtcp2_conn *conn, ngtcp2_tstamp ts) { conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED | NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED; - conn->pktns.rtb.persistent_congestion_start_ts = ts; + conn->handshake_confirmed_ts = ts; ngtcp2_conn_discard_handshake_state(conn, ts); @@ -8443,18 +8167,18 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn, size_t dgramlen, int new_cid_used, ngtcp2_tstamp ts) { - ngtcp2_dcid dcid, *bound_dcid, *last; + ngtcp2_dcid dcid; ngtcp2_pv *pv; int rv; ngtcp2_duration pto; int require_new_cid; int local_addr_eq; + int pref_addr_migration; uint32_t remote_addr_cmp; - size_t len, i; assert(conn->server); - if (conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + if (conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) && ngtcp2_path_eq(&conn->pv->fallback_dcid.ps.path, path)) { /* If new path equals fallback path, that means connection migrated back to the original path. Fallback path is @@ -8478,9 +8202,11 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn, } remote_addr_cmp = - ngtcp2_addr_compare(&conn->dcid.current.ps.path.remote, &path->remote); + ngtcp2_addr_cmp(&conn->dcid.current.ps.path.remote, &path->remote); local_addr_eq = ngtcp2_addr_eq(&conn->dcid.current.ps.path.local, &path->local); + pref_addr_migration = + !local_addr_eq && conn_server_preferred_addr_migration(conn, &path->local); /* * When to change DCID? RFC 9002 section 9.5 says: @@ -8504,44 +8230,25 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn, ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "non-probing packet was received from new remote address"); - len = ngtcp2_ringbuf_len(&conn->dcid.bound.rb); + if (ngtcp2_dcidtr_pop_bound_dcid(&conn->dcid.dtr, &dcid, path) == 0) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "Found DCID which has already been bound to the new path"); - for (i = 0; i < len; ++i) { - bound_dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); - if (ngtcp2_path_eq(&bound_dcid->ps.path, path)) { - ngtcp2_log_info( - &conn->log, NGTCP2_LOG_EVENT_CON, - "Found DCID which has already been bound to the new path"); - - ngtcp2_dcid_copy(&dcid, bound_dcid); - if (i == 0) { - ngtcp2_ringbuf_pop_front(&conn->dcid.bound.rb); - } else if (i == ngtcp2_ringbuf_len(&conn->dcid.bound.rb) - 1) { - ngtcp2_ringbuf_pop_back(&conn->dcid.bound.rb); - } else { - last = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, len - 1); - ngtcp2_dcid_copy(bound_dcid, last); - ngtcp2_ringbuf_pop_back(&conn->dcid.bound.rb); - } - require_new_cid = 0; + require_new_cid = 0; - if (dcid.cid.datalen) { - rv = conn_call_activate_dcid(conn, &dcid); - if (rv != 0) { - return rv; - } + if (dcid.cid.datalen) { + rv = conn_call_activate_dcid(conn, &dcid); + if (rv != 0) { + return rv; } - break; } - } - - if (i == len) { + } else { if (require_new_cid) { - if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb) == 0) { + if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) { return NGTCP2_ERR_CONN_ID_BLOCKED; } - ngtcp2_dcid_copy(&dcid, ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0)); - ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + + ngtcp2_dcidtr_pop_unused(&conn->dcid.dtr, &dcid); rv = conn_call_activate_dcid(conn, &dcid); if (rv != 0) { @@ -8564,32 +8271,33 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn, pto = conn_compute_pto(conn, &conn->pktns); rv = ngtcp2_pv_new(&pv, &dcid, conn_compute_pv_timeout_pto(conn, pto), - NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE, &conn->log, conn->mem); + NGTCP2_PV_FLAG_NONE, &conn->log, conn->mem); if (rv != 0) { return rv; } - if (conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE)) { - ngtcp2_dcid_copy(&pv->fallback_dcid, &conn->pv->fallback_dcid); - pv->fallback_pto = conn->pv->fallback_pto; + if (conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT)) { + ngtcp2_pv_set_fallback(pv, &conn->pv->fallback_dcid, + conn->pv->fallback_pto); /* Unset the flag bit so that conn_stop_pv does not retire DCID. */ - conn->pv->flags &= (uint8_t)~NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE; - } else { - ngtcp2_dcid_copy(&pv->fallback_dcid, &conn->dcid.current); - pv->fallback_pto = pto; + conn->pv->flags &= (uint8_t)~NGTCP2_PV_FLAG_FALLBACK_PRESENT; + } else if (!pref_addr_migration) { + ngtcp2_pv_set_fallback(pv, &conn->dcid.current, pto); } - ngtcp2_dcid_copy(&conn->dcid.current, &dcid); + if (!pref_addr_migration) { + ngtcp2_dcid_copy(&conn->dcid.current, &dcid); - if (!local_addr_eq || (remote_addr_cmp & (NGTCP2_ADDR_COMPARE_FLAG_ADDR | - NGTCP2_ADDR_COMPARE_FLAG_FAMILY))) { - conn_reset_congestion_state(conn, ts); - } + if (!local_addr_eq || (remote_addr_cmp & (NGTCP2_ADDR_CMP_FLAG_ADDR | + NGTCP2_ADDR_CMP_FLAG_FAMILY))) { + conn_reset_congestion_state(conn, ts); + } - conn_reset_ecn_validation_state(conn); + conn_reset_ecn_validation_state(conn); - ngtcp2_conn_stop_pmtud(conn); + ngtcp2_conn_stop_pmtud(conn); + } if (conn->pv) { ngtcp2_log_info( @@ -8634,7 +8342,7 @@ static int conn_recv_pkt_from_new_path(ngtcp2_conn *conn, return 0; } - if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) && ngtcp2_path_eq(&pv->fallback_dcid.ps.path, path)) { pv->fallback_dcid.bytes_recv += dgramlen; return 0; @@ -8650,7 +8358,6 @@ static int conn_recv_pkt_from_new_path(ngtcp2_conn *conn, return rv; } - ngtcp2_dcid_set_path(bound_dcid, path); bound_dcid->bytes_recv += dgramlen; return 0; @@ -8763,20 +8470,13 @@ conn_recv_delayed_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_pkt_info *pi, ngtcp2_qlog_pkt_received_end(&conn->qlog, hd, pktlen); - rv = pktns_commit_recv_pkt_num(pktns, hd->pkt_num, require_ack, pkt_ts); + rv = pktns_commit_recv_pkt_num(pktns, hd->pkt_num, pi, require_ack, pkt_ts); if (rv != 0) { return rv; } - pktns_increase_ecn_counts(pktns, pi); - /* Initial and Handshake are always acknowledged without delay. No need to call ngtcp2_acktr_immediate_ack(). */ - rv = ngtcp2_conn_sched_ack(conn, &pktns->acktr, hd->pkt_num, require_ack, - pkt_ts); - if (rv != 0) { - return rv; - } conn_restart_timer_on_read(conn, ts); @@ -8794,8 +8494,6 @@ static int conn_allow_path_change_under_disable_active_migration(ngtcp2_conn *conn, const ngtcp2_path *path) { uint32_t remote_addr_cmp; - const ngtcp2_preferred_addr *paddr; - ngtcp2_addr addr; assert(conn->server); assert(conn->local.transport_params.disable_active_migration); @@ -8804,40 +8502,15 @@ conn_allow_path_change_under_disable_active_migration(ngtcp2_conn *conn, (NAT rebinding). */ if (ngtcp2_addr_eq(&conn->dcid.current.ps.path.local, &path->local)) { remote_addr_cmp = - ngtcp2_addr_compare(&conn->dcid.current.ps.path.remote, &path->remote); + ngtcp2_addr_cmp(&conn->dcid.current.ps.path.remote, &path->remote); - return (remote_addr_cmp | NGTCP2_ADDR_COMPARE_FLAG_PORT) == - NGTCP2_ADDR_COMPARE_FLAG_PORT; + return (remote_addr_cmp | NGTCP2_ADDR_CMP_FLAG_PORT) == + NGTCP2_ADDR_CMP_FLAG_PORT; } /* If local address changes, it must be one of the preferred addresses. */ - - if (!conn->local.transport_params.preferred_addr_present) { - return 0; - } - - paddr = &conn->local.transport_params.preferred_addr; - - if (paddr->ipv4_present) { - ngtcp2_addr_init(&addr, (const ngtcp2_sockaddr *)&paddr->ipv4, - sizeof(paddr->ipv4)); - - if (ngtcp2_addr_eq(&addr, &path->local)) { - return 1; - } - } - - if (paddr->ipv6_present) { - ngtcp2_addr_init(&addr, (const ngtcp2_sockaddr *)&paddr->ipv6, - sizeof(paddr->ipv6)); - - if (ngtcp2_addr_eq(&addr, &path->local)) { - return 1; - } - } - - return 0; + return conn_server_preferred_addr_migration(conn, &path->local); } /* @@ -8904,13 +8577,28 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, int path_challenge_recved = 0; size_t num_ack_processed = 0; - if (conn->server && conn->local.transport_params.disable_active_migration && - !ngtcp2_path_eq(&conn->dcid.current.ps.path, path) && - !conn_allow_path_change_under_disable_active_migration(conn, path)) { - ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, - "packet is discarded because active migration is disabled"); + if (conn->server) { + if (conn->local.transport_params.disable_active_migration && + !ngtcp2_path_eq(&conn->dcid.current.ps.path, path) && + !conn_allow_path_change_under_disable_active_migration(conn, path)) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_PKT, + "packet is discarded because local active migration is disabled"); - return NGTCP2_ERR_DISCARD_PKT; + return NGTCP2_ERR_DISCARD_PKT; + } + + assert(conn->remote.transport_params); + + if (conn->remote.transport_params->disable_active_migration && + !ngtcp2_addr_eq(&conn->dcid.current.ps.path.local, &path->local) && + !conn_server_preferred_addr_migration(conn, &path->local)) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_PKT, + "packet is discarded because remote active migration is disabled"); + + return NGTCP2_ERR_DISCARD_PKT; + } } if (pkt[0] & NGTCP2_HEADER_FORM_BIT) { @@ -9035,8 +8723,8 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, payload = pkt + hdpktlen; payloadlen = pktlen - hdpktlen; - hd.pkt_num = - ngtcp2_pkt_adjust_pkt_num(pktns->rx.max_pkt_num, hd.pkt_num, hd.pkt_numlen); + hd.pkt_num = ngtcp2_pkt_adjust_pkt_num(pktns->acktr.max_pkt_num, hd.pkt_num, + hd.pkt_numlen); if (hd.pkt_num > NGTCP2_MAX_PKT_NUM) { ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, "pkn=%" PRId64 " is greater than maximum pkn", hd.pkt_num); @@ -9046,6 +8734,19 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, ngtcp2_log_rx_pkt_hd(&conn->log, &hd); if (hd.type == NGTCP2_PKT_1RTT) { + if (conn->server && conn->rx.preferred_addr.pkt_num != -1 && + conn->rx.preferred_addr.pkt_num < hd.pkt_num && + ngtcp2_sockaddr_eq((const ngtcp2_sockaddr *)&conn->hs_local_addr, + path->local.addr)) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_PKT, + "pkt=%" PRId64 + " is discarded because it was received on handshake local " + "address after preferred address migration", + hd.pkt_num); + return NGTCP2_ERR_DISCARD_PKT; + } + key_phase_bit_changed = conn_key_phase_changed(conn, &hd); } @@ -9067,7 +8768,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, } else { force_decrypt_failure = 1; } - } else if (pktns->rx.max_pkt_num < hd.pkt_num) { + } else if (pktns->acktr.max_pkt_num < hd.pkt_num) { assert(ckm->pkt_num < hd.pkt_num); if (!conn->crypto.key_update.new_rx_ckm) { ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, @@ -9336,7 +9037,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, path_challenge_recved = 1; break; case NGTCP2_FRAME_PATH_RESPONSE: - rv = conn_recv_path_response(conn, &fr->path_response, ts); + rv = conn_recv_path_response(conn, &hd, &fr->path_response, ts); if (rv != 0) { return rv; } @@ -9426,7 +9127,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, if (conn->server && hd.type == NGTCP2_PKT_1RTT && !ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { - if (non_probing_pkt && pktns->rx.max_pkt_num < hd.pkt_num && + if (non_probing_pkt && pktns->acktr.max_pkt_num < hd.pkt_num && !conn_path_validation_in_progress(conn, path)) { rv = conn_recv_non_probing_pkt_on_new_path(conn, path, dgramlen, new_cid_used, ts); @@ -9470,25 +9171,17 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, } } - rv = pktns_commit_recv_pkt_num(pktns, hd.pkt_num, require_ack, pkt_ts); + rv = pktns_commit_recv_pkt_num(pktns, hd.pkt_num, pi, require_ack, pkt_ts); if (rv != 0) { return rv; } - pktns_increase_ecn_counts(pktns, pi); - if (require_ack && (++pktns->acktr.rx_npkt >= conn->local.settings.ack_thresh || (pi->ecn & NGTCP2_ECN_MASK) == NGTCP2_ECN_CE)) { ngtcp2_acktr_immediate_ack(&pktns->acktr); } - rv = - ngtcp2_conn_sched_ack(conn, &pktns->acktr, hd.pkt_num, require_ack, pkt_ts); - if (rv != 0) { - return rv; - } - conn_restart_timer_on_read(conn, ts); ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); @@ -9721,24 +9414,6 @@ static int conn_recv_cpkt(ngtcp2_conn *conn, const ngtcp2_path *path, return 0; } -/* - * conn_is_retired_path returns nonzero if |path| is included in - * retired path list. - */ -static int conn_is_retired_path(ngtcp2_conn *conn, const ngtcp2_path *path) { - size_t i, len = ngtcp2_ringbuf_len(&conn->dcid.retired.rb); - ngtcp2_dcid *dcid; - - for (i = 0; i < len; ++i) { - dcid = ngtcp2_ringbuf_get(&conn->dcid.retired.rb, i); - if (ngtcp2_path_eq(&dcid->ps.path, path)) { - return 1; - } - } - - return 0; -} - /* * conn_enqueue_handshake_done enqueues HANDSHAKE_DONE frame for * transmission. @@ -9875,7 +9550,7 @@ static ngtcp2_ssize conn_read_handshake(ngtcp2_conn *conn, } } - if (conn->hs_pktns->rx.max_pkt_num != -1) { + if (conn->hs_pktns->acktr.max_pkt_num != -1) { ngtcp2_conn_discard_initial_state(conn, ts); } @@ -9956,7 +9631,7 @@ static ngtcp2_ssize conn_read_handshake(ngtcp2_conn *conn, } } - conn->pktns.rtb.persistent_congestion_start_ts = ts; + conn->handshake_confirmed_ts = ts; /* Re-arm loss detection timer here after handshake has been confirmed. */ @@ -9996,7 +9671,7 @@ int ngtcp2_conn_read_pkt_versioned(ngtcp2_conn *conn, const ngtcp2_path *path, /* client does not expect a packet from unknown path. */ if (!conn->server && !ngtcp2_path_eq(&conn->dcid.current.ps.path, path) && (!conn->pv || !ngtcp2_path_eq(&conn->pv->dcid.ps.path, path)) && - !conn_is_retired_path(conn, path)) { + !ngtcp2_dcidtr_check_path_retired(&conn->dcid.dtr, path)) { ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "ignore packet from unknown path"); return 0; @@ -10172,7 +9847,6 @@ static ngtcp2_ssize conn_write_handshake(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, ngtcp2_ssize res = 0, nwrite = 0, early_spktlen = 0; size_t origlen = destlen; uint64_t pending_early_datalen; - ngtcp2_dcid *dcid; ngtcp2_preferred_addr *paddr; switch (conn->state) { @@ -10290,11 +9964,11 @@ static ngtcp2_ssize conn_write_handshake(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, assert(conn->remote.transport_params); if (conn->remote.transport_params->preferred_addr_present) { - assert(!ngtcp2_ringbuf_full(&conn->dcid.unused.rb)); + assert(!ngtcp2_dcidtr_unused_full(&conn->dcid.dtr)); paddr = &conn->remote.transport_params->preferred_addr; - dcid = ngtcp2_ringbuf_push_back(&conn->dcid.unused.rb); - ngtcp2_dcid_init(dcid, 1, &paddr->cid, paddr->stateless_reset_token); + ngtcp2_dcidtr_push_unused(&conn->dcid.dtr, 1, &paddr->cid, + paddr->stateless_reset_token); rv = ngtcp2_gaptr_push(&conn->dcid.seqgap, 1, 1); if (rv != 0) { @@ -10519,20 +10193,6 @@ int ngtcp2_conn_get_handshake_completed(ngtcp2_conn *conn) { (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED); } -int ngtcp2_conn_sched_ack(ngtcp2_conn *conn, ngtcp2_acktr *acktr, - int64_t pkt_num, int active_ack, ngtcp2_tstamp ts) { - int rv; - (void)conn; - - rv = ngtcp2_acktr_add(acktr, pkt_num, active_ack, ts); - if (rv != 0) { - assert(rv != NGTCP2_ERR_INVALID_ARGUMENT); - return rv; - } - - return 0; -} - int ngtcp2_accept(ngtcp2_pkt_hd *dest, const uint8_t *pkt, size_t pktlen) { ngtcp2_ssize nread; ngtcp2_pkt_hd hd, *p; @@ -10904,68 +10564,14 @@ int ngtcp2_conn_initiate_key_update(ngtcp2_conn *conn, ngtcp2_tstamp ts) { return conn_initiate_key_update(conn, ts); } -/* - * conn_retire_stale_bound_dcid retires stale destination connection - * ID in conn->dcid.bound to keep some unused destination connection - * IDs available. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGTCP2_ERR_NOMEM - * Out of memory. - */ -static int conn_retire_stale_bound_dcid(ngtcp2_conn *conn, - ngtcp2_duration timeout, - ngtcp2_tstamp ts) { - size_t i; - ngtcp2_dcid *dcid, *last; - int rv; - - for (i = 0; i < ngtcp2_ringbuf_len(&conn->dcid.bound.rb);) { - dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); - - assert(dcid->cid.datalen); - - if (ngtcp2_tstamp_not_elapsed(dcid->bound_ts, timeout, ts)) { - ++i; - continue; - } - - rv = conn_retire_dcid_seq(conn, dcid->seq); - if (rv != 0) { - return rv; - } - - if (i == 0) { - ngtcp2_ringbuf_pop_front(&conn->dcid.bound.rb); - continue; - } - - if (i == ngtcp2_ringbuf_len(&conn->dcid.bound.rb) - 1) { - ngtcp2_ringbuf_pop_back(&conn->dcid.bound.rb); - break; - } - - last = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, - ngtcp2_ringbuf_len(&conn->dcid.bound.rb) - 1); - ngtcp2_dcid_copy(dcid, last); - ngtcp2_ringbuf_pop_back(&conn->dcid.bound.rb); - } - - return 0; -} - ngtcp2_tstamp ngtcp2_conn_loss_detection_expiry(ngtcp2_conn *conn) { return conn->cstat.loss_detection_timer; } ngtcp2_tstamp ngtcp2_conn_internal_expiry(ngtcp2_conn *conn) { - ngtcp2_tstamp res = UINT64_MAX; + ngtcp2_tstamp res = UINT64_MAX, ts; ngtcp2_duration pto = conn_compute_pto(conn, &conn->pktns); ngtcp2_scid *scid; - ngtcp2_dcid *dcid; - size_t i, len; if (conn->pv) { res = ngtcp2_pv_next_expiry(conn->pv); @@ -10982,20 +10588,15 @@ ngtcp2_tstamp ngtcp2_conn_internal_expiry(ngtcp2_conn *conn) { } } - if (ngtcp2_ringbuf_len(&conn->dcid.retired.rb)) { - dcid = ngtcp2_ringbuf_get(&conn->dcid.retired.rb, 0); - res = ngtcp2_min_uint64(res, dcid->retired_ts + pto); + ts = ngtcp2_dcidtr_earliest_retired_ts(&conn->dcid.dtr); + if (ts != UINT64_MAX) { + res = ngtcp2_min_uint64(res, ts + pto); } if (conn->dcid.current.cid.datalen) { - len = ngtcp2_ringbuf_len(&conn->dcid.bound.rb); - for (i = 0; i < len; ++i) { - dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); - - assert(dcid->cid.datalen); - assert(dcid->bound_ts != UINT64_MAX); - - res = ngtcp2_min_uint64(res, dcid->bound_ts + 3 * pto); + ts = ngtcp2_dcidtr_earliest_bound_ts(&conn->dcid.dtr); + if (ts != UINT64_MAX) { + res = ngtcp2_min_uint64(res, ts + 3 * pto); } } @@ -11081,7 +10682,8 @@ int ngtcp2_conn_handle_expiry(ngtcp2_conn *conn, ngtcp2_tstamp ts) { } if (conn->dcid.current.cid.datalen) { - rv = conn_retire_stale_bound_dcid(conn, 3 * pto, ts); + rv = ngtcp2_dcidtr_retire_stale_bound_dcid(&conn->dcid.dtr, 3 * pto, ts, + dcidtr_on_retire, conn); if (rv != 0) { return rv; } @@ -11578,7 +11180,8 @@ int ngtcp2_conn_set_local_transport_params_versioned( assert(conn->server); assert(params->active_connection_id_limit >= NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT); - assert(params->active_connection_id_limit <= NGTCP2_MAX_DCID_POOL_SIZE); + assert(params->active_connection_id_limit <= + NGTCP2_DCIDTR_MAX_UNUSED_DCID_SIZE); if (conn->hs_pktns == NULL || conn->hs_pktns->crypto.tx.ckm) { return NGTCP2_ERR_INVALID_STATE; @@ -11739,8 +11342,7 @@ static ngtcp2_ssize conn_write_vmsg_wrapper(ngtcp2_conn *conn, if (cstat->bytes_in_flight >= cstat->cwnd) { conn->rst.is_cwnd_limited = 1; - } else if (conn->rst.app_limited == 0 && - (cstat->cwnd >= cstat->ssthresh || + } else if ((cstat->cwnd >= cstat->ssthresh || cstat->bytes_in_flight * 2 < cstat->cwnd) && nwrite == 0 && conn_pacing_pkt_tx_allowed(conn, ts) && (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED)) { @@ -12930,7 +12532,7 @@ static void conn_get_loss_time_and_pktns(ngtcp2_conn *conn, static ngtcp2_tstamp conn_get_earliest_pto_expiry(ngtcp2_conn *conn, ngtcp2_tstamp ts) { - ngtcp2_pktns *ns[] = {conn->in_pktns, conn->hs_pktns, &conn->pktns}; + ngtcp2_pktns *const ns[] = {conn->in_pktns, conn->hs_pktns, &conn->pktns}; size_t i; ngtcp2_tstamp earliest_ts = UINT64_MAX, t; ngtcp2_conn_stat *cstat = &conn->cstat; @@ -13249,18 +12851,22 @@ static size_t conn_get_num_active_dcid(ngtcp2_conn *conn) { size_t n = 1; /* for conn->dcid.current */ ngtcp2_pv *pv = conn->pv; + if (conn->dcid.current.cid.datalen == 0) { + return n; + } + if (pv) { if (pv->dcid.seq != conn->dcid.current.seq) { ++n; } - if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) && pv->fallback_dcid.seq != conn->dcid.current.seq && pv->fallback_dcid.seq != pv->dcid.seq) { ++n; } } - n += ngtcp2_ringbuf_len(&conn->dcid.retired.rb); + n += ngtcp2_dcidtr_retired_len(&conn->dcid.dtr); return n; } @@ -13293,12 +12899,16 @@ size_t ngtcp2_conn_get_active_dcid(ngtcp2_conn *conn, ngtcp2_cid_token *dest) { copy_dcid_to_cid_token(dest, &conn->dcid.current); ++dest; + if (conn->dcid.current.cid.datalen == 0) { + return 1; + } + if (pv) { if (pv->dcid.seq != conn->dcid.current.seq) { copy_dcid_to_cid_token(dest, &pv->dcid); ++dest; } - if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) && pv->fallback_dcid.seq != conn->dcid.current.seq && pv->fallback_dcid.seq != pv->dcid.seq) { copy_dcid_to_cid_token(dest, &pv->fallback_dcid); @@ -13306,9 +12916,9 @@ size_t ngtcp2_conn_get_active_dcid(ngtcp2_conn *conn, ngtcp2_cid_token *dest) { } } - len = ngtcp2_ringbuf_len(&conn->dcid.retired.rb); + len = ngtcp2_ringbuf_len(&conn->dcid.dtr.retired.rb); for (i = 0; i < len; ++i) { - dcid = ngtcp2_ringbuf_get(&conn->dcid.retired.rb, i); + dcid = ngtcp2_ringbuf_get(&conn->dcid.dtr.retired.rb, i); copy_dcid_to_cid_token(dest, dcid); ++dest; } @@ -13353,7 +12963,7 @@ static int conn_initiate_migration_precheck(ngtcp2_conn *conn, return NGTCP2_ERR_INVALID_STATE; } - if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb) == 0) { + if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) { return NGTCP2_ERR_CONN_ID_BLOCKED; } @@ -13368,7 +12978,7 @@ int ngtcp2_conn_initiate_immediate_migration(ngtcp2_conn *conn, const ngtcp2_path *path, ngtcp2_tstamp ts) { int rv; - ngtcp2_dcid *dcid; + ngtcp2_dcid dcid; ngtcp2_pv *pv; assert(!conn->server); @@ -13389,16 +12999,15 @@ int ngtcp2_conn_initiate_immediate_migration(ngtcp2_conn *conn, } } - rv = conn_retire_dcid(conn, &conn->dcid.current, ts); + rv = conn_retire_active_dcid(conn, &conn->dcid.current, ts); if (rv != 0) { return rv; } - dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); - ngtcp2_dcid_set_path(dcid, path); + ngtcp2_dcidtr_pop_unused(&conn->dcid.dtr, &dcid); + ngtcp2_dcid_set_path(&dcid, path); - ngtcp2_dcid_copy(&conn->dcid.current, dcid); - ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + ngtcp2_dcid_copy(&conn->dcid.current, &dcid); conn_reset_congestion_state(conn, ts); conn_reset_ecn_validation_state(conn); @@ -13407,7 +13016,7 @@ int ngtcp2_conn_initiate_immediate_migration(ngtcp2_conn *conn, connection should be closed if this path validation failed. The current design allows an application to continue, by migrating into yet another path. */ - rv = ngtcp2_pv_new(&pv, dcid, conn_compute_pv_timeout(conn), + rv = ngtcp2_pv_new(&pv, &dcid, conn_compute_pv_timeout(conn), NGTCP2_PV_FLAG_NONE, &conn->log, conn->mem); if (rv != 0) { return rv; @@ -13421,7 +13030,7 @@ int ngtcp2_conn_initiate_immediate_migration(ngtcp2_conn *conn, int ngtcp2_conn_initiate_migration(ngtcp2_conn *conn, const ngtcp2_path *path, ngtcp2_tstamp ts) { int rv; - ngtcp2_dcid *dcid; + ngtcp2_dcid dcid; ngtcp2_pv *pv; assert(!conn->server); @@ -13440,16 +13049,15 @@ int ngtcp2_conn_initiate_migration(ngtcp2_conn *conn, const ngtcp2_path *path, } } - dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); - ngtcp2_dcid_set_path(dcid, path); + ngtcp2_dcidtr_pop_unused(&conn->dcid.dtr, &dcid); + ngtcp2_dcid_set_path(&dcid, path); - rv = ngtcp2_pv_new(&pv, dcid, conn_compute_pv_timeout(conn), + rv = ngtcp2_pv_new(&pv, &dcid, conn_compute_pv_timeout(conn), NGTCP2_PV_FLAG_NONE, &conn->log, conn->mem); if (rv != 0) { return rv; } - ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); conn->pv = pv; return conn_call_activate_dcid(conn, &pv->dcid); @@ -13487,7 +13095,7 @@ uint64_t ngtcp2_conn_get_streams_uni_left(ngtcp2_conn *conn) { uint64_t ngtcp2_conn_get_cwnd_left(ngtcp2_conn *conn) { uint64_t bytes_in_flight = conn->cstat.bytes_in_flight; - uint64_t cwnd = conn_get_cwnd(conn); + uint64_t cwnd = conn->cstat.cwnd; if (cwnd > bytes_in_flight) { return cwnd - bytes_in_flight; @@ -13630,7 +13238,7 @@ int ngtcp2_conn_set_stream_user_data(ngtcp2_conn *conn, int64_t stream_id, void ngtcp2_conn_update_pkt_tx_time(ngtcp2_conn *conn, ngtcp2_tstamp ts) { ngtcp2_duration pacing_interval; - ngtcp2_duration wait; + ngtcp2_duration wait, d; conn_update_timestamp(conn, ts); @@ -13651,6 +13259,12 @@ void ngtcp2_conn_update_pkt_tx_time(ngtcp2_conn *conn, ngtcp2_tstamp ts) { wait = (ngtcp2_duration)(conn->tx.pacing.pktlen * pacing_interval); + if (conn->tx.pacing.compensation >= NGTCP2_MILLISECONDS) { + d = ngtcp2_min_uint64(wait, conn->tx.pacing.compensation); + wait -= d; + conn->tx.pacing.compensation -= d; + } + conn->tx.pacing.next_ts = ts + wait; conn->tx.pacing.pktlen = 0; } @@ -13659,48 +13273,6 @@ size_t ngtcp2_conn_get_send_quantum(ngtcp2_conn *conn) { return conn->cstat.send_quantum; } -int ngtcp2_conn_track_retired_dcid_seq(ngtcp2_conn *conn, uint64_t seq) { - if (conn->dcid.retire_unacked.len >= - ngtcp2_arraylen(conn->dcid.retire_unacked.seqs)) { - return NGTCP2_ERR_CONNECTION_ID_LIMIT; - } - - conn->dcid.retire_unacked.seqs[conn->dcid.retire_unacked.len++] = seq; - - return 0; -} - -void ngtcp2_conn_untrack_retired_dcid_seq(ngtcp2_conn *conn, uint64_t seq) { - size_t i; - - for (i = 0; i < conn->dcid.retire_unacked.len; ++i) { - if (conn->dcid.retire_unacked.seqs[i] != seq) { - continue; - } - - if (i != conn->dcid.retire_unacked.len - 1) { - conn->dcid.retire_unacked.seqs[i] = - conn->dcid.retire_unacked.seqs[conn->dcid.retire_unacked.len - 1]; - } - - --conn->dcid.retire_unacked.len; - - return; - } -} - -int ngtcp2_conn_check_retired_dcid_tracked(ngtcp2_conn *conn, uint64_t seq) { - size_t i; - - for (i = 0; i < conn->dcid.retire_unacked.len; ++i) { - if (conn->dcid.retire_unacked.seqs[i] == seq) { - return 1; - } - } - - return 0; -} - size_t ngtcp2_conn_get_stream_loss_count(ngtcp2_conn *conn, int64_t stream_id) { ngtcp2_strm *strm = ngtcp2_conn_find_stream(conn, stream_id); @@ -13737,8 +13309,7 @@ ngtcp2_ssize ngtcp2_pkt_write_connection_close( int rv; ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_INITIAL, dcid, - scid, /* pkt_num = */ 0, /* pkt_numlen = */ 1, version, - /* len = */ 0); + scid, /* pkt_num = */ 0, /* pkt_numlen = */ 1, version); ngtcp2_vec_init(&ckm.secret, NULL, 0); ngtcp2_vec_init(&ckm.iv, iv, 12); @@ -13767,6 +13338,7 @@ ngtcp2_ssize ngtcp2_pkt_write_connection_close( fr.type = NGTCP2_FRAME_CONNECTION_CLOSE; fr.connection_close.error_code = error_code; + fr.connection_close.frame_type = 0; fr.connection_close.reasonlen = reasonlen; fr.connection_close.reason = (uint8_t *)reason; diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.h index 55073fcc828d73..0ba8d6efcc6ac1 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.h @@ -51,6 +51,7 @@ #include "ngtcp2_qlog.h" #include "ngtcp2_rst.h" #include "ngtcp2_conn_stat.h" +#include "ngtcp2_dcidtr.h" typedef enum { /* Client specific handshake states */ @@ -78,18 +79,6 @@ typedef enum { accept. */ #define NGTCP2_MAX_RETRIES 3 -/* NGTCP2_MAX_BOUND_DCID_POOL_SIZE is the maximum number of - destination connection ID which have been bound to a particular - path, but not yet used as primary path and path validation is not - performed from the local endpoint. */ -#define NGTCP2_MAX_BOUND_DCID_POOL_SIZE 4 -/* NGTCP2_MAX_DCID_POOL_SIZE is the maximum number of destination - connection ID the remote endpoint provides to store. It must be - the power of 2. */ -#define NGTCP2_MAX_DCID_POOL_SIZE 8 -/* NGTCP2_MAX_DCID_RETIRED_SIZE is the maximum number of retired DCID - kept to catch in-flight packet on retired path. */ -#define NGTCP2_MAX_DCID_RETIRED_SIZE 2 /* NGTCP2_MAX_SCID_POOL_SIZE is the maximum number of source connection ID the local endpoint provides to the remote endpoint. The chosen value was described in old draft. Now a remote endpoint @@ -239,11 +228,6 @@ typedef struct ngtcp2_pktns { /* pngap tracks received packet number in order to suppress duplicated packet number. */ ngtcp2_gaptr pngap; - /* max_pkt_num is the largest packet number received so far. */ - int64_t max_pkt_num; - /* max_pkt_ts is the timestamp when max_pkt_num packet is - received. */ - ngtcp2_tstamp max_pkt_ts; /* max_ack_eliciting_pkt_num is the largest ack-eliciting packet number received so far. */ int64_t max_ack_eliciting_pkt_num; @@ -268,21 +252,6 @@ typedef struct ngtcp2_pktns { * ngtcp2_pktns. */ ngtcp2_pkt_chain *buffed_pkts; - - struct { - /* ect0, ect1, and ce are the number of QUIC packets received - with those markings. */ - size_t ect0; - size_t ect1; - size_t ce; - struct { - /* ect0, ect1, ce are the ECN counts received in the latest - ACK frame. */ - uint64_t ect0; - uint64_t ect1; - uint64_t ce; - } ack; - } ecn; } rx; struct { @@ -336,12 +305,6 @@ typedef struct ngtcp2_early_transport_params { uint64_t max_datagram_frame_size; } ngtcp2_early_transport_params; -ngtcp2_static_ringbuf_def(dcid_bound, NGTCP2_MAX_BOUND_DCID_POOL_SIZE, - sizeof(ngtcp2_dcid)) -ngtcp2_static_ringbuf_def(dcid_unused, NGTCP2_MAX_DCID_POOL_SIZE, - sizeof(ngtcp2_dcid)) -ngtcp2_static_ringbuf_def(dcid_retired, NGTCP2_MAX_DCID_RETIRED_SIZE, - sizeof(ngtcp2_dcid)) ngtcp2_static_ringbuf_def(path_challenge, 4, sizeof(ngtcp2_path_challenge_entry)) @@ -366,6 +329,8 @@ struct ngtcp2_conn { records it in order to verify retry_source_connection_id transport parameter. Server does not use this field. */ ngtcp2_cid retry_scid; + /* hs_local_addr is a local address used during handshake. */ + ngtcp2_sockaddr_union hs_local_addr; ngtcp2_pktns *in_pktns; ngtcp2_pktns *hs_pktns; ngtcp2_pktns pktns; @@ -373,31 +338,13 @@ struct ngtcp2_conn { struct { /* current is the current destination connection ID. */ ngtcp2_dcid current; - /* bound is a set of destination connection IDs which are bound to - particular paths. These paths are not validated yet. */ - ngtcp2_static_ringbuf_dcid_bound bound; - /* unused is a set of unused CID received from peer. */ - ngtcp2_static_ringbuf_dcid_unused unused; - /* retired is a set of CID retired by local endpoint. Keep them - in 3*PTO to catch packets in flight along the old path. */ - ngtcp2_static_ringbuf_dcid_retired retired; + ngtcp2_dcidtr dtr; /* seqgap tracks received sequence numbers in order to ignore retransmitted duplicated NEW_CONNECTION_ID frame. */ ngtcp2_gaptr seqgap; /* retire_prior_to is the largest retire_prior_to received so far. */ uint64_t retire_prior_to; - struct { - /* seqs contains sequence number of Connection ID whose - retirement is not acknowledged by the remote endpoint yet. */ - uint64_t seqs[NGTCP2_MAX_DCID_POOL_SIZE * 2]; - /* len is the number of sequence numbers that seq contains. */ - size_t len; - } retire_unacked; - /* zerolen_seq is a pseudo sequence number of zero-length - Destination Connection ID in order to distinguish between - them. */ - uint64_t zerolen_seq; } dcid; struct { @@ -421,11 +368,6 @@ struct ngtcp2_conn { struct { /* strmq contains ngtcp2_strm which has frames to send. */ ngtcp2_pq strmq; - /* ack is ACK frame. The underlying buffer is reused. */ - ngtcp2_frame *ack; - /* max_ack_ranges is the number of additional ngtcp2_ack_range - which ack can contain. */ - size_t max_ack_ranges; /* offset is the offset the local endpoint has sent to the remote endpoint. */ uint64_t offset; @@ -458,6 +400,15 @@ struct ngtcp2_conn { /* next_ts is the time to send next packet. It is UINT64_MAX if packet pacing is disabled or expired.*/ ngtcp2_tstamp next_ts; + /* compensation is the amount of time that a local endpoint + waits too long for pacing. This happens because there is an + overhead before start writing packets after pacing timer + expires. If multiple QUIC connections are handled by a + single thread, which is typical use case for event loop based + servers, each processing of QUIC connection adds overhead, + for example, TLS handshake, and packet encryption/decryption, + etc. */ + ngtcp2_duration compensation; } pacing; } tx; @@ -478,6 +429,12 @@ struct ngtcp2_conn { ngtcp2_static_ringbuf_path_challenge path_challenge; /* ccerr is the received connection close error. */ ngtcp2_ccerr ccerr; + + struct { + /* pkt_num is the lowest incoming packet number of the packet + that server verified preferred address usage of client. */ + int64_t pkt_num; + } preferred_addr; } rx; struct { @@ -671,6 +628,9 @@ struct ngtcp2_conn { const ngtcp2_mem *mem; /* idle_ts is the time instant when idle timer started. */ ngtcp2_tstamp idle_ts; + /* handshake_confirmed_ts is the time instant when handshake is + confirmed. For server, it is confirmed when completed. */ + ngtcp2_tstamp handshake_confirmed_ts; void *user_data; uint32_t client_chosen_version; uint32_t negotiated_version; @@ -724,21 +684,6 @@ typedef struct ngtcp2_vmsg { }; } ngtcp2_vmsg; -/* - * ngtcp2_conn_sched_ack stores packet number |pkt_num| and its - * reception timestamp |ts| in order to send its ACK. - * - * It returns 0 if it succeeds, or one of the following negative error - * codes: - * - * NGTCP2_ERR_NOMEM - * Out of memory - * NGTCP2_ERR_PROTO - * Same packet number has already been added. - */ -int ngtcp2_conn_sched_ack(ngtcp2_conn *conn, ngtcp2_acktr *acktr, - int64_t pkt_num, int active_ack, ngtcp2_tstamp ts); - /* * ngtcp2_conn_find_stream returns a stream whose stream ID is * |stream_id|. If no such stream is found, it returns NULL. @@ -1137,28 +1082,6 @@ int ngtcp2_conn_set_remote_transport_params( int ngtcp2_conn_set_0rtt_remote_transport_params( ngtcp2_conn *conn, const ngtcp2_transport_params *params); -/* - * ngtcp2_conn_create_ack_frame creates ACK frame, and assigns its - * pointer to |*pfr| if there are any received packets to acknowledge. - * If there are no packets to acknowledge, this function returns 0, - * and |*pfr| is untouched. The caller is advised to set |*pfr| to - * NULL before calling this function, and check it after this function - * returns. - * - * Call ngtcp2_acktr_commit_ack after a created ACK frame is - * successfully serialized into a packet. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGTCP2_ERR_NOMEM - * Out of memory. - */ -int ngtcp2_conn_create_ack_frame(ngtcp2_conn *conn, ngtcp2_frame **pfr, - ngtcp2_pktns *pktns, uint8_t type, - ngtcp2_tstamp ts, ngtcp2_duration ack_delay, - uint64_t ack_delay_exponent); - /* * ngtcp2_conn_discard_initial_state discards state for Initial packet * number space. diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.c new file mode 100644 index 00000000000000..8a8d7733797176 --- /dev/null +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.c @@ -0,0 +1,497 @@ +/* + * ngtcp2 + * + * Copyright (c) 2025 ngtcp2 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 "ngtcp2_dcidtr.h" + +#include + +#include "ngtcp2_tstamp.h" +#include "ngtcp2_macro.h" + +void ngtcp2_dcidtr_init(ngtcp2_dcidtr *dtr) { + ngtcp2_static_ringbuf_dcid_unused_init(&dtr->unused); + ngtcp2_static_ringbuf_dcid_bound_init(&dtr->bound); + ngtcp2_static_ringbuf_dcid_retired_init(&dtr->retired); + + dtr->retire_unacked.len = 0; +} + +int ngtcp2_dcidtr_track_retired_seq(ngtcp2_dcidtr *dtr, uint64_t seq) { + if (dtr->retire_unacked.len >= ngtcp2_arraylen(dtr->retire_unacked.seqs)) { + return NGTCP2_ERR_CONNECTION_ID_LIMIT; + } + + dtr->retire_unacked.seqs[dtr->retire_unacked.len++] = seq; + + return 0; +} + +void ngtcp2_dcidtr_untrack_retired_seq(ngtcp2_dcidtr *dtr, uint64_t seq) { + size_t i; + + for (i = 0; i < dtr->retire_unacked.len; ++i) { + if (dtr->retire_unacked.seqs[i] != seq) { + continue; + } + + if (i != dtr->retire_unacked.len - 1) { + dtr->retire_unacked.seqs[i] = + dtr->retire_unacked.seqs[dtr->retire_unacked.len - 1]; + } + + --dtr->retire_unacked.len; + + return; + } +} + +int ngtcp2_dcidtr_check_retired_seq_tracked(const ngtcp2_dcidtr *dtr, + uint64_t seq) { + size_t i; + + for (i = 0; i < dtr->retire_unacked.len; ++i) { + if (dtr->retire_unacked.seqs[i] == seq) { + return 1; + } + } + + return 0; +} + +static int dcidtr_on_retire(ngtcp2_dcidtr *dtr, const ngtcp2_dcid *dcid, + ngtcp2_dcidtr_cb on_retire, void *user_data) { + int rv; + + if (ngtcp2_dcidtr_check_retired_seq_tracked(dtr, dcid->seq)) { + return 0; + } + + rv = ngtcp2_dcidtr_track_retired_seq(dtr, dcid->seq); + if (rv != 0) { + return rv; + } + + if (!on_retire) { + return 0; + } + + return on_retire(dcid, user_data); +} + +ngtcp2_dcid *ngtcp2_dcidtr_find_bound_dcid(const ngtcp2_dcidtr *dtr, + const ngtcp2_path *path) { + ngtcp2_dcid *dcid; + const ngtcp2_ringbuf *rb = &dtr->bound.rb; + size_t i, len = ngtcp2_ringbuf_len(rb); + + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(rb, i); + + if (ngtcp2_path_eq(&dcid->ps.path, path)) { + return dcid; + } + } + + return NULL; +} + +ngtcp2_dcid *ngtcp2_dcidtr_bind_zerolen_dcid(ngtcp2_dcidtr *dtr, + const ngtcp2_path *path) { + ngtcp2_dcid *dcid = ngtcp2_ringbuf_push_back(&dtr->bound.rb); + ngtcp2_cid cid; + + ngtcp2_cid_zero(&cid); + ngtcp2_dcid_init(dcid, 0, &cid, NULL); + ngtcp2_dcid_set_path(dcid, path); + + return dcid; +} + +int ngtcp2_dcidtr_bind_dcid(ngtcp2_dcidtr *dtr, ngtcp2_dcid **pdest, + const ngtcp2_path *path, ngtcp2_tstamp ts, + ngtcp2_dcidtr_cb on_retire, void *user_data) { + const ngtcp2_dcid *src; + ngtcp2_dcid *dest; + int rv; + + if (ngtcp2_ringbuf_full(&dtr->bound.rb)) { + rv = dcidtr_on_retire(dtr, ngtcp2_ringbuf_get(&dtr->bound.rb, 0), on_retire, + user_data); + if (rv != 0) { + return rv; + } + } + + src = ngtcp2_ringbuf_get(&dtr->unused.rb, 0); + dest = ngtcp2_ringbuf_push_back(&dtr->bound.rb); + + ngtcp2_dcid_copy(dest, src); + dest->bound_ts = ts; + ngtcp2_dcid_set_path(dest, path); + + ngtcp2_ringbuf_pop_front(&dtr->unused.rb); + + *pdest = dest; + + return 0; +} + +static int verify_stateless_reset(const ngtcp2_ringbuf *rb, + const ngtcp2_path *path, + const uint8_t *token) { + const ngtcp2_dcid *dcid; + size_t i, len = ngtcp2_ringbuf_len(rb); + + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(rb, i); + if (ngtcp2_dcid_verify_stateless_reset_token(dcid, path, token) == 0) { + return 0; + } + } + + return NGTCP2_ERR_INVALID_ARGUMENT; +} + +int ngtcp2_dcidtr_verify_stateless_reset(const ngtcp2_dcidtr *dtr, + const ngtcp2_path *path, + const uint8_t *token) { + int rv; + + rv = verify_stateless_reset(&dtr->retired.rb, path, token); + if (rv == 0) { + return 0; + } + + return verify_stateless_reset(&dtr->bound.rb, path, token); +} + +static int verify_token_uniqueness(const ngtcp2_ringbuf *rb, int *pfound, + uint64_t seq, const ngtcp2_cid *cid, + const uint8_t *token) { + const ngtcp2_dcid *dcid; + size_t i, len = ngtcp2_ringbuf_len(rb); + int rv; + + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(rb, i); + rv = ngtcp2_dcid_verify_uniqueness(dcid, seq, cid, token); + if (rv != 0) { + return NGTCP2_ERR_PROTO; + } + + if (ngtcp2_cid_eq(&dcid->cid, cid)) { + *pfound = 1; + } + } + + return 0; +} + +int ngtcp2_dcidtr_verify_token_uniqueness(const ngtcp2_dcidtr *dtr, int *pfound, + uint64_t seq, const ngtcp2_cid *cid, + const uint8_t *token) { + int rv; + + rv = verify_token_uniqueness(&dtr->bound.rb, pfound, seq, cid, token); + if (rv != 0) { + return rv; + } + + return verify_token_uniqueness(&dtr->unused.rb, pfound, seq, cid, token); +} + +static void remove_dcid_at(ngtcp2_ringbuf *rb, size_t at) { + const ngtcp2_dcid *src; + ngtcp2_dcid *dest; + + if (at == 0) { + ngtcp2_ringbuf_pop_front(rb); + return; + } + + if (at == ngtcp2_ringbuf_len(rb) - 1) { + ngtcp2_ringbuf_pop_back(rb); + return; + } + + src = ngtcp2_ringbuf_get(rb, ngtcp2_ringbuf_len(rb) - 1); + dest = ngtcp2_ringbuf_get(rb, at); + + ngtcp2_dcid_copy(dest, src); + ngtcp2_ringbuf_pop_back(rb); +} + +static int dcidtr_retire_dcid_prior_to(ngtcp2_dcidtr *dtr, ngtcp2_ringbuf *rb, + uint64_t seq, ngtcp2_dcidtr_cb on_retire, + void *user_data) { + size_t i; + const ngtcp2_dcid *dcid; + int rv; + + for (i = 0; i < ngtcp2_ringbuf_len(rb);) { + dcid = ngtcp2_ringbuf_get(rb, i); + if (dcid->seq >= seq) { + ++i; + continue; + } + + rv = dcidtr_on_retire(dtr, dcid, on_retire, user_data); + if (rv != 0) { + return rv; + } + + remove_dcid_at(rb, i); + } + + return 0; +} + +int ngtcp2_dcidtr_retire_inactive_dcid_prior_to(ngtcp2_dcidtr *dtr, + uint64_t seq, + ngtcp2_dcidtr_cb on_retire, + void *user_data) { + int rv; + + rv = + dcidtr_retire_dcid_prior_to(dtr, &dtr->bound.rb, seq, on_retire, user_data); + if (rv != 0) { + return rv; + } + + return dcidtr_retire_dcid_prior_to(dtr, &dtr->unused.rb, seq, on_retire, + user_data); +} + +int ngtcp2_dcidtr_retire_active_dcid(ngtcp2_dcidtr *dtr, + const ngtcp2_dcid *dcid, ngtcp2_tstamp ts, + ngtcp2_dcidtr_cb on_deactivate, + void *user_data) { + ngtcp2_ringbuf *rb = &dtr->retired.rb; + const ngtcp2_dcid *stale_dcid; + ngtcp2_dcid *dest; + int rv; + + assert(dcid->cid.datalen); + + if (ngtcp2_ringbuf_full(rb)) { + stale_dcid = ngtcp2_ringbuf_get(rb, 0); + rv = on_deactivate(stale_dcid, user_data); + if (rv != 0) { + return rv; + } + } + + dest = ngtcp2_ringbuf_push_back(rb); + ngtcp2_dcid_copy(dest, dcid); + dest->retired_ts = ts; + + return dcidtr_on_retire(dtr, dest, NULL, NULL); +} + +int ngtcp2_dcidtr_remove_stale_retired_dcid(ngtcp2_dcidtr *dtr, + ngtcp2_duration timeout, + ngtcp2_tstamp ts, + ngtcp2_dcidtr_cb on_deactivate, + void *user_data) { + ngtcp2_ringbuf *rb = &dtr->retired.rb; + const ngtcp2_dcid *dcid; + int rv; + + for (; ngtcp2_ringbuf_len(rb);) { + dcid = ngtcp2_ringbuf_get(rb, 0); + if (ngtcp2_tstamp_not_elapsed(dcid->retired_ts, timeout, ts)) { + break; + } + + rv = on_deactivate(dcid, user_data); + if (rv != 0) { + return rv; + } + + ngtcp2_ringbuf_pop_front(rb); + } + + return 0; +} + +int ngtcp2_dcidtr_pop_bound_dcid(ngtcp2_dcidtr *dtr, ngtcp2_dcid *dest, + const ngtcp2_path *path) { + const ngtcp2_dcid *src; + ngtcp2_ringbuf *rb = &dtr->bound.rb; + size_t len = ngtcp2_ringbuf_len(rb); + size_t i; + + for (i = 0; i < len; ++i) { + src = ngtcp2_ringbuf_get(rb, i); + if (ngtcp2_path_eq(&src->ps.path, path)) { + ngtcp2_dcid_copy(dest, src); + remove_dcid_at(rb, i); + + return 0; + } + } + + return NGTCP2_ERR_INVALID_ARGUMENT; +} + +int ngtcp2_dcidtr_retire_stale_bound_dcid(ngtcp2_dcidtr *dtr, + ngtcp2_duration timeout, + ngtcp2_tstamp ts, + ngtcp2_dcidtr_cb on_retire, + void *user_data) { + ngtcp2_ringbuf *rb = &dtr->bound.rb; + size_t i; + const ngtcp2_dcid *dcid; + int rv; + + for (i = 0; i < ngtcp2_ringbuf_len(rb);) { + dcid = ngtcp2_ringbuf_get(rb, i); + + assert(dcid->cid.datalen); + + if (ngtcp2_tstamp_not_elapsed(dcid->bound_ts, timeout, ts)) { + ++i; + continue; + } + + rv = dcidtr_on_retire(dtr, dcid, on_retire, user_data); + if (rv != 0) { + return rv; + } + + remove_dcid_at(rb, i); + } + + return 0; +} + +ngtcp2_tstamp ngtcp2_dcidtr_earliest_bound_ts(const ngtcp2_dcidtr *dtr) { + const ngtcp2_ringbuf *rb = &dtr->bound.rb; + size_t i, len = ngtcp2_ringbuf_len(rb); + ngtcp2_tstamp res = UINT64_MAX; + const ngtcp2_dcid *dcid; + + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(rb, i); + + assert(dcid->cid.datalen); + assert(dcid->bound_ts != UINT64_MAX); + + res = ngtcp2_min_uint64(res, dcid->bound_ts); + } + + return res; +} + +ngtcp2_tstamp ngtcp2_dcidtr_earliest_retired_ts(const ngtcp2_dcidtr *dtr) { + const ngtcp2_ringbuf *rb = &dtr->retired.rb; + const ngtcp2_dcid *dcid; + + if (ngtcp2_ringbuf_len(rb) == 0) { + return UINT64_MAX; + } + + dcid = ngtcp2_ringbuf_get(rb, 0); + + return dcid->retired_ts; +} + +void ngtcp2_dcidtr_push_unused(ngtcp2_dcidtr *dtr, uint64_t seq, + const ngtcp2_cid *cid, const uint8_t *token) { + ngtcp2_dcid *dcid = ngtcp2_ringbuf_push_back(&dtr->unused.rb); + + ngtcp2_dcid_init(dcid, seq, cid, token); +} + +void ngtcp2_dcidtr_pop_unused_cid_token(ngtcp2_dcidtr *dtr, ngtcp2_dcid *dest) { + ngtcp2_ringbuf *rb = &dtr->unused.rb; + const ngtcp2_dcid *src; + + assert(ngtcp2_ringbuf_len(rb)); + + src = ngtcp2_ringbuf_get(rb, 0); + + dest->flags = NGTCP2_DCID_FLAG_NONE; + ngtcp2_dcid_copy_cid_token(dest, src); + + ngtcp2_ringbuf_pop_front(rb); +} + +void ngtcp2_dcidtr_pop_unused(ngtcp2_dcidtr *dtr, ngtcp2_dcid *dest) { + ngtcp2_ringbuf *rb = &dtr->unused.rb; + const ngtcp2_dcid *src; + + assert(ngtcp2_ringbuf_len(rb)); + + src = ngtcp2_ringbuf_get(rb, 0); + + ngtcp2_dcid_copy(dest, src); + + ngtcp2_ringbuf_pop_front(rb); +} + +int ngtcp2_dcidtr_check_path_retired(const ngtcp2_dcidtr *dtr, + const ngtcp2_path *path) { + const ngtcp2_ringbuf *rb = &dtr->retired.rb; + size_t i, len = ngtcp2_ringbuf_len(rb); + const ngtcp2_dcid *dcid; + + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(rb, i); + if (ngtcp2_path_eq(&dcid->ps.path, path)) { + return 1; + } + } + + return 0; +} + +size_t ngtcp2_dcidtr_unused_len(const ngtcp2_dcidtr *dtr) { + return ngtcp2_ringbuf_len(&dtr->unused.rb); +} + +size_t ngtcp2_dcidtr_bound_len(const ngtcp2_dcidtr *dtr) { + return ngtcp2_ringbuf_len(&dtr->bound.rb); +} + +size_t ngtcp2_dcidtr_retired_len(const ngtcp2_dcidtr *dtr) { + return ngtcp2_ringbuf_len(&dtr->retired.rb); +} + +size_t ngtcp2_dcidtr_inactive_len(const ngtcp2_dcidtr *dtr) { + return ngtcp2_ringbuf_len(&dtr->unused.rb) + + ngtcp2_ringbuf_len(&dtr->bound.rb); +} + +int ngtcp2_dcidtr_unused_full(const ngtcp2_dcidtr *dtr) { + return ngtcp2_ringbuf_full(&dtr->unused.rb); +} + +int ngtcp2_dcidtr_unused_empty(const ngtcp2_dcidtr *dtr) { + return ngtcp2_ringbuf_len(&dtr->unused.rb) == 0; +} + +int ngtcp2_dcidtr_bound_full(const ngtcp2_dcidtr *dtr) { + return ngtcp2_ringbuf_full(&dtr->bound.rb); +} diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.h new file mode 100644 index 00000000000000..17942389b814d1 --- /dev/null +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_dcidtr.h @@ -0,0 +1,343 @@ +/* + * ngtcp2 + * + * Copyright (c) 2025 ngtcp2 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 NGTCP2_DCIDTR_H +#define NGTCP2_DCIDTR_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* defined(HAVE_CONFIG_H) */ + +#include + +#include "ngtcp2_cid.h" +#include "ngtcp2_ringbuf.h" + +/* NGTCP2_DCIDTR_MAX_BOUND_DCID_SIZE is the maximum number of + Destination Connection ID which has been bound to a particular + path, but not yet used as primary path, and path validation is not + performed from the local endpoint. It must be the power of 2. */ +#define NGTCP2_DCIDTR_MAX_BOUND_DCID_SIZE 4 +/* NGTCP2_DCIDTR_MAX_UNUSED_DCID_SIZE is the maximum number of + Destination Connection ID the remote endpoint provides to store. + It must be the power of 2. */ +#define NGTCP2_DCIDTR_MAX_UNUSED_DCID_SIZE 8 +/* NGTCP2_DCIDTR_MAX_RETIRED_DCID_SIZE is the maximum number of + retired Destination Connection ID kept to catch in-flight packet on + a retired path. It must be the power of 2. */ +#define NGTCP2_DCIDTR_MAX_RETIRED_DCID_SIZE 2 + +ngtcp2_static_ringbuf_def(dcid_bound, NGTCP2_DCIDTR_MAX_BOUND_DCID_SIZE, + sizeof(ngtcp2_dcid)) +ngtcp2_static_ringbuf_def(dcid_unused, NGTCP2_DCIDTR_MAX_UNUSED_DCID_SIZE, + sizeof(ngtcp2_dcid)) +ngtcp2_static_ringbuf_def(dcid_retired, NGTCP2_DCIDTR_MAX_RETIRED_DCID_SIZE, + sizeof(ngtcp2_dcid)) + +/* + * ngtcp2_dcidtr stores unused, bound, and retired Destination + * Connection IDs. + */ +typedef struct ngtcp2_dcidtr { + /* unused is a set of unused Destination Connection ID received from + a remote endpoint. They are considered inactive. */ + ngtcp2_static_ringbuf_dcid_unused unused; + /* bound is a set of Destination Connection IDs which are bound to + particular paths. These paths are not validated yet. They are + considered inactive. */ + ngtcp2_static_ringbuf_dcid_bound bound; + /* retired is a set of Destination Connection ID retired by local + endpoint. Keep them in 3*PTO to catch packets in flight along + the old path. They are considered active. */ + ngtcp2_static_ringbuf_dcid_retired retired; + struct { + /* seqs contains sequence number of Destination Connection ID + whose retirement is not acknowledged by the remote endpoint + yet. */ + uint64_t seqs[NGTCP2_DCIDTR_MAX_UNUSED_DCID_SIZE * 2]; + /* len is the number of sequence numbers that seq contains. */ + size_t len; + } retire_unacked; +} ngtcp2_dcidtr; + +typedef int (*ngtcp2_dcidtr_cb)(const ngtcp2_dcid *dcid, void *user_data); + +/* + * ngtcp2_dcidtr_init initializes |dtr|. + */ +void ngtcp2_dcidtr_init(ngtcp2_dcidtr *dtr); + +/* + * ngtcp2_dcidtr_track_retired_seq tracks the sequence number |seq| of + * unacknowledged retiring Destination Connection ID. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CONNECTION_ID_LIMIT + * The number of unacknowledged retirement exceeds the limit. + */ +int ngtcp2_dcidtr_track_retired_seq(ngtcp2_dcidtr *dtr, uint64_t seq); + +/* + * ngtcp2_dcidtr_untrack_retired_seq deletes the sequence number |seq| + * of unacknowledged retiring Destination Connection ID. It is fine + * if such sequence number is not found. + */ +void ngtcp2_dcidtr_untrack_retired_seq(ngtcp2_dcidtr *dtr, uint64_t seq); + +/* + * ngtcp2_dcidtr_check_retired_seq_tracked returns nonzero if |seq| + * has already been tracked. + */ +int ngtcp2_dcidtr_check_retired_seq_tracked(const ngtcp2_dcidtr *dtr, + uint64_t seq); + +/* + * ngtcp2_dcidtr_find_bound_dcid returns the pointer to ngtcp2_dcid + * that bound to |path|. It returns NULL if there is no such + * ngtcp2_dcid. + */ +ngtcp2_dcid *ngtcp2_dcidtr_find_bound_dcid(const ngtcp2_dcidtr *dtr, + const ngtcp2_path *path); + +/* + * ngtcp2_dcidtr_bind_zerolen_dcid binds zero-length Destination + * Connection ID to |path|, and returns the pointer to the bound + * ngtcp2_dcid. + */ +ngtcp2_dcid *ngtcp2_dcidtr_bind_zerolen_dcid(ngtcp2_dcidtr *dtr, + const ngtcp2_path *path); + +/* + * ngtcp2_dcidtr_bind_dcid binds non-zero Destination Connection ID to + * |path|. |ts| is the current timestamp. The buffer space of bound + * Destination Connection ID is limited. If it is full, the earliest + * one is removed. |on_retire|, if specified, is called for the + * removed ngtcp2_dcid with |user_data|. This function assigns the + * pointer to bound ngtcp2_dcid to |*pdest|. + * + * This function returns 0 if it succeeds, or negative error code that + * |on_retire| returns. + */ +int ngtcp2_dcidtr_bind_dcid(ngtcp2_dcidtr *dtr, ngtcp2_dcid **pdest, + const ngtcp2_path *path, ngtcp2_tstamp ts, + ngtcp2_dcidtr_cb on_retire, void *user_data); + +/* + * ngtcp2_dcidtr_verify_stateless_reset verifies the stateless reset + * token |token| received from |path|. It returns 0 if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * There is no Destination Connection ID that matches the given + * |path| and |token|. + */ +int ngtcp2_dcidtr_verify_stateless_reset(const ngtcp2_dcidtr *dtr, + const ngtcp2_path *path, + const uint8_t *token); + +/* + * ngtcp2_dcidtr_verify_token_uniqueness verifies that the uniqueness + * of the combination of |seq|, |cid|, and |token| against the exiting + * Destination Connection IDs. That is: + * + * - If they do not share the same seq, then their Connection IDs must + * be different. + * + * - If they share the same seq, then their Connection IDs and tokens + * must be the same. + * + * If this function succeeds, and there is Destination Connection ID + * which shares |seq|, |cid|, and |token|, |*pfound| is set to + * nonzero. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_PROTO + * The given combination of values does not satisfy the above + * conditions. + */ +int ngtcp2_dcidtr_verify_token_uniqueness(const ngtcp2_dcidtr *dtr, int *pfound, + uint64_t seq, const ngtcp2_cid *cid, + const uint8_t *token); + +/* + * ngtcp2_dcidtr_retire_inactive_dcid_prior_to retires inactive + * Destination Connection IDs (unused or bound) whose seq is less than + * |seq|. For each retired ngtcp2_dcid, |on_retire|, if specified, is + * called with |user_data|. + * + * This function returns 0 if it succeeds, or negative error code that + * |on_retire| returns. + */ +int ngtcp2_dcidtr_retire_inactive_dcid_prior_to(ngtcp2_dcidtr *dtr, + uint64_t seq, + ngtcp2_dcidtr_cb on_retire, + void *user_data); + +/* + * ngtcp2_dcidtr_retire_active_dcid adds an active |dcid| to the + * retired Destination Connection ID buffer. The buffer space of + * retired Destination Connection ID is limited. If it is full, the + * earliest one is removed. |on_deactivate| is called for the removed + * ngtcp2_dcid with |user_data|. + * + * This function returns 0 if it succeeds, or negative error code that + * |on_deactivate| returns. + */ +int ngtcp2_dcidtr_retire_active_dcid(ngtcp2_dcidtr *dtr, + const ngtcp2_dcid *dcid, ngtcp2_tstamp ts, + ngtcp2_dcidtr_cb on_deactivate, + void *user_data); + +/* + * ngtcp2_dcidtr_retire_stale_bound_dcid retires stale bound + * Destination Connection ID. For each retired ngtcp2_dcid, + * |on_retire|, if specified, is called with |user_data|. + */ +int ngtcp2_dcidtr_retire_stale_bound_dcid(ngtcp2_dcidtr *dtr, + ngtcp2_duration timeout, + ngtcp2_tstamp ts, + ngtcp2_dcidtr_cb on_retire, + void *user_data); + +/* + * ngtcp2_dcidtr_remove_stale_retired_dcid removes stale retired + * Destination Connection ID. For each removed ngtcp2_dcid, + * |on_deactivate| is called with |user_data|. + * + * This function returns 0 if it succeeds, or negative error code that + * |on_deactivate| returns. + */ +int ngtcp2_dcidtr_remove_stale_retired_dcid(ngtcp2_dcidtr *dtr, + ngtcp2_duration timeout, + ngtcp2_tstamp ts, + ngtcp2_dcidtr_cb on_deactivate, + void *user_data); + +/* + * ngtcp2_dcidtr_pop_bound_dcid removes Destination Connection ID that + * is bound to |path|, and copies it into the object pointed by + * |dest|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * No ngtcp2_dcid bound to |path| found. + */ +int ngtcp2_dcidtr_pop_bound_dcid(ngtcp2_dcidtr *dtr, ngtcp2_dcid *dest, + const ngtcp2_path *path); + +/* + * ngtcp2_dcidtr_earliest_bound_ts returns earliest timestamp when a + * Destination Connection ID is bound. If there is no bound + * Destination Connection ID, this function returns UINT64_MAX. + */ +ngtcp2_tstamp ngtcp2_dcidtr_earliest_bound_ts(const ngtcp2_dcidtr *dtr); + +/* + * ngtcp2_dcidtr_earliest_retired_ts returns earliest timestamp when a + * Destination Connection ID is retired. If there is no retired + * Destination Connection ID, this function returns UINT64_MAX. + */ +ngtcp2_tstamp ngtcp2_dcidtr_earliest_retired_ts(const ngtcp2_dcidtr *dtr); + +/* + * ngtcp2_dcidtr_push_unused adds new Destination Connection ID to the + * unused buffer. |seq| is its sequence number, |cid| is its + * Connection ID, and |token| is its stateless reset token. If the + * buffer space is full, the earliest ngtcp2_dcid is removed. + */ +void ngtcp2_dcidtr_push_unused(ngtcp2_dcidtr *dtr, uint64_t seq, + const ngtcp2_cid *cid, const uint8_t *token); + +/* + * ngtcp2_dcidtr_pop_unused_cid_token removes an unused Destination + * Connection ID, and copies it into the object pointed by |dcid| with + * ngtcp2_dcid_copy_cid_token. This function assumes that there is at + * least one unused Destination Connection ID. + */ +void ngtcp2_dcidtr_pop_unused_cid_token(ngtcp2_dcidtr *dtr, ngtcp2_dcid *dcid); + +/* + * ngtcp2_dcidtr_pop_unused removes an unused Destination Connection + * ID, and copies it into the object pointed by |dcid| with + * ngtcp2_dcid_copy. This function assumes that there is at least one + * unused Destination Connection ID. + */ +void ngtcp2_dcidtr_pop_unused(ngtcp2_dcidtr *dtr, ngtcp2_dcid *dcid); + +/* + * ngtcp2_dcidtr_check_path_retired returns nonzero if |path| is + * included in retired Destination Connection IDs. + */ +int ngtcp2_dcidtr_check_path_retired(const ngtcp2_dcidtr *dtr, + const ngtcp2_path *path); + +/* + * ngtcp2_dcidtr_unused_len returns the number of unused Destination + * Connection ID. + */ +size_t ngtcp2_dcidtr_unused_len(const ngtcp2_dcidtr *dtr); + +/* + * ngtcp2_dcidtr_bound_len returns the number of bound Destination + * Connection ID. + */ +size_t ngtcp2_dcidtr_bound_len(const ngtcp2_dcidtr *dtr); + +/* + * ngtcp2_dcidtr_retired_len returns the number of retired Destination + * Connection ID. + */ +size_t ngtcp2_dcidtr_retired_len(const ngtcp2_dcidtr *dtr); + +/* + * ngtcp2_dcidtr_inactive_len returns the number of unused and bound + * Destination Connection ID. + */ +size_t ngtcp2_dcidtr_inactive_len(const ngtcp2_dcidtr *dtr); + +/* + * ngtcp2_dcidtr_unused_full returns nonzero if the buffer of unused + * Destination Connection ID is full. + */ +int ngtcp2_dcidtr_unused_full(const ngtcp2_dcidtr *dtr); + +/* + * ngtcp2_dcidtr_unused_empty returns nonzero if the buffer of unused + * Destination Connection ID is empty. + */ +int ngtcp2_dcidtr_unused_empty(const ngtcp2_dcidtr *dtr); + +/* + * ngtcp2_dcidtr_bound_full returns nonzero if the buffer of bound + * Destination Connection ID is full. + */ +int ngtcp2_dcidtr_bound_full(const ngtcp2_dcidtr *dtr); + +#endif /* NGTCP2_DCIDTR_H */ diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_frame_chain.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_frame_chain.c index 6a8a22c3f0d010..0f6b06a788d174 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_frame_chain.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_frame_chain.c @@ -29,17 +29,6 @@ ngtcp2_objalloc_def(frame_chain, ngtcp2_frame_chain, oplent) -int ngtcp2_frame_chain_new(ngtcp2_frame_chain **pfrc, const ngtcp2_mem *mem) { - *pfrc = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_frame_chain)); - if (*pfrc == NULL) { - return NGTCP2_ERR_NOMEM; - } - - ngtcp2_frame_chain_init(*pfrc); - - return 0; -} - int ngtcp2_frame_chain_objalloc_new(ngtcp2_frame_chain **pfrc, ngtcp2_objalloc *objalloc) { *pfrc = ngtcp2_objalloc_frame_chain_get(objalloc); @@ -83,13 +72,13 @@ int ngtcp2_frame_chain_new_token_objalloc_new(ngtcp2_frame_chain **pfrc, size_t tokenlen, ngtcp2_objalloc *objalloc, const ngtcp2_mem *mem) { - size_t avail = sizeof(ngtcp2_frame) - sizeof(ngtcp2_new_token); int rv; uint8_t *p; ngtcp2_frame *fr; - if (tokenlen > avail) { - rv = ngtcp2_frame_chain_extralen_new(pfrc, tokenlen - avail, mem); + if (tokenlen > NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES) { + rv = ngtcp2_frame_chain_extralen_new( + pfrc, tokenlen - NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES, mem); } else { rv = ngtcp2_frame_chain_objalloc_new(pfrc, objalloc); } @@ -144,8 +133,7 @@ void ngtcp2_frame_chain_objalloc_del(ngtcp2_frame_chain *frc, break; case NGTCP2_FRAME_NEW_TOKEN: - if (frc->fr.new_token.tokenlen > - sizeof(ngtcp2_frame) - sizeof(ngtcp2_new_token)) { + if (frc->fr.new_token.tokenlen > NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES) { ngtcp2_frame_chain_del(frc, mem); return; diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_frame_chain.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_frame_chain.h index e5b6779c0f03c2..e7b33632529cd4 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_frame_chain.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_frame_chain.h @@ -95,28 +95,15 @@ int ngtcp2_bind_frame_chains(ngtcp2_frame_chain *a, ngtcp2_frame_chain *b, #define NGTCP2_MAX_STREAM_DATACNT 256 /* - * ngtcp2_frame_chain_new allocates ngtcp2_frame_chain object and - * assigns its pointer to |*pfrc|. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGTCP2_ERR_NOMEM - * Out of memory. - */ -int ngtcp2_frame_chain_new(ngtcp2_frame_chain **pfrc, const ngtcp2_mem *mem); - -/* - * ngtcp2_frame_chain_objalloc_new behaves like - * ngtcp2_frame_chain_new, but it uses |objalloc| to allocate the object. + * ngtcp2_frame_chain_objalloc_new allocates ngtcp2_frame_chain using + * |objalloc|. */ int ngtcp2_frame_chain_objalloc_new(ngtcp2_frame_chain **pfrc, ngtcp2_objalloc *objalloc); /* - * ngtcp2_frame_chain_extralen_new works like ngtcp2_frame_chain_new, - * but it allocates extra memory |extralen| in order to extend - * ngtcp2_frame. + * ngtcp2_frame_chain_extralen_new allocates ngtcp2_frame_chain + * followed by |extralen| bytes. */ int ngtcp2_frame_chain_extralen_new(ngtcp2_frame_chain **pfrc, size_t extralen, const ngtcp2_mem *mem); @@ -134,12 +121,18 @@ int ngtcp2_frame_chain_extralen_new(ngtcp2_frame_chain **pfrc, size_t extralen, #define NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES \ (NGTCP2_FRAME_CHAIN_STREAM_AVAIL / sizeof(ngtcp2_vec) + 1) +/* NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES is the length of a token that + changes allocation method. If the length is more than this value, + ngtcp2_frame_chain is allocated without ngtcp2_objalloc. + Otherwise, it is allocated using ngtcp2_objalloc. */ +#define NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES \ + (sizeof(ngtcp2_frame) - sizeof(ngtcp2_new_token)) + /* - * ngtcp2_frame_chain_stream_datacnt_objalloc_new works like - * ngtcp2_frame_chain_new, but it allocates enough data to store - * additional |datacnt| - 1 ngtcp2_vec object after ngtcp2_stream - * object. If no additional space is required, in other words, - * |datacnt| <= NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES, + * ngtcp2_frame_chain_stream_datacnt_objalloc_new allocates enough + * data to store additional |datacnt| - 1 ngtcp2_vec object after + * ngtcp2_stream object. If no additional space is required, in other + * words, |datacnt| <= NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES, * ngtcp2_frame_chain_objalloc_new is called internally. Otherwise, * ngtcp2_frame_chain_extralen_new is used and objalloc is not used. * Therefore, it is important to call ngtcp2_frame_chain_objalloc_del @@ -150,6 +143,13 @@ int ngtcp2_frame_chain_stream_datacnt_objalloc_new(ngtcp2_frame_chain **pfrc, ngtcp2_objalloc *objalloc, const ngtcp2_mem *mem); +/* + * ngtcp2_frame_chain_new_token_objalloc_new allocates enough space to + * store the given token. If |tokenlen| <= + * NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES, ngtcp2_frame_chain_objalloc_new + * is called internally. Otherwise, ngtcp2_frame_chain_extralen_new + * is used, and objalloc is not used. + */ int ngtcp2_frame_chain_new_token_objalloc_new(ngtcp2_frame_chain **pfrc, const uint8_t *token, size_t tokenlen, diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_gaptr.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_gaptr.c index 3bfa398480c382..d04b9634c20528 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_gaptr.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_gaptr.c @@ -35,7 +35,9 @@ void ngtcp2_gaptr_init(ngtcp2_gaptr *gaptr, const ngtcp2_mem *mem) { } static int gaptr_gap_init(ngtcp2_gaptr *gaptr) { - ngtcp2_range range = {0, UINT64_MAX}; + ngtcp2_range range = { + .end = UINT64_MAX, + }; return ngtcp2_ksl_insert(&gaptr->gap, NULL, &range, NULL); } @@ -50,7 +52,11 @@ void ngtcp2_gaptr_free(ngtcp2_gaptr *gaptr) { int ngtcp2_gaptr_push(ngtcp2_gaptr *gaptr, uint64_t offset, uint64_t datalen) { int rv; - ngtcp2_range k, m, l, r, q = {offset, offset + datalen}; + ngtcp2_range k, m, l, r; + ngtcp2_range q = { + .begin = offset, + .end = offset + datalen, + }; ngtcp2_ksl_it it; if (ngtcp2_ksl_len(&gaptr->gap) == 0) { @@ -110,11 +116,16 @@ uint64_t ngtcp2_gaptr_first_gap_offset(const ngtcp2_gaptr *gaptr) { ngtcp2_range ngtcp2_gaptr_get_first_gap_after(const ngtcp2_gaptr *gaptr, uint64_t offset) { - ngtcp2_range q = {offset, offset + 1}; + ngtcp2_range q = { + .begin = offset, + .end = offset + 1, + }; ngtcp2_ksl_it it; if (ngtcp2_ksl_len(&gaptr->gap) == 0) { - ngtcp2_range r = {0, UINT64_MAX}; + ngtcp2_range r = { + .end = UINT64_MAX, + }; return r; } @@ -128,7 +139,10 @@ ngtcp2_range ngtcp2_gaptr_get_first_gap_after(const ngtcp2_gaptr *gaptr, int ngtcp2_gaptr_is_pushed(const ngtcp2_gaptr *gaptr, uint64_t offset, uint64_t datalen) { - ngtcp2_range q = {offset, offset + datalen}; + ngtcp2_range q = { + .begin = offset, + .end = offset + datalen, + }; ngtcp2_ksl_it it; ngtcp2_range m; @@ -138,6 +152,9 @@ int ngtcp2_gaptr_is_pushed(const ngtcp2_gaptr *gaptr, uint64_t offset, it = ngtcp2_ksl_lower_bound_search(&gaptr->gap, &q, ngtcp2_ksl_range_exclusive_search); + + assert(!ngtcp2_ksl_it_end(&it)); + m = ngtcp2_range_intersect(&q, (ngtcp2_range *)ngtcp2_ksl_it_key(&it)); return ngtcp2_range_len(&m) == 0; diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.c index 5e74f647241816..22c131a1ac677c 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.c @@ -33,7 +33,7 @@ #include "ngtcp2_mem.h" #include "ngtcp2_range.h" -static ngtcp2_ksl_blk null_blk = {{{NULL, NULL, 0, 0, {0}}}}; +static ngtcp2_ksl_blk null_blk; ngtcp2_objalloc_def(ksl_blk, ngtcp2_ksl_blk, oplent) diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_map.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_map.c index 0b66fceac6c993..5e4726e63ff7ef 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_map.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_map.c @@ -119,7 +119,11 @@ void ngtcp2_map_print_distance(const ngtcp2_map *map) { static int insert(ngtcp2_map_bucket *table, size_t hashbits, ngtcp2_map_key_type key, void *data) { size_t idx = hash(key, hashbits); - ngtcp2_map_bucket b = {0, key, data}, *bkt; + ngtcp2_map_bucket b = { + .key = key, + .data = data, + }; + ngtcp2_map_bucket *bkt; size_t mask = (1u << hashbits) - 1; for (;;) { diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_mem.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_mem.c index d30e1f986e97d3..48f430f474f126 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_mem.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_mem.c @@ -51,8 +51,12 @@ static void *default_realloc(void *ptr, size_t size, void *user_data) { return realloc(ptr, size); } -static const ngtcp2_mem mem_default = {NULL, default_malloc, default_free, - default_calloc, default_realloc}; +static const ngtcp2_mem mem_default = { + .malloc = default_malloc, + .free = default_free, + .calloc = default_calloc, + .realloc = default_realloc, +}; const ngtcp2_mem *ngtcp2_mem_default(void) { return &mem_default; } diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.c index 5c82e1bd503f98..d78978492ca8e7 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.c @@ -145,8 +145,7 @@ int ngtcp2_pkt_decode_version_cid(ngtcp2_version_cid *dest, const uint8_t *data, void ngtcp2_pkt_hd_init(ngtcp2_pkt_hd *hd, uint8_t flags, uint8_t type, const ngtcp2_cid *dcid, const ngtcp2_cid *scid, - int64_t pkt_num, size_t pkt_numlen, uint32_t version, - size_t len) { + int64_t pkt_num, size_t pkt_numlen, uint32_t version) { hd->flags = flags; hd->type = type; @@ -167,7 +166,7 @@ void ngtcp2_pkt_hd_init(ngtcp2_pkt_hd *hd, uint8_t flags, uint8_t type, hd->tokenlen = 0; hd->pkt_numlen = pkt_numlen; hd->version = version; - hd->len = len; + hd->len = 0; } ngtcp2_ssize ngtcp2_pkt_decode_hd_long(ngtcp2_pkt_hd *dest, const uint8_t *pkt, @@ -2285,8 +2284,7 @@ ngtcp2_ssize ngtcp2_pkt_write_retry( } ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_RETRY, dcid, - scid, /* pkt_num = */ 0, /* pkt_numlen = */ 1, version, - /* len = */ 0); + scid, /* pkt_num = */ 0, /* pkt_numlen = */ 1, version); pseudo_retrylen = ngtcp2_pkt_encode_pseudo_retry(pseudo_retry, sizeof(pseudo_retry), &hd, diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.h index 86ebecef7bc870..756076e7a7f258 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.h @@ -57,6 +57,10 @@ #define NGTCP2_STREAM_LEN_BIT 0x02 #define NGTCP2_STREAM_OFF_BIT 0x04 +/* NGTCP2_MIN_QUIC_PKTLEN is the minimum length of a valid QUIC + packet. */ +#define NGTCP2_MIN_QUIC_PKTLEN 21 + /* NGTCP2_STREAM_OVERHEAD is the maximum number of bytes required other than payload for STREAM frame. That is from type field to the beginning of the payload. */ @@ -407,13 +411,11 @@ void ngtcp2_pkt_chain_del(ngtcp2_pkt_chain *pc, const ngtcp2_mem *mem); * |dcid| and/or |scid| is NULL, Destination Connection ID and/or * Source Connection ID of |hd| is empty respectively. |pkt_numlen| * is the number of bytes used to encode |pkt_num| and either 1, 2, or - * 4. |version| is QUIC version for long header. |len| is the length - * field of Initial, 0RTT, and Handshake packets. + * 4. |version| is QUIC version for long header. */ void ngtcp2_pkt_hd_init(ngtcp2_pkt_hd *hd, uint8_t flags, uint8_t type, const ngtcp2_cid *dcid, const ngtcp2_cid *scid, - int64_t pkt_num, size_t pkt_numlen, uint32_t version, - size_t len); + int64_t pkt_num, size_t pkt_numlen, uint32_t version); /* * ngtcp2_pkt_encode_hd_long encodes |hd| as QUIC long header into diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pq.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pq.c index 19e3e3e36aa5e6..162bed00156755 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pq.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pq.c @@ -161,19 +161,3 @@ void ngtcp2_pq_remove(ngtcp2_pq *pq, ngtcp2_pq_entry *item) { int ngtcp2_pq_empty(const ngtcp2_pq *pq) { return pq->length == 0; } size_t ngtcp2_pq_size(const ngtcp2_pq *pq) { return pq->length; } - -int ngtcp2_pq_each(const ngtcp2_pq *pq, ngtcp2_pq_item_cb fun, void *arg) { - size_t i; - - if (pq->length == 0) { - return 0; - } - - for (i = 0; i < pq->length; ++i) { - if ((*fun)(pq->q[i], arg)) { - return 1; - } - } - - return 0; -} diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pq.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pq.h index 84961c9143978c..aa195a9f7baee3 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pq.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pq.h @@ -109,17 +109,6 @@ int ngtcp2_pq_empty(const ngtcp2_pq *pq); */ size_t ngtcp2_pq_size(const ngtcp2_pq *pq); -typedef int (*ngtcp2_pq_item_cb)(ngtcp2_pq_entry *item, void *arg); - -/* - * ngtcp2_pq_each applies |fun| to each item in |pq|. The |arg| is - * passed as arg parameter to callback function. This function must - * not change the ordering key. If the return value from callback is - * nonzero, this function returns 1 immediately without iterating - * remaining items. Otherwise this function returns 0. - */ -int ngtcp2_pq_each(const ngtcp2_pq *pq, ngtcp2_pq_item_cb fun, void *arg); - /* * ngtcp2_pq_remove removes |item| from |pq|. |pq| must contain * |item| otherwise the behavior is undefined. diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.c index e4fee94eb558d3..471f84c76440fe 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.c @@ -170,3 +170,10 @@ void ngtcp2_pv_cancel_expired_timer(ngtcp2_pv *pv, ngtcp2_tstamp ts) { pv->flags |= NGTCP2_PV_FLAG_CANCEL_TIMER; } + +void ngtcp2_pv_set_fallback(ngtcp2_pv *pv, const ngtcp2_dcid *dcid, + ngtcp2_duration pto) { + pv->flags |= NGTCP2_PV_FLAG_FALLBACK_PRESENT; + ngtcp2_dcid_copy(&pv->fallback_dcid, dcid); + pv->fallback_pto = pto; +} diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.h index e9573da497bee4..2d07e41648db2e 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pv.h @@ -71,11 +71,13 @@ void ngtcp2_pv_entry_init(ngtcp2_pv_entry *pvent, const uint8_t *data, /* NGTCP2_PV_FLAG_CANCEL_TIMER indicates that the expiry timer is cancelled. */ #define NGTCP2_PV_FLAG_CANCEL_TIMER 0x02u -/* NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE indicates that fallback DCID is - available in ngtcp2_pv. If path validation fails, fallback to the - fallback DCID. If path validation succeeds, fallback DCID is - retired if it does not equal to the current DCID. */ -#define NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE 0x04u +/* NGTCP2_PV_FLAG_FALLBACK_PRESENT indicates that a fallback + Destination Connection ID and PTO are available in ngtcp2_pv. If + path validation fails, then fallback to them. If path validation + succeeds, the fallback Destination Connection ID is retired if it + is not zero length, and does not equal to the current Destination + Connection ID. */ +#define NGTCP2_PV_FLAG_FALLBACK_PRESENT 0x04u /* NGTCP2_PV_FLAG_PREFERRED_ADDR indicates that client is migrating to server's preferred address. This flag is only used by client. */ #define NGTCP2_PV_FLAG_PREFERRED_ADDR 0x10u @@ -191,4 +193,10 @@ ngtcp2_tstamp ngtcp2_pv_next_expiry(ngtcp2_pv *pv); */ void ngtcp2_pv_cancel_expired_timer(ngtcp2_pv *pv, ngtcp2_tstamp ts); +/* + * ngtcp2_pv_set_fallback sets |dcid| and |pto| as fallback. + */ +void ngtcp2_pv_set_fallback(ngtcp2_pv *pv, const ngtcp2_dcid *dcid, + ngtcp2_duration pto); + #endif /* !defined(NGTCP2_PV_H) */ diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_range.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_range.c index 7bbefc0175c595..e89891532936e8 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_range.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_range.c @@ -32,7 +32,7 @@ void ngtcp2_range_init(ngtcp2_range *r, uint64_t begin, uint64_t end) { ngtcp2_range ngtcp2_range_intersect(const ngtcp2_range *a, const ngtcp2_range *b) { - ngtcp2_range r = {0, 0}; + ngtcp2_range r = {0}; uint64_t begin = ngtcp2_max_uint64(a->begin, b->begin); uint64_t end = ngtcp2_min_uint64(a->end, b->end); diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ringbuf.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ringbuf.c index 41446739bf699d..353afca4d48fb5 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ringbuf.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ringbuf.c @@ -122,4 +122,6 @@ void *ngtcp2_ringbuf_get(const ngtcp2_ringbuf *rb, size_t offset) { return &rb->buf[offset * rb->size]; } -int ngtcp2_ringbuf_full(ngtcp2_ringbuf *rb) { return rb->len == rb->mask + 1; } +int ngtcp2_ringbuf_full(const ngtcp2_ringbuf *rb) { + return rb->len == rb->mask + 1; +} diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ringbuf.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ringbuf.h index 6953ea6278f88d..d490524805b1e9 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ringbuf.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ringbuf.h @@ -113,7 +113,7 @@ void *ngtcp2_ringbuf_get(const ngtcp2_ringbuf *rb, size_t offset); #define ngtcp2_ringbuf_len(RB) ((RB)->len) /* ngtcp2_ringbuf_full returns nonzero if |rb| is full. */ -int ngtcp2_ringbuf_full(ngtcp2_ringbuf *rb); +int ngtcp2_ringbuf_full(const ngtcp2_ringbuf *rb); /* ngtcp2_static_ringbuf_def defines ngtcp2_ringbuf struct wrapper which uses a statically allocated buffer. ngtcp2_ringbuf_free diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.c index ce6c2113ddf1ce..853f1d650eaf54 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rob.c @@ -122,7 +122,10 @@ static int rob_write_data(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, size_t n; int rv; ngtcp2_rob_data *d; - ngtcp2_range range = {offset, offset + len}; + ngtcp2_range range = { + .begin = offset, + .end = offset + len, + }; ngtcp2_ksl_it it; for (it = ngtcp2_ksl_lower_bound_search(&rob->dataksl, &range, @@ -163,7 +166,11 @@ int ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, size_t datalen) { int rv; ngtcp2_rob_gap *g; - ngtcp2_range m, l, r, q = {offset, offset + datalen}; + ngtcp2_range m, l, r; + ngtcp2_range q = { + .begin = offset, + .end = offset + datalen, + }; ngtcp2_ksl_it it; it = ngtcp2_ksl_lower_bound_search(&rob->gapksl, &q, diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rst.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rst.c index 89c89acdc265a2..181691f3e69401 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rst.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rst.c @@ -46,6 +46,11 @@ void ngtcp2_rs_init(ngtcp2_rs *rs) { } void ngtcp2_rst_init(ngtcp2_rst *rst) { + rst->last_seq = -1; + ngtcp2_rst_reset(rst); +} + +void ngtcp2_rst_reset(ngtcp2_rst *rst) { ngtcp2_rs_init(&rst->rs); rst->delivered = 0; rst->delivered_ts = 0; @@ -53,7 +58,7 @@ void ngtcp2_rst_init(ngtcp2_rst *rst) { rst->app_limited = 0; rst->is_cwnd_limited = 0; rst->lost = 0; - rst->last_seq = -1; + rst->valid_after_seq = rst->last_seq; } void ngtcp2_rst_on_pkt_sent(ngtcp2_rst *rst, ngtcp2_rtb_entry *ent, @@ -108,6 +113,10 @@ void ngtcp2_rst_update_rate_sample(ngtcp2_rst *rst, const ngtcp2_rtb_entry *ent, ngtcp2_tstamp ts) { ngtcp2_rs *rs = &rst->rs; + if (ent->rst.end_seq <= rst->valid_after_seq) { + return; + } + rst->delivered += ent->pktlen; rst->delivered_ts = ts; diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rst.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rst.h index 95616eee97d99f..c2580306cc59fe 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rst.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rst.h @@ -73,11 +73,17 @@ typedef struct ngtcp2_rst { across all packet number spaces, we can replace this with a packet number. */ int64_t last_seq; + /* valid_after_seq is the sequence number, and ignore a packet if + the sequence number of the packet is less than or equal to this + number. */ + int64_t valid_after_seq; int is_cwnd_limited; } ngtcp2_rst; void ngtcp2_rst_init(ngtcp2_rst *rst); +void ngtcp2_rst_reset(ngtcp2_rst *rst); + void ngtcp2_rst_on_pkt_sent(ngtcp2_rst *rst, ngtcp2_rtb_entry *ent, const ngtcp2_conn_stat *cstat); void ngtcp2_rst_on_ack_recv(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat); diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.c index 090355f5dbc938..f7a7f5724b7ac3 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.c @@ -101,7 +101,6 @@ void ngtcp2_rtb_init(ngtcp2_rtb *rtb, ngtcp2_rst *rst, ngtcp2_cc *cc, rtb->probe_pkt_left = 0; rtb->cc_pkt_num = cc_pkt_num; rtb->cc_bytes_in_flight = 0; - rtb->persistent_congestion_start_ts = UINT64_MAX; rtb->num_lost_pkts = 0; rtb->num_lost_pmtud_pkts = 0; } @@ -672,8 +671,8 @@ static int process_acked_pkt(ngtcp2_rtb_entry *ent, ngtcp2_conn *conn, break; case NGTCP2_FRAME_RETIRE_CONNECTION_ID: - ngtcp2_conn_untrack_retired_dcid_seq(conn, - frc->fr.retire_connection_id.seq); + ngtcp2_dcidtr_untrack_retired_seq(&conn->dcid.dtr, + frc->fr.retire_connection_id.seq); break; case NGTCP2_FRAME_NEW_CONNECTION_ID: assert(conn->scid.num_in_flight); @@ -734,11 +733,11 @@ static void conn_verify_ecn(ngtcp2_conn *conn, ngtcp2_pktns *pktns, if ((ecn_acked && fr->type == NGTCP2_FRAME_ACK) || (fr->type == NGTCP2_FRAME_ACK_ECN && - (pktns->rx.ecn.ack.ect0 > fr->ecn.ect0 || - pktns->rx.ecn.ack.ect1 > fr->ecn.ect1 || - pktns->rx.ecn.ack.ce > fr->ecn.ce || - (fr->ecn.ect0 - pktns->rx.ecn.ack.ect0) + - (fr->ecn.ce - pktns->rx.ecn.ack.ce) < + (pktns->acktr.ecn.ack.ect0 > fr->ecn.ect0 || + pktns->acktr.ecn.ack.ect1 > fr->ecn.ect1 || + pktns->acktr.ecn.ack.ce > fr->ecn.ce || + (fr->ecn.ect0 - pktns->acktr.ecn.ack.ect0) + + (fr->ecn.ce - pktns->acktr.ecn.ack.ce) < ecn_acked || fr->ecn.ect0 > pktns->tx.ecn.ect0 || fr->ecn.ect1))) { ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, @@ -755,13 +754,13 @@ static void conn_verify_ecn(ngtcp2_conn *conn, ngtcp2_pktns *pktns, if (fr->type == NGTCP2_FRAME_ACK_ECN) { if (cc->congestion_event && largest_pkt_sent_ts != UINT64_MAX && - fr->ecn.ce > pktns->rx.ecn.ack.ce) { + fr->ecn.ce > pktns->acktr.ecn.ack.ce) { cc->congestion_event(cc, cstat, largest_pkt_sent_ts, 0, ts); } - pktns->rx.ecn.ack.ect0 = fr->ecn.ect0; - pktns->rx.ecn.ack.ect1 = fr->ecn.ect1; - pktns->rx.ecn.ack.ce = fr->ecn.ce; + pktns->acktr.ecn.ack.ect0 = fr->ecn.ect0; + pktns->acktr.ecn.ack.ect1 = fr->ecn.ect1; + pktns->acktr.ecn.ack.ce = fr->ecn.ce; } } @@ -1052,7 +1051,7 @@ static int rtb_detect_lost_pkt(ngtcp2_rtb *rtb, uint64_t *ppkt_lost, max_ack_delay) * NGTCP2_PERSISTENT_CONGESTION_THRESHOLD; - start_ts = ngtcp2_max_uint64(rtb->persistent_congestion_start_ts, + start_ts = ngtcp2_max_uint64(conn->handshake_confirmed_ts, cstat->first_rtt_sample_ts); for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) { diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.h index 2ef772b2e14f4b..3a9397eac5bb6f 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_rtb.h @@ -184,10 +184,6 @@ typedef struct ngtcp2_rtb { count a packet whose packet number is greater than or equals to cc_pkt_num. */ uint64_t cc_bytes_in_flight; - /* persistent_congestion_start_ts is the time when persistent - congestion evaluation is started. It happens roughly after - handshake is confirmed. */ - ngtcp2_tstamp persistent_congestion_start_ts; /* num_lost_pkts is the number entries in ents which has NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED flag set. */ size_t num_lost_pkts; diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_transport_params.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_transport_params.c index dda59c48858185..ca517532e3a695 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_transport_params.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_transport_params.c @@ -410,7 +410,7 @@ static int decode_varint(uint64_t *pdest, const uint8_t **pp, } len = ngtcp2_get_uvarintlen(p); - if ((uint64_t)(end - p) < len) { + if ((size_t)(end - p) < len) { return -1; } @@ -530,8 +530,11 @@ int ngtcp2_transport_params_decode_versioned(int transport_params_version, params->active_connection_id_limit = NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; - p = data; - end = data + datalen; + p = end = data; + + if (datalen) { + end += datalen; + } for (; (size_t)(end - p) >= 2;) { if (decode_varint(¶m_type, &p, end) != 0) { diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_vec.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_vec.c index 0b9c92d47d7d88..dbca8691d64888 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_vec.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_vec.c @@ -35,33 +35,6 @@ ngtcp2_vec *ngtcp2_vec_init(ngtcp2_vec *vec, const uint8_t *base, size_t len) { return vec; } -int ngtcp2_vec_new(ngtcp2_vec **pvec, const uint8_t *data, size_t datalen, - const ngtcp2_mem *mem) { - size_t len; - uint8_t *p; - - len = sizeof(ngtcp2_vec) + datalen; - - *pvec = ngtcp2_mem_malloc(mem, len); - if (*pvec == NULL) { - return NGTCP2_ERR_NOMEM; - } - - p = (uint8_t *)(*pvec) + sizeof(ngtcp2_vec); - (*pvec)->base = p; - (*pvec)->len = datalen; - - if (datalen) { - /* p = */ ngtcp2_cpymem(p, data, datalen); - } - - return 0; -} - -void ngtcp2_vec_del(ngtcp2_vec *vec, const ngtcp2_mem *mem) { - ngtcp2_mem_free(mem, vec); -} - uint64_t ngtcp2_vec_len(const ngtcp2_vec *vec, size_t n) { size_t i; size_t res = 0; @@ -225,13 +198,14 @@ size_t ngtcp2_vec_copy_at_most(ngtcp2_vec *dst, size_t dstcnt, continue; } - dst[j] = src[i]; - - if (dst[j].len > left) { + if (src[i].len > left) { + dst[j].base = src[i].base; dst[j].len = left; + return j + 1; } + dst[j] = src[i]; left -= dst[j].len; ++i; ++j; diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_vec.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_vec.h index f7611efcb7d6ce..55e735d164edcb 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_vec.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_vec.h @@ -46,22 +46,6 @@ */ ngtcp2_vec *ngtcp2_vec_init(ngtcp2_vec *vec, const uint8_t *base, size_t len); -/* - * ngtcp2_vec_new allocates and initializes |*pvec| with given |data| - * of length |datalen|. This function allocates memory for |*pvec| - * and the given data with a single allocation, and the contents - * pointed by |data| is copied into the allocated memory space. To - * free the allocated memory, call ngtcp2_vec_del. - */ -int ngtcp2_vec_new(ngtcp2_vec **pvec, const uint8_t *data, size_t datalen, - const ngtcp2_mem *mem); - -/* - * ngtcp2_vec_del frees the memory allocated by |vec| which is - * allocated and initialized by ngtcp2_vec_new. - */ -void ngtcp2_vec_del(ngtcp2_vec *vec, const ngtcp2_mem *mem); - /* * ngtcp2_vec_len returns the sum of length in |vec| of |n| elements. */ From 8076284f9ec383dd5e9c72467cd97aee1bfae9d5 Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Mon, 24 Feb 2025 19:53:37 -0500 Subject: [PATCH 91/92] deps: update cjs-module-lexer to 2.1.0 PR-URL: https://github.com/nodejs/node/pull/57180 Reviewed-By: Chemi Atlow Reviewed-By: Rafael Gonzaga --- deps/cjs-module-lexer/README.md | 19 +++++++++++++++++++ deps/cjs-module-lexer/dist/lexer.mjs | 2 +- deps/cjs-module-lexer/src/README.md | 19 +++++++++++++++++++ deps/cjs-module-lexer/src/build/Makefile | 21 ++++++++++++++++----- deps/cjs-module-lexer/src/package-lock.json | 4 ++-- deps/cjs-module-lexer/src/package.json | 2 +- src/cjs_module_lexer_version.h | 2 +- 7 files changed, 59 insertions(+), 10 deletions(-) diff --git a/deps/cjs-module-lexer/README.md b/deps/cjs-module-lexer/README.md index cc7ca50cc72243..addf5edc6b1193 100644 --- a/deps/cjs-module-lexer/README.md +++ b/deps/cjs-module-lexer/README.md @@ -456,6 +456,25 @@ If you need to build lib/lexer.wat (optional) you must first install [wabt](https://github.com/WebAssembly/wabt) as a sibling folder to this project. The wat file is then build by running `make lib/lexer.wat` +### Creating a Release +These are the steps to create and publish a release. You will need docker +installed as well as having installed [wabt](https://github.com/WebAssembly/wabt) +as outlined above: + +- [ ] Figure out if the release should be semver patch, minor or major based on the changes since + the last release and determine the new version. +- [ ] Update the package.json version, and run a full build and test + - npm install + - npm run build + - npm run test +- [ ] Commit and tag the changes, pushing up to main and the tag + - For example + - `git tag -a 1.4.2 -m "1.4.2"` + - `git push origin tag 1.4.2` +- [ ] Create the GitHub release +- [ ] Run npm publish from an account with access (asking somebody with access + the nodejs-foundation account is an option if you don't have access. + ### License MIT diff --git a/deps/cjs-module-lexer/dist/lexer.mjs b/deps/cjs-module-lexer/dist/lexer.mjs index 806bb46287259d..b6f5fb548bb73f 100644 --- a/deps/cjs-module-lexer/dist/lexer.mjs +++ b/deps/cjs-module-lexer/dist/lexer.mjs @@ -1,2 +1,2 @@ -/* cjs-module-lexer 2.0.0 */ +/* cjs-module-lexer 2.1.0 */ let A;const B=1===new Uint8Array(new Uint16Array([1]).buffer)[0];export function parse(I,C="@"){if(!A)throw new Error("Not initialized");const w=I.length+1,D=(A.__heap_base.value||A.__heap_base)+4*w-A.memory.buffer.byteLength;D>0&&A.memory.grow(Math.ceil(D/65536));const G=A.sa(w);(B?g:E)(I,new Uint16Array(A.memory.buffer,G,w));const S=A.parseCJS(G,I.length,0,0,0);if(S){const B=new Error(`Parse error ${C}${A.e()}:${I.slice(0,A.e()).split("\n").length}:${A.e()-I.lastIndexOf("\n",A.e()-1)}`);throw Object.assign(B,{idx:A.e()}),5!==S&&6!==S&&7!==S||Object.assign(B,{code:"ERR_LEXER_ESM_SYNTAX"}),B}let R=new Set,o=new Set,H=new Set;for(;A.rre();){const B=Q(I.slice(A.res(),A.ree()));B&&o.add(B)}for(;A.ru();)H.add(Q(I.slice(A.us(),A.ue())));for(;A.re();){let B=Q(I.slice(A.es(),A.ee()));void 0===B||H.has(B)||R.add(B)}return{exports:[...R],reexports:[...o]}}function Q(A){if('"'!==A[0]&&"'"!==A[0])return A;try{const B=(0,eval)(A);for(let A=0;A>>8}}function g(A,B){const Q=A.length;let E=0;for(;EA.charCodeAt(0))}let C;export function init(){return C||(C=(async()=>{const B=await WebAssembly.compile(I()),{exports:Q}=await WebAssembly.instantiate(B);A=Q})())}export function initSync(){if(A)return;const B=new WebAssembly.Module(I()),{exports:Q}=new WebAssembly.Instance(B);A=Q} \ No newline at end of file diff --git a/deps/cjs-module-lexer/src/README.md b/deps/cjs-module-lexer/src/README.md index cc7ca50cc72243..addf5edc6b1193 100755 --- a/deps/cjs-module-lexer/src/README.md +++ b/deps/cjs-module-lexer/src/README.md @@ -456,6 +456,25 @@ If you need to build lib/lexer.wat (optional) you must first install [wabt](https://github.com/WebAssembly/wabt) as a sibling folder to this project. The wat file is then build by running `make lib/lexer.wat` +### Creating a Release +These are the steps to create and publish a release. You will need docker +installed as well as having installed [wabt](https://github.com/WebAssembly/wabt) +as outlined above: + +- [ ] Figure out if the release should be semver patch, minor or major based on the changes since + the last release and determine the new version. +- [ ] Update the package.json version, and run a full build and test + - npm install + - npm run build + - npm run test +- [ ] Commit and tag the changes, pushing up to main and the tag + - For example + - `git tag -a 1.4.2 -m "1.4.2"` + - `git push origin tag 1.4.2` +- [ ] Create the GitHub release +- [ ] Run npm publish from an account with access (asking somebody with access + the nodejs-foundation account is an option if you don't have access. + ### License MIT diff --git a/deps/cjs-module-lexer/src/build/Makefile b/deps/cjs-module-lexer/src/build/Makefile index f13c390b8cdb96..b8e96d82534f7d 100755 --- a/deps/cjs-module-lexer/src/build/Makefile +++ b/deps/cjs-module-lexer/src/build/Makefile @@ -1,10 +1,21 @@ +# These flags depend on the system and may be overridden +WASM_CC := clang +WASM_CFLAGS := --sysroot=/usr/share/wasi-sysroot +WASM_LDFLAGS := -nostartfiles + +# These are project-specific and are expected to be kept intact +WASM_TARGET := -target wasm32-unknown-wasi +WASM_EXTRA_CFLAGS := -I include-wasm/ -Wno-logical-op-parentheses -Wno-parentheses -Oz +WASM_EXTRA_LDFLAGS := -Wl,-z,stack-size=13312,--no-entry,--compress-relocations,--strip-all +WASM_EXTRA_LDFLAGS += -Wl,--export=__heap_base,--export=parseCJS,--export=sa +WASM_EXTRA_LDFLAGS += -Wl,--export=e,--export=re,--export=es,--export=ee +WASM_EXTRA_LDFLAGS += -Wl,--export=rre,--export=ree,--export=res,--export=ru,--export=us,--export=ue + lib/lexer.wasm: include-wasm/cjs-module-lexer.h src/lexer.c @mkdir -p lib - clang --sysroot=/usr/share/wasi-sysroot -target wasm32-unknown-wasi src/lexer.c -I include-wasm -o lib/lexer.wasm -nostartfiles \ - -Wl,-z,stack-size=13312,--no-entry,--compress-relocations,--strip-all,--export=__heap_base,\ - --export=parseCJS,--export=sa,--export=e,--export=re,--export=es,--export=ee,--export=rre,--export=ree,--export=res,--export=ru,--export=us,--export=ue \ - -Wno-logical-op-parentheses -Wno-parentheses \ - -Oz + $(WASM_CC) $(WASM_CFLAGS) $(WASM_TARGET) $(WASM_EXTRA_CFLAGS) \ + src/lexer.c -o lib/lexer.wasm \ + $(WASM_LDFLAGS) $(WASM_EXTRA_LDFLAGS) optimize: lib/lexer.wasm ${WASM_OPT} -Oz lib/lexer.wasm -o lib/lexer.wasm diff --git a/deps/cjs-module-lexer/src/package-lock.json b/deps/cjs-module-lexer/src/package-lock.json index 3c5f50d681e7b3..e8de2e3e921611 100644 --- a/deps/cjs-module-lexer/src/package-lock.json +++ b/deps/cjs-module-lexer/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "cjs-module-lexer", - "version": "2.0.0", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cjs-module-lexer", - "version": "2.0.0", + "version": "2.1.0", "license": "MIT", "devDependencies": { "@babel/cli": "^7.5.5", diff --git a/deps/cjs-module-lexer/src/package.json b/deps/cjs-module-lexer/src/package.json index d59353db6da501..9ef0db81e12b46 100755 --- a/deps/cjs-module-lexer/src/package.json +++ b/deps/cjs-module-lexer/src/package.json @@ -1,6 +1,6 @@ { "name": "cjs-module-lexer", - "version": "2.0.0", + "version": "2.1.0", "description": "Lexes CommonJS modules, returning their named exports metadata", "main": "lexer.js", "exports": { diff --git a/src/cjs_module_lexer_version.h b/src/cjs_module_lexer_version.h index da33536cce1b80..8d754aadde0121 100644 --- a/src/cjs_module_lexer_version.h +++ b/src/cjs_module_lexer_version.h @@ -2,5 +2,5 @@ // Refer to tools/dep_updaters/update-cjs-module-lexer.sh #ifndef SRC_CJS_MODULE_LEXER_VERSION_H_ #define SRC_CJS_MODULE_LEXER_VERSION_H_ -#define CJS_MODULE_LEXER_VERSION "2.0.0" +#define CJS_MODULE_LEXER_VERSION "2.1.0" #endif // SRC_CJS_MODULE_LEXER_VERSION_H_ From 9d2368f64329bf194c4e82b349e76fdad879d32a Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Tue, 25 Feb 2025 11:26:36 -0500 Subject: [PATCH 92/92] 2025-02-26, Version 23.9.0 (Current) Notable changes: dns: * (SEMVER-MINOR) add TLSA record query and parsing (Rithvik Vibhu) https://github.com/nodejs/node/pull/52983 process: * (SEMVER-MINOR) add threadCpuUsage (Paolo Insogna) https://github.com/nodejs/node/pull/56467 PR-URL: https://github.com/nodejs/node/pull/57207 --- CHANGELOG.md | 3 +- doc/api/dns.md | 4 +- doc/api/process.md | 2 +- doc/api/test.md | 2 +- doc/changelogs/CHANGELOG_V23.md | 104 ++++++++++++++++++++++++++++++++ src/node_version.h | 6 +- 6 files changed, 113 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caa3168d643d08..02528747f50d42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,8 @@ release. -23.8.0
+23.9.0
+23.8.0
23.7.0
23.6.1
23.6.0
diff --git a/doc/api/dns.md b/doc/api/dns.md index 5dc7a5230f5fec..f60397237740fd 100644 --- a/doc/api/dns.md +++ b/doc/api/dns.md @@ -808,7 +808,7 @@ be an array of objects with the following properties: ## `dns.resolveTlsa(hostname, callback)` @@ -1502,7 +1502,7 @@ the following properties: ### `dnsPromises.resolveTlsa(hostname)` * `hostname` {string} diff --git a/doc/api/process.md b/doc/api/process.md index a58d7cc03cfff0..f03cfa88d83513 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -4207,7 +4207,7 @@ Thrown: ## `process.threadCpuUsage([previousValue])` * `previousValue` {Object} A previous return value from calling diff --git a/doc/api/test.md b/doc/api/test.md index 5135b0c39ccdb0..e82814a650fa22 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -3406,7 +3406,7 @@ added: - v20.15.0 changes: - version: - - REPLACEME + - v23.9.0 pr-url: https://github.com/nodejs/node/pull/56765 description: Add the `options` parameter. - version: v23.4.0 diff --git a/doc/changelogs/CHANGELOG_V23.md b/doc/changelogs/CHANGELOG_V23.md index f3af7367df9c04..c5e0178601f74d 100644 --- a/doc/changelogs/CHANGELOG_V23.md +++ b/doc/changelogs/CHANGELOG_V23.md @@ -8,6 +8,7 @@ +23.9.0
23.8.0
23.7.0
23.6.1
@@ -47,6 +48,109 @@ * [io.js](CHANGELOG_IOJS.md) * [Archive](CHANGELOG_ARCHIVE.md) + + +## 2025-02-26, Version 23.9.0 (Current), @targos + +### Notable Changes + +* \[[`927d985aa0`](https://github.com/nodejs/node/commit/927d985aa0)] - **(SEMVER-MINOR)** **dns**: add TLSA record query and parsing (Rithvik Vibhu) [#52983](https://github.com/nodejs/node/pull/52983) +* \[[`0236fbf75a`](https://github.com/nodejs/node/commit/0236fbf75a)] - **(SEMVER-MINOR)** **process**: add threadCpuUsage (Paolo Insogna) [#56467](https://github.com/nodejs/node/pull/56467) + +### Commits + +* \[[`f4a82fddb1`](https://github.com/nodejs/node/commit/f4a82fddb1)] - **benchmark**: add a warmup on bench-openSync (Elves Vieira) [#57051](https://github.com/nodejs/node/pull/57051) +* \[[`b384baa073`](https://github.com/nodejs/node/commit/b384baa073)] - **build**: print 'Formatting Markdown...' for long task markdown formatting (1ilsang) [#57108](https://github.com/nodejs/node/pull/57108) +* \[[`fec2d50308`](https://github.com/nodejs/node/commit/fec2d50308)] - **build**: add skip\_apidoc\_files and include QUIC (RafaelGSS) [#56941](https://github.com/nodejs/node/pull/56941) +* \[[`5af35d1850`](https://github.com/nodejs/node/commit/5af35d1850)] - **build**: fix GN build failure (Cheng) [#57013](https://github.com/nodejs/node/pull/57013) +* \[[`35f89aa66f`](https://github.com/nodejs/node/commit/35f89aa66f)] - **build**: fix GN build of uv (Cheng) [#56955](https://github.com/nodejs/node/pull/56955) +* \[[`e26d4841d1`](https://github.com/nodejs/node/commit/e26d4841d1)] - **cli**: allow --cpu-prof\* in NODE\_OPTIONS (Carlos Espa) [#57018](https://github.com/nodejs/node/pull/57018) +* \[[`b50fc42a99`](https://github.com/nodejs/node/commit/b50fc42a99)] - **crypto**: support --use-system-ca on non-Windows and non-macOS (Joyee Cheung) [#57009](https://github.com/nodejs/node/pull/57009) +* \[[`dfdaa92a37`](https://github.com/nodejs/node/commit/dfdaa92a37)] - **crypto**: fix missing OPENSSL\_NO\_ENGINE guard (Shelley Vohr) [#57012](https://github.com/nodejs/node/pull/57012) +* \[[`18ea88bcbe`](https://github.com/nodejs/node/commit/18ea88bcbe)] - **crypto**: cleanup root certificates and skip PEM deserialization (Joyee Cheung) [#56999](https://github.com/nodejs/node/pull/56999) +* \[[`8076284f9e`](https://github.com/nodejs/node/commit/8076284f9e)] - **deps**: update cjs-module-lexer to 2.1.0 (Node.js GitHub Bot) [#57180](https://github.com/nodejs/node/pull/57180) +* \[[`8644cf3e5a`](https://github.com/nodejs/node/commit/8644cf3e5a)] - **deps**: update ngtcp2 to 1.11.0 (Node.js GitHub Bot) [#57179](https://github.com/nodejs/node/pull/57179) +* \[[`2aceca15d6`](https://github.com/nodejs/node/commit/2aceca15d6)] - **deps**: update sqlite to 3.49.1 (Node.js GitHub Bot) [#57178](https://github.com/nodejs/node/pull/57178) +* \[[`8421021427`](https://github.com/nodejs/node/commit/8421021427)] - **deps**: update ada to 3.1.0 (Node.js GitHub Bot) [#57083](https://github.com/nodejs/node/pull/57083) +* \[[`21d795a5f0`](https://github.com/nodejs/node/commit/21d795a5f0)] - **(SEMVER-MINOR)** **dns**: add TLSA record query and parsing (Rithvik Vibhu) [#52983](https://github.com/nodejs/node/pull/52983) +* \[[`455bf5a0a8`](https://github.com/nodejs/node/commit/455bf5a0a8)] - **doc**: update options to filehandle.appendFile() (Hasegawa-Yukihiro) [#56972](https://github.com/nodejs/node/pull/56972) +* \[[`f35bd869ee`](https://github.com/nodejs/node/commit/f35bd869ee)] - **doc**: add additional caveat for fs.watch (Michael Dawson) [#57150](https://github.com/nodejs/node/pull/57150) +* \[[`4413ce7ed3`](https://github.com/nodejs/node/commit/4413ce7ed3)] - **doc**: fix typo in Windows building instructions (Tim Jacomb) [#57158](https://github.com/nodejs/node/pull/57158) +* \[[`66614cfcf3`](https://github.com/nodejs/node/commit/66614cfcf3)] - **doc**: fix web.libera.chat link in pull-requests.md (Samuel Bronson) [#57076](https://github.com/nodejs/node/pull/57076) +* \[[`587112cb08`](https://github.com/nodejs/node/commit/587112cb08)] - **doc**: remove buffered flag from performance hooks examples (Pavel Romanov) [#52607](https://github.com/nodejs/node/pull/52607) +* \[[`fdc8aeb8a0`](https://github.com/nodejs/node/commit/fdc8aeb8a0)] - **doc**: fix 'introduced\_in' version in typescript module (1ilsang) [#57109](https://github.com/nodejs/node/pull/57109) +* \[[`b6960499c8`](https://github.com/nodejs/node/commit/b6960499c8)] - **doc**: fix link and history of `SourceMap` sections (Antoine du Hamel) [#57098](https://github.com/nodejs/node/pull/57098) +* \[[`0de128ca97`](https://github.com/nodejs/node/commit/0de128ca97)] - **doc**: add `module namespace object` links (Dario Piotrowicz) [#57093](https://github.com/nodejs/node/pull/57093) +* \[[`5a74568320`](https://github.com/nodejs/node/commit/5a74568320)] - **doc**: disambiguate pseudo-code statement (Dario Piotrowicz) [#57092](https://github.com/nodejs/node/pull/57092) +* \[[`46df14ddcb`](https://github.com/nodejs/node/commit/46df14ddcb)] - **doc**: update clang-cl on Windows building guide (Joyee Cheung) [#57087](https://github.com/nodejs/node/pull/57087) +* \[[`4b02fdc72f`](https://github.com/nodejs/node/commit/4b02fdc72f)] - **doc**: update Xcode version used for arm64 and pkg (Michaël Zasso) [#57104](https://github.com/nodejs/node/pull/57104) +* \[[`78d4e52a52`](https://github.com/nodejs/node/commit/78d4e52a52)] - **doc**: fix wrong articles used to address modules (Dario Piotrowicz) [#57090](https://github.com/nodejs/node/pull/57090) +* \[[`ed5671f1bc`](https://github.com/nodejs/node/commit/ed5671f1bc)] - **doc**: update `module.builtinModules` sentence (Dario Piotrowicz) [#57089](https://github.com/nodejs/node/pull/57089) +* \[[`9de45cbac9`](https://github.com/nodejs/node/commit/9de45cbac9)] - **doc**: `modules.md`: fix `distance` definition (Alexander “weej” Jones) [#57046](https://github.com/nodejs/node/pull/57046) +* \[[`a7e5ef9e01`](https://github.com/nodejs/node/commit/a7e5ef9e01)] - **doc**: fix wrong verb form (Dario Piotrowicz) [#57091](https://github.com/nodejs/node/pull/57091) +* \[[`c02494f5fe`](https://github.com/nodejs/node/commit/c02494f5fe)] - **doc**: fix transpiler loader hooks documentation (Joyee Cheung) [#57037](https://github.com/nodejs/node/pull/57037) +* \[[`5b2dfadd40`](https://github.com/nodejs/node/commit/5b2dfadd40)] - **doc**: add a note about `require('../common')` in testing documentation (Aditi) [#56953](https://github.com/nodejs/node/pull/56953) +* \[[`50ba04e214`](https://github.com/nodejs/node/commit/50ba04e214)] - **doc**: recommend writing tests in new files and including comments (Joyee Cheung) [#57028](https://github.com/nodejs/node/pull/57028) +* \[[`6951133e1a`](https://github.com/nodejs/node/commit/6951133e1a)] - **doc**: improve documentation on argument validation (Aditi) [#56954](https://github.com/nodejs/node/pull/56954) +* \[[`44dd8a5cc2`](https://github.com/nodejs/node/commit/44dd8a5cc2)] - **doc**: buffer: fix typo on `Buffer.copyBytesFrom(` `offset` option (tpoisseau) [#57015](https://github.com/nodejs/node/pull/57015) +* \[[`c011271a70`](https://github.com/nodejs/node/commit/c011271a70)] - **doc**: update cleanup to trust on vuln db automation (Rafael Gonzaga) [#57004](https://github.com/nodejs/node/pull/57004) +* \[[`a6b7bce3a0`](https://github.com/nodejs/node/commit/a6b7bce3a0)] - **doc**: move stability index after history section for consistency (Antoine du Hamel) [#56997](https://github.com/nodejs/node/pull/56997) +* \[[`3bc6d626b4`](https://github.com/nodejs/node/commit/3bc6d626b4)] - **doc**: add `signal` to `filehandle.writeFile()` options (Yukihiro Hasegawa) [#56804](https://github.com/nodejs/node/pull/56804) +* \[[`2990cc8616`](https://github.com/nodejs/node/commit/2990cc8616)] - **doc**: run license-builder (github-actions\[bot]) [#56985](https://github.com/nodejs/node/pull/56985) +* \[[`40f3a516bf`](https://github.com/nodejs/node/commit/40f3a516bf)] - **fs**: handle UV\_ENOTDIR in `fs.statSync` with `throwIfNoEntry` provided (Juan José Arboleda) [#56996](https://github.com/nodejs/node/pull/56996) +* \[[`e10ef275e8`](https://github.com/nodejs/node/commit/e10ef275e8)] - **inspector**: convert event params to protocol without json (Chengzhong Wu) [#57027](https://github.com/nodejs/node/pull/57027) +* \[[`d6234b4652`](https://github.com/nodejs/node/commit/d6234b4652)] - **inspector**: skip promise hook in the inspector async hook (Joyee Cheung) [#57148](https://github.com/nodejs/node/pull/57148) +* \[[`aa817853cd`](https://github.com/nodejs/node/commit/aa817853cd)] - **lib**: fixup more incorrect ERR\_INVALID\_ARG\_VALUE uses (James M Snell) [#57177](https://github.com/nodejs/node/pull/57177) +* \[[`e08d7d4e53`](https://github.com/nodejs/node/commit/e08d7d4e53)] - **lib**: fixup incorrect argument order in assertEncoding (James M Snell) [#57177](https://github.com/nodejs/node/pull/57177) +* \[[`f77069b4e0`](https://github.com/nodejs/node/commit/f77069b4e0)] - **meta**: bump `actions/setup-python` from 5.3.0 to 5.4.0 (dependabot\[bot]) [#56867](https://github.com/nodejs/node/pull/56867) +* \[[`35cdd9b9fe`](https://github.com/nodejs/node/commit/35cdd9b9fe)] - **meta**: bump `peter-evans/create-pull-request` from 7.0.5 to 7.0.6 (dependabot\[bot]) [#56866](https://github.com/nodejs/node/pull/56866) +* \[[`3d61604f2a`](https://github.com/nodejs/node/commit/3d61604f2a)] - **meta**: bump `mozilla-actions/sccache-action` from 0.0.6 to 0.0.7 (dependabot\[bot]) [#56865](https://github.com/nodejs/node/pull/56865) +* \[[`0dd0108fc5`](https://github.com/nodejs/node/commit/0dd0108fc5)] - **meta**: bump `codecov/codecov-action` from 5.0.7 to 5.3.1 (dependabot\[bot]) [#56864](https://github.com/nodejs/node/pull/56864) +* \[[`58d70369e3`](https://github.com/nodejs/node/commit/58d70369e3)] - **meta**: bump `step-security/harden-runner` from 2.10.2 to 2.10.4 (dependabot\[bot]) [#56863](https://github.com/nodejs/node/pull/56863) +* \[[`dfd42db739`](https://github.com/nodejs/node/commit/dfd42db739)] - **meta**: bump `actions/cache` from 4.1.2 to 4.2.0 (dependabot\[bot]) [#56862](https://github.com/nodejs/node/pull/56862) +* \[[`7f5f02ba2b`](https://github.com/nodejs/node/commit/7f5f02ba2b)] - **meta**: bump `actions/stale` from 9.0.0 to 9.1.0 (dependabot\[bot]) [#56860](https://github.com/nodejs/node/pull/56860) +* \[[`85ac02f8d3`](https://github.com/nodejs/node/commit/85ac02f8d3)] - **meta**: bump `github/codeql-action` from 3.27.5 to 3.28.8 (dependabot\[bot]) [#56859](https://github.com/nodejs/node/pull/56859) +* \[[`d62299b021`](https://github.com/nodejs/node/commit/d62299b021)] - **meta**: add CODEOWNERS for SQLite (Colin Ihrig) [#57147](https://github.com/nodejs/node/pull/57147) +* \[[`2ec4ff17a6`](https://github.com/nodejs/node/commit/2ec4ff17a6)] - **meta**: update last name for jkrems (Jan Martin) [#57006](https://github.com/nodejs/node/pull/57006) +* \[[`ad3c572027`](https://github.com/nodejs/node/commit/ad3c572027)] - **module**: improve error message from asynchronicity in require(esm) (Joyee Cheung) [#57126](https://github.com/nodejs/node/pull/57126) +* \[[`cc1cafd562`](https://github.com/nodejs/node/commit/cc1cafd562)] - **module**: allow omitting context in synchronous next hooks (Joyee Cheung) [#57056](https://github.com/nodejs/node/pull/57056) +* \[[`c6ddfa52fb`](https://github.com/nodejs/node/commit/c6ddfa52fb)] - **(SEMVER-MINOR)** **process**: add threadCpuUsage (Paolo Insogna) [#56467](https://github.com/nodejs/node/pull/56467) +* \[[`ac35106625`](https://github.com/nodejs/node/commit/ac35106625)] - **sea**: suppress builtin warning with disableExperimentalSEAWarning option (koooge) [#57086](https://github.com/nodejs/node/pull/57086) +* \[[`ef314dc773`](https://github.com/nodejs/node/commit/ef314dc773)] - **src**: fix crash when lazy getter is invoked in a vm context (Chengzhong Wu) [#57168](https://github.com/nodejs/node/pull/57168) +* \[[`90a4de02b6`](https://github.com/nodejs/node/commit/90a4de02b6)] - **src**: do not format single string argument for THROW\_ERR\_\* (Joyee Cheung) [#57126](https://github.com/nodejs/node/pull/57126) +* \[[`e0a91f631b`](https://github.com/nodejs/node/commit/e0a91f631b)] - **src**: gate all quic behind disabled-by-default compile flag (James M Snell) [#57142](https://github.com/nodejs/node/pull/57142) +* \[[`7dd326e3a7`](https://github.com/nodejs/node/commit/7dd326e3a7)] - **src**: move instead of copy shared pointer in node\_blob (Michaël Zasso) [#57120](https://github.com/nodejs/node/pull/57120) +* \[[`e3127b89a2`](https://github.com/nodejs/node/commit/e3127b89a2)] - **src**: replace NewFromUtf8 with OneByteString where appropriate (James M Snell) [#57096](https://github.com/nodejs/node/pull/57096) +* \[[`56f9fe7514`](https://github.com/nodejs/node/commit/56f9fe7514)] - **src**: port `defineLazyProperties` to native code (Antoine du Hamel) [#57081](https://github.com/nodejs/node/pull/57081) +* \[[`90875ba0ca`](https://github.com/nodejs/node/commit/90875ba0ca)] - **src**: improve error handling in node\_blob (James M Snell) [#57078](https://github.com/nodejs/node/pull/57078) +* \[[`5414eb48b5`](https://github.com/nodejs/node/commit/5414eb48b5)] - **src**: improve error handling in multiple files (James M Snell) [#56962](https://github.com/nodejs/node/pull/56962) +* \[[`286bb84188`](https://github.com/nodejs/node/commit/286bb84188)] - **src**: fix accessing empty string (Cheng) [#57014](https://github.com/nodejs/node/pull/57014) +* \[[`fa26f83e5b`](https://github.com/nodejs/node/commit/fa26f83e5b)] - **src**: lock the isolate properly in IsolateData destructor (Joyee Cheung) [#57031](https://github.com/nodejs/node/pull/57031) +* \[[`7e2dac9fcc`](https://github.com/nodejs/node/commit/7e2dac9fcc)] - **src**: add self-assigment memcpy checks (Burkov Egor) [#56986](https://github.com/nodejs/node/pull/56986) +* \[[`d8e70dcaa6`](https://github.com/nodejs/node/commit/d8e70dcaa6)] - **src**: improve node::Dotenv trimming (Dario Piotrowicz) [#56983](https://github.com/nodejs/node/pull/56983) +* \[[`41f444fa78`](https://github.com/nodejs/node/commit/41f444fa78)] - **src**: improve error handling in string\_bytes/decoder (James M Snell) [#56978](https://github.com/nodejs/node/pull/56978) +* \[[`d0ee8c0a20`](https://github.com/nodejs/node/commit/d0ee8c0a20)] - **src**: improve error handling in process\_wrap (James M Snell) [#56977](https://github.com/nodejs/node/pull/56977) +* \[[`1a244177a3`](https://github.com/nodejs/node/commit/1a244177a3)] - **test**: add doAppendAndCancel test (Hasegawa-Yukihiro) [#56972](https://github.com/nodejs/node/pull/56972) +* \[[`51dff8b1ae`](https://github.com/nodejs/node/commit/51dff8b1ae)] - **test**: fix test-without-async-context-frame.mjs in debug mode (Joyee Cheung) [#57034](https://github.com/nodejs/node/pull/57034) +* \[[`7c7e9f4d84`](https://github.com/nodejs/node/commit/7c7e9f4d84)] - **test**: make eval snapshot comparison more flexible (Shelley Vohr) [#57020](https://github.com/nodejs/node/pull/57020) +* \[[`315244e59e`](https://github.com/nodejs/node/commit/315244e59e)] - **test**: simplify test-http2-client-promisify-connect-error (Luigi Pinca) [#57144](https://github.com/nodejs/node/pull/57144) +* \[[`ccf496cff9`](https://github.com/nodejs/node/commit/ccf496cff9)] - **test**: improve error output of test-http2-client-promisify-connect-error (Antoine du Hamel) [#57135](https://github.com/nodejs/node/pull/57135) +* \[[`a588066518`](https://github.com/nodejs/node/commit/a588066518)] - **test**: add case for unrecognised fields within pjson "exports" (Jacob Smith) [#57026](https://github.com/nodejs/node/pull/57026) +* \[[`b369ad6e45`](https://github.com/nodejs/node/commit/b369ad6e45)] - **test**: remove unnecessary assert requiring from tests (Dario Piotrowicz) [#57008](https://github.com/nodejs/node/pull/57008) +* \[[`9b98ac6a81`](https://github.com/nodejs/node/commit/9b98ac6a81)] - **test**: update WPT for urlpattern to ef6d83d789 (Node.js GitHub Bot) [#56984](https://github.com/nodejs/node/pull/56984) +* \[[`0a82d27d28`](https://github.com/nodejs/node/commit/0a82d27d28)] - **test**: reduce flakiness on test-net-write-fully-async-buffer (Yagiz Nizipli) [#56971](https://github.com/nodejs/node/pull/56971) +* \[[`ab150d7781`](https://github.com/nodejs/node/commit/ab150d7781)] - **test**: remove flakiness on macOS test (Yagiz Nizipli) [#56971](https://github.com/nodejs/node/pull/56971) +* \[[`ccb8c12712`](https://github.com/nodejs/node/commit/ccb8c12712)] - **test,crypto**: make tests work for BoringSSL (Shelley Vohr) [#57021](https://github.com/nodejs/node/pull/57021) +* \[[`116c1fe84c`](https://github.com/nodejs/node/commit/116c1fe84c)] - **test\_runner**: refactor testPlan counter increse (Pietro Marchini) [#56765](https://github.com/nodejs/node/pull/56765) +* \[[`2929fc6449`](https://github.com/nodejs/node/commit/2929fc6449)] - **test\_runner**: allow special characters in snapshot keys (Carlos Espa) [#57017](https://github.com/nodejs/node/pull/57017) +* \[[`a025d7ba07`](https://github.com/nodejs/node/commit/a025d7ba07)] - **tools**: run Linux tests on GitHub arm64 runners as well (Dennis Ameling) [#57162](https://github.com/nodejs/node/pull/57162) +* \[[`73a8514305`](https://github.com/nodejs/node/commit/73a8514305)] - **tools**: consolidate 'introduced\_in' check for docs (1ilsang) [#57109](https://github.com/nodejs/node/pull/57109) +* \[[`6cdee545f6`](https://github.com/nodejs/node/commit/6cdee545f6)] - **tools**: do not run major-release workflow on forks (Rich Trott) [#57064](https://github.com/nodejs/node/pull/57064) +* \[[`1efd74b1b0`](https://github.com/nodejs/node/commit/1efd74b1b0)] - **tools**: fix release URL computation in update-root-certs.mjs (Joyee Cheung) [#56843](https://github.com/nodejs/node/pull/56843) +* \[[`a9112df8d3`](https://github.com/nodejs/node/commit/a9112df8d3)] - **tools**: add support for `import source` syntax in linter (Antoine du Hamel) [#56992](https://github.com/nodejs/node/pull/56992) +* \[[`c6d6be2c3b`](https://github.com/nodejs/node/commit/c6d6be2c3b)] - **typings**: fix `ImportModuleDynamicallyCallback` return type (Chengzhong Wu) [#57160](https://github.com/nodejs/node/pull/57160) +* \[[`d922153cbf`](https://github.com/nodejs/node/commit/d922153cbf)] - **url**: improve urlpattern regexp performance (Yagiz Nizipli) [#57136](https://github.com/nodejs/node/pull/57136) + ## 2025-02-13, Version 23.8.0 (Current), @targos diff --git a/src/node_version.h b/src/node_version.h index 046abf10181364..2c84e6045ead84 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -23,13 +23,13 @@ #define SRC_NODE_VERSION_H_ #define NODE_MAJOR_VERSION 23 -#define NODE_MINOR_VERSION 8 -#define NODE_PATCH_VERSION 1 +#define NODE_MINOR_VERSION 9 +#define NODE_PATCH_VERSION 0 #define NODE_VERSION_IS_LTS 0 #define NODE_VERSION_LTS_CODENAME "" -#define NODE_VERSION_IS_RELEASE 0 +#define NODE_VERSION_IS_RELEASE 1 #ifndef NODE_STRINGIFY #define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n)