mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
quic: start adding in the internal quic js api
While the external API for QUIC is expected to be the WebTransport API primarily, this provides the internal API for QUIC that aligns with the native C++ QUIC components. PR-URL: https://github.com/nodejs/node/pull/53256 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
This commit is contained in:
parent
103b8439ca
commit
cdae315706
2
.nycrc
2
.nycrc
@ -4,7 +4,7 @@
|
|||||||
"test/**",
|
"test/**",
|
||||||
"tools/**",
|
"tools/**",
|
||||||
"benchmark/**",
|
"benchmark/**",
|
||||||
"deps/**"
|
"deps/**",
|
||||||
],
|
],
|
||||||
"reporter": [
|
"reporter": [
|
||||||
"html",
|
"html",
|
||||||
|
@ -3644,6 +3644,42 @@ removed: v10.0.0
|
|||||||
|
|
||||||
The `node:repl` module was unable to parse data from the REPL history file.
|
The `node:repl` module was unable to parse data from the REPL history file.
|
||||||
|
|
||||||
|
<a id="ERR_QUIC_CONNECTION_FAILED"></a>
|
||||||
|
|
||||||
|
### `ERR_QUIC_CONNECTION_FAILED`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
> Stability: 1 - Experimental
|
||||||
|
|
||||||
|
Establishing a QUIC connection failed.
|
||||||
|
|
||||||
|
<a id="ERR_QUIC_ENDPOINT_CLOSED"></a>
|
||||||
|
|
||||||
|
### `ERR_QUIC_ENDPOINT_CLOSED`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
> Stability: 1 - Experimental
|
||||||
|
|
||||||
|
A QUIC Endpoint closed with an error.
|
||||||
|
|
||||||
|
<a id="ERR_QUIC_OPEN_STREAM_FAILED"></a>
|
||||||
|
|
||||||
|
### `ERR_QUIC_OPEN_STREAM_FAILED`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
> Stability: 1 - Experimental
|
||||||
|
|
||||||
|
Opening a QUIC stream failed.
|
||||||
|
|
||||||
<a id="ERR_SOCKET_CANNOT_SEND"></a>
|
<a id="ERR_SOCKET_CANNOT_SEND"></a>
|
||||||
|
|
||||||
### `ERR_SOCKET_CANNOT_SEND`
|
### `ERR_SOCKET_CANNOT_SEND`
|
||||||
|
@ -1644,6 +1644,9 @@ E('ERR_PARSE_ARGS_UNKNOWN_OPTION', (option, allowPositionals) => {
|
|||||||
E('ERR_PERFORMANCE_INVALID_TIMESTAMP',
|
E('ERR_PERFORMANCE_INVALID_TIMESTAMP',
|
||||||
'%d is not a valid timestamp', TypeError);
|
'%d is not a valid timestamp', TypeError);
|
||||||
E('ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS', '%s', TypeError);
|
E('ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS', '%s', TypeError);
|
||||||
|
E('ERR_QUIC_CONNECTION_FAILED', 'QUIC connection failed', Error);
|
||||||
|
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_REQUIRE_CYCLE_MODULE', '%s', Error);
|
E('ERR_REQUIRE_CYCLE_MODULE', '%s', Error);
|
||||||
E('ERR_REQUIRE_ESM',
|
E('ERR_REQUIRE_ESM',
|
||||||
function(filename, hasEsmSyntax, parentPath = null, packageJsonPath = null) {
|
function(filename, hasEsmSyntax, parentPath = null, packageJsonPath = null) {
|
||||||
|
2548
lib/internal/quic/quic.js
Normal file
2548
lib/internal/quic/quic.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -132,6 +132,9 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const {
|
|||||||
"internal/http2/core", "internal/http2/compat",
|
"internal/http2/core", "internal/http2/compat",
|
||||||
"internal/streams/lazy_transform",
|
"internal/streams/lazy_transform",
|
||||||
#endif // !HAVE_OPENSSL
|
#endif // !HAVE_OPENSSL
|
||||||
|
#if !NODE_OPENSSL_HAS_QUIC
|
||||||
|
"internal/quic/quic",
|
||||||
|
#endif // !NODE_OPENSSL_HAS_QUIC
|
||||||
"sqlite", // Experimental.
|
"sqlite", // Experimental.
|
||||||
"sys", // Deprecated.
|
"sys", // Deprecated.
|
||||||
"wasi", // Experimental.
|
"wasi", // Experimental.
|
||||||
|
@ -233,6 +233,7 @@ bool SetOption(Environment* env,
|
|||||||
Maybe<Endpoint::Options> Endpoint::Options::From(Environment* env,
|
Maybe<Endpoint::Options> Endpoint::Options::From(Environment* env,
|
||||||
Local<Value> value) {
|
Local<Value> value) {
|
||||||
if (value.IsEmpty() || !value->IsObject()) {
|
if (value.IsEmpty() || !value->IsObject()) {
|
||||||
|
if (value->IsUndefined()) return Just(Endpoint::Options());
|
||||||
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
|
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
|
||||||
return Nothing<Options>();
|
return Nothing<Options>();
|
||||||
}
|
}
|
||||||
@ -656,6 +657,25 @@ void Endpoint::InitPerContext(Realm* realm, Local<Object> target) {
|
|||||||
NODE_DEFINE_CONSTANT(target, DEFAULT_REGULARTOKEN_EXPIRATION);
|
NODE_DEFINE_CONSTANT(target, DEFAULT_REGULARTOKEN_EXPIRATION);
|
||||||
NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_PACKET_LENGTH);
|
NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_PACKET_LENGTH);
|
||||||
|
|
||||||
|
static constexpr auto CLOSECONTEXT_CLOSE =
|
||||||
|
static_cast<int>(CloseContext::CLOSE);
|
||||||
|
static constexpr auto CLOSECONTEXT_BIND_FAILURE =
|
||||||
|
static_cast<int>(CloseContext::BIND_FAILURE);
|
||||||
|
static constexpr auto CLOSECONTEXT_LISTEN_FAILURE =
|
||||||
|
static_cast<int>(CloseContext::LISTEN_FAILURE);
|
||||||
|
static constexpr auto CLOSECONTEXT_RECEIVE_FAILURE =
|
||||||
|
static_cast<int>(CloseContext::RECEIVE_FAILURE);
|
||||||
|
static constexpr auto CLOSECONTEXT_SEND_FAILURE =
|
||||||
|
static_cast<int>(CloseContext::SEND_FAILURE);
|
||||||
|
static constexpr auto CLOSECONTEXT_START_FAILURE =
|
||||||
|
static_cast<int>(CloseContext::START_FAILURE);
|
||||||
|
NODE_DEFINE_CONSTANT(target, CLOSECONTEXT_CLOSE);
|
||||||
|
NODE_DEFINE_CONSTANT(target, CLOSECONTEXT_BIND_FAILURE);
|
||||||
|
NODE_DEFINE_CONSTANT(target, CLOSECONTEXT_LISTEN_FAILURE);
|
||||||
|
NODE_DEFINE_CONSTANT(target, CLOSECONTEXT_RECEIVE_FAILURE);
|
||||||
|
NODE_DEFINE_CONSTANT(target, CLOSECONTEXT_SEND_FAILURE);
|
||||||
|
NODE_DEFINE_CONSTANT(target, CLOSECONTEXT_START_FAILURE);
|
||||||
|
|
||||||
SetConstructorFunction(realm->context(),
|
SetConstructorFunction(realm->context(),
|
||||||
target,
|
target,
|
||||||
"Endpoint",
|
"Endpoint",
|
||||||
@ -682,6 +702,7 @@ Endpoint::Endpoint(Environment* env,
|
|||||||
udp_(this),
|
udp_(this),
|
||||||
addrLRU_(options_.address_lru_size) {
|
addrLRU_(options_.address_lru_size) {
|
||||||
MakeWeak();
|
MakeWeak();
|
||||||
|
STAT_RECORD_TIMESTAMP(Stats, created_at);
|
||||||
IF_QUIC_DEBUG(env) {
|
IF_QUIC_DEBUG(env) {
|
||||||
Debug(this, "Endpoint created. Options %s", options.ToString());
|
Debug(this, "Endpoint created. Options %s", options.ToString());
|
||||||
}
|
}
|
||||||
@ -704,6 +725,7 @@ SocketAddress Endpoint::local_address() const {
|
|||||||
|
|
||||||
void Endpoint::MarkAsBusy(bool on) {
|
void Endpoint::MarkAsBusy(bool on) {
|
||||||
Debug(this, "Marking endpoint as %s", on ? "busy" : "not busy");
|
Debug(this, "Marking endpoint as %s", on ? "busy" : "not busy");
|
||||||
|
if (on) STAT_INCREMENT(Stats, server_busy_count);
|
||||||
state_->busy = on ? 1 : 0;
|
state_->busy = on ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1087,6 +1109,7 @@ void Endpoint::Destroy(CloseContext context, int status) {
|
|||||||
state_->bound = 0;
|
state_->bound = 0;
|
||||||
state_->receiving = 0;
|
state_->receiving = 0;
|
||||||
BindingData::Get(env()).listening_endpoints.erase(this);
|
BindingData::Get(env()).listening_endpoints.erase(this);
|
||||||
|
STAT_RECORD_TIMESTAMP(Stats, destroyed_at);
|
||||||
|
|
||||||
EmitClose(close_context_, close_status_);
|
EmitClose(close_context_, close_status_);
|
||||||
}
|
}
|
||||||
|
@ -1682,10 +1682,16 @@ void Session::EmitStream(BaseObjectPtr<Stream> stream) {
|
|||||||
if (is_destroyed()) return;
|
if (is_destroyed()) return;
|
||||||
if (!env()->can_call_into_js()) return;
|
if (!env()->can_call_into_js()) return;
|
||||||
CallbackScope<Session> cb_scope(this);
|
CallbackScope<Session> cb_scope(this);
|
||||||
Local<Value> arg = stream->object();
|
auto isolate = env()->isolate();
|
||||||
|
Local<Value> argv[] = {
|
||||||
|
stream->object(),
|
||||||
|
Integer::NewFromUnsigned(isolate,
|
||||||
|
static_cast<uint32_t>(stream->direction())),
|
||||||
|
};
|
||||||
|
|
||||||
Debug(this, "Notifying JavaScript of stream created");
|
Debug(this, "Notifying JavaScript of stream created");
|
||||||
MakeCallback(BindingData::Get(env()).stream_created_callback(), 1, &arg);
|
MakeCallback(
|
||||||
|
BindingData::Get(env()).stream_created_callback(), arraysize(argv), argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::EmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
|
void Session::EmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
|
||||||
@ -2358,6 +2364,11 @@ void Session::InitPerContext(Realm* realm, Local<Object> target) {
|
|||||||
NODE_DEFINE_CONSTANT(target, QUIC_PROTO_MAX);
|
NODE_DEFINE_CONSTANT(target, QUIC_PROTO_MAX);
|
||||||
NODE_DEFINE_CONSTANT(target, QUIC_PROTO_MIN);
|
NODE_DEFINE_CONSTANT(target, QUIC_PROTO_MIN);
|
||||||
|
|
||||||
|
NODE_DEFINE_STRING_CONSTANT(
|
||||||
|
target, "DEFAULT_CIPHERS", TLSContext::DEFAULT_CIPHERS);
|
||||||
|
NODE_DEFINE_STRING_CONSTANT(
|
||||||
|
target, "DEFAULT_GROUPS", TLSContext::DEFAULT_GROUPS);
|
||||||
|
|
||||||
#define V(name, _) IDX_STATS_SESSION_##name,
|
#define V(name, _) IDX_STATS_SESSION_##name,
|
||||||
enum SessionStatsIdx { SESSION_STATS(V) IDX_STATS_SESSION_COUNT };
|
enum SessionStatsIdx { SESSION_STATS(V) IDX_STATS_SESSION_COUNT };
|
||||||
#undef V
|
#undef V
|
||||||
|
@ -274,7 +274,7 @@ crypto::SSLCtxPointer TLSContext::Initialize() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Side::CLIENT: {
|
case Side::CLIENT: {
|
||||||
ctx_.reset(SSL_CTX_new(TLS_client_method()));
|
ctx.reset(SSL_CTX_new(TLS_client_method()));
|
||||||
CHECK_EQ(ngtcp2_crypto_quictls_configure_client_context(ctx.get()), 0);
|
CHECK_EQ(ngtcp2_crypto_quictls_configure_client_context(ctx.get()), 0);
|
||||||
|
|
||||||
SSL_CTX_set_session_cache_mode(
|
SSL_CTX_set_session_cache_mode(
|
||||||
|
@ -1,76 +1,76 @@
|
|||||||
// Flags: --expose-internals
|
// Flags: --expose-internals --no-warnings
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const common = require('../common');
|
const { hasQuic } = require('../common');
|
||||||
if (!common.hasQuic)
|
|
||||||
common.skip('missing quic');
|
|
||||||
|
|
||||||
const { internalBinding } = require('internal/test/binding');
|
|
||||||
const {
|
|
||||||
ok,
|
|
||||||
strictEqual,
|
|
||||||
deepStrictEqual,
|
|
||||||
} = require('node:assert');
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
SocketAddress: _SocketAddress,
|
describe,
|
||||||
AF_INET,
|
it,
|
||||||
} = internalBinding('block_list');
|
} = require('node:test');
|
||||||
const quic = internalBinding('quic');
|
|
||||||
|
|
||||||
quic.setCallbacks({
|
describe('quic internal endpoint listen defaults', { skip: !hasQuic }, async () => {
|
||||||
onEndpointClose: common.mustCall((...args) => {
|
const {
|
||||||
deepStrictEqual(args, [0, 0]);
|
ok,
|
||||||
}),
|
strictEqual,
|
||||||
|
throws,
|
||||||
|
} = require('node:assert');
|
||||||
|
|
||||||
// The following are unused in this test
|
const {
|
||||||
onSessionNew() {},
|
SocketAddress,
|
||||||
onSessionClose() {},
|
} = require('net');
|
||||||
onSessionDatagram() {},
|
|
||||||
onSessionDatagramStatus() {},
|
const {
|
||||||
onSessionHandshake() {},
|
Endpoint,
|
||||||
onSessionPathValidation() {},
|
} = require('internal/quic/quic');
|
||||||
onSessionTicket() {},
|
|
||||||
onSessionVersionNegotiation() {},
|
it('are reasonable and work as expected', async () => {
|
||||||
onStreamCreated() {},
|
const endpoint = new Endpoint({
|
||||||
onStreamBlocked() {},
|
onsession() {},
|
||||||
onStreamClose() {},
|
session: {},
|
||||||
onStreamReset() {},
|
stream: {},
|
||||||
onStreamHeaders() {},
|
}, {});
|
||||||
onStreamTrailers() {},
|
|
||||||
|
ok(!endpoint.state.isBound);
|
||||||
|
ok(!endpoint.state.isReceiving);
|
||||||
|
ok(!endpoint.state.isListening);
|
||||||
|
|
||||||
|
strictEqual(endpoint.address, undefined);
|
||||||
|
|
||||||
|
throws(() => endpoint.listen(123), {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
});
|
||||||
|
|
||||||
|
endpoint.listen();
|
||||||
|
throws(() => endpoint.listen(), {
|
||||||
|
code: 'ERR_INVALID_STATE',
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(endpoint.state.isBound);
|
||||||
|
ok(endpoint.state.isReceiving);
|
||||||
|
ok(endpoint.state.isListening);
|
||||||
|
|
||||||
|
const address = endpoint.address;
|
||||||
|
ok(address instanceof SocketAddress);
|
||||||
|
|
||||||
|
strictEqual(address.address, '127.0.0.1');
|
||||||
|
strictEqual(address.family, 'ipv4');
|
||||||
|
strictEqual(address.flowlabel, 0);
|
||||||
|
ok(address.port !== 0);
|
||||||
|
|
||||||
|
ok(!endpoint.destroyed);
|
||||||
|
endpoint.destroy();
|
||||||
|
strictEqual(endpoint.closed, endpoint.close());
|
||||||
|
await endpoint.closed;
|
||||||
|
ok(endpoint.destroyed);
|
||||||
|
|
||||||
|
throws(() => endpoint.listen(), {
|
||||||
|
code: 'ERR_INVALID_STATE',
|
||||||
|
});
|
||||||
|
throws(() => { endpoint.busy = true; }, {
|
||||||
|
code: 'ERR_INVALID_STATE',
|
||||||
|
});
|
||||||
|
await endpoint[Symbol.asyncDispose]();
|
||||||
|
|
||||||
|
strictEqual(endpoint.address, undefined);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const endpoint = new quic.Endpoint({});
|
|
||||||
|
|
||||||
const state = new DataView(endpoint.state);
|
|
||||||
ok(!state.getUint8(quic.IDX_STATE_ENDPOINT_LISTENING));
|
|
||||||
ok(!state.getUint8(quic.IDX_STATE_ENDPOINT_RECEIVING));
|
|
||||||
ok(!state.getUint8(quic.IDX_STATE_ENDPOINT_BOUND));
|
|
||||||
strictEqual(endpoint.address(), undefined);
|
|
||||||
|
|
||||||
endpoint.listen({});
|
|
||||||
|
|
||||||
ok(state.getUint8(quic.IDX_STATE_ENDPOINT_LISTENING));
|
|
||||||
ok(state.getUint8(quic.IDX_STATE_ENDPOINT_RECEIVING));
|
|
||||||
ok(state.getUint8(quic.IDX_STATE_ENDPOINT_BOUND));
|
|
||||||
const address = endpoint.address();
|
|
||||||
ok(address instanceof _SocketAddress);
|
|
||||||
|
|
||||||
const detail = address.detail({
|
|
||||||
address: undefined,
|
|
||||||
port: undefined,
|
|
||||||
family: undefined,
|
|
||||||
flowlabel: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
strictEqual(detail.address, '127.0.0.1');
|
|
||||||
strictEqual(detail.family, AF_INET);
|
|
||||||
strictEqual(detail.flowlabel, 0);
|
|
||||||
ok(detail.port !== 0);
|
|
||||||
|
|
||||||
endpoint.closeGracefully();
|
|
||||||
|
|
||||||
ok(!state.getUint8(quic.IDX_STATE_ENDPOINT_LISTENING));
|
|
||||||
ok(!state.getUint8(quic.IDX_STATE_ENDPOINT_RECEIVING));
|
|
||||||
ok(!state.getUint8(quic.IDX_STATE_ENDPOINT_BOUND));
|
|
||||||
strictEqual(endpoint.address(), undefined);
|
|
||||||
|
@ -1,215 +1,230 @@
|
|||||||
// Flags: --expose-internals
|
// Flags: --expose-internals
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const common = require('../common');
|
const { hasQuic } = require('../common');
|
||||||
if (!common.hasQuic)
|
|
||||||
common.skip('missing quic');
|
|
||||||
const {
|
const {
|
||||||
throws,
|
describe,
|
||||||
} = require('node:assert');
|
it,
|
||||||
|
} = require('node:test');
|
||||||
|
|
||||||
const { internalBinding } = require('internal/test/binding');
|
describe('quic internal endpoint options', { skip: !hasQuic }, async () => {
|
||||||
const quic = internalBinding('quic');
|
const {
|
||||||
|
strictEqual,
|
||||||
|
throws,
|
||||||
|
} = require('node:assert');
|
||||||
|
|
||||||
quic.setCallbacks({
|
const {
|
||||||
onEndpointClose() {},
|
Endpoint,
|
||||||
onSessionNew() {},
|
} = require('internal/quic/quic');
|
||||||
onSessionClose() {},
|
|
||||||
onSessionDatagram() {},
|
|
||||||
onSessionDatagramStatus() {},
|
|
||||||
onSessionHandshake() {},
|
|
||||||
onSessionPathValidation() {},
|
|
||||||
onSessionTicket() {},
|
|
||||||
onSessionVersionNegotiation() {},
|
|
||||||
onStreamCreated() {},
|
|
||||||
onStreamBlocked() {},
|
|
||||||
onStreamClose() {},
|
|
||||||
onStreamReset() {},
|
|
||||||
onStreamHeaders() {},
|
|
||||||
onStreamTrailers() {},
|
|
||||||
});
|
|
||||||
|
|
||||||
throws(() => new quic.Endpoint(), {
|
const {
|
||||||
code: 'ERR_INVALID_ARG_TYPE',
|
inspect,
|
||||||
message: 'options must be an object'
|
} = require('util');
|
||||||
});
|
|
||||||
|
|
||||||
throws(() => new quic.Endpoint('a'), {
|
const callbackConfig = {
|
||||||
code: 'ERR_INVALID_ARG_TYPE',
|
onsession() {},
|
||||||
message: 'options must be an object'
|
session: {},
|
||||||
});
|
stream: {},
|
||||||
|
};
|
||||||
|
|
||||||
throws(() => new quic.Endpoint(null), {
|
it('invalid options', async () => {
|
||||||
code: 'ERR_INVALID_ARG_TYPE',
|
['a', null, false, NaN].forEach((i) => {
|
||||||
message: 'options must be an object'
|
throws(() => new Endpoint(callbackConfig, i), {
|
||||||
});
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
});
|
||||||
throws(() => new quic.Endpoint(false), {
|
|
||||||
code: 'ERR_INVALID_ARG_TYPE',
|
|
||||||
message: 'options must be an object'
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
|
||||||
// Just Works... using all defaults
|
|
||||||
new quic.Endpoint({});
|
|
||||||
}
|
|
||||||
|
|
||||||
const cases = [
|
|
||||||
{
|
|
||||||
key: 'retryTokenExpiration',
|
|
||||||
valid: [
|
|
||||||
1, 10, 100, 1000, 10000, 10000n,
|
|
||||||
],
|
|
||||||
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'tokenExpiration',
|
|
||||||
valid: [
|
|
||||||
1, 10, 100, 1000, 10000, 10000n,
|
|
||||||
],
|
|
||||||
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'maxConnectionsPerHost',
|
|
||||||
valid: [
|
|
||||||
1, 10, 100, 1000, 10000, 10000n,
|
|
||||||
],
|
|
||||||
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'maxConnectionsTotal',
|
|
||||||
valid: [
|
|
||||||
1, 10, 100, 1000, 10000, 10000n,
|
|
||||||
],
|
|
||||||
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'maxStatelessResetsPerHost',
|
|
||||||
valid: [
|
|
||||||
1, 10, 100, 1000, 10000, 10000n,
|
|
||||||
],
|
|
||||||
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'addressLRUSize',
|
|
||||||
valid: [
|
|
||||||
1, 10, 100, 1000, 10000, 10000n,
|
|
||||||
],
|
|
||||||
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'maxRetries',
|
|
||||||
valid: [
|
|
||||||
1, 10, 100, 1000, 10000, 10000n,
|
|
||||||
],
|
|
||||||
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'maxPayloadSize',
|
|
||||||
valid: [
|
|
||||||
1, 10, 100, 1000, 10000, 10000n,
|
|
||||||
],
|
|
||||||
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'unacknowledgedPacketThreshold',
|
|
||||||
valid: [
|
|
||||||
1, 10, 100, 1000, 10000, 10000n,
|
|
||||||
],
|
|
||||||
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'validateAddress',
|
|
||||||
valid: [true, false, 0, 1, 'a'],
|
|
||||||
invalid: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'disableStatelessReset',
|
|
||||||
valid: [true, false, 0, 1, 'a'],
|
|
||||||
invalid: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'ipv6Only',
|
|
||||||
valid: [true, false, 0, 1, 'a'],
|
|
||||||
invalid: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'cc',
|
|
||||||
valid: [
|
|
||||||
quic.CC_ALGO_RENO,
|
|
||||||
quic.CC_ALGO_CUBIC,
|
|
||||||
quic.CC_ALGO_BBR,
|
|
||||||
quic.CC_ALGO_BBR2,
|
|
||||||
quic.CC_ALGO_RENO_STR,
|
|
||||||
quic.CC_ALGO_CUBIC_STR,
|
|
||||||
quic.CC_ALGO_BBR_STR,
|
|
||||||
quic.CC_ALGO_BBR2_STR,
|
|
||||||
],
|
|
||||||
invalid: [-1, 4, 1n, 'a', null, false, true, {}, [], () => {}],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'udpReceiveBufferSize',
|
|
||||||
valid: [0, 1, 2, 3, 4, 1000],
|
|
||||||
invalid: [-1, 'a', null, false, true, {}, [], () => {}],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'udpSendBufferSize',
|
|
||||||
valid: [0, 1, 2, 3, 4, 1000],
|
|
||||||
invalid: [-1, 'a', null, false, true, {}, [], () => {}],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'udpTTL',
|
|
||||||
valid: [0, 1, 2, 3, 4, 255],
|
|
||||||
invalid: [-1, 256, 'a', null, false, true, {}, [], () => {}],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'resetTokenSecret',
|
|
||||||
valid: [
|
|
||||||
new Uint8Array(16),
|
|
||||||
new Uint16Array(8),
|
|
||||||
new Uint32Array(4),
|
|
||||||
],
|
|
||||||
invalid: [
|
|
||||||
'a', null, false, true, {}, [], () => {},
|
|
||||||
new Uint8Array(15),
|
|
||||||
new Uint8Array(17),
|
|
||||||
new ArrayBuffer(16),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'tokenSecret',
|
|
||||||
valid: [
|
|
||||||
new Uint8Array(16),
|
|
||||||
new Uint16Array(8),
|
|
||||||
new Uint32Array(4),
|
|
||||||
],
|
|
||||||
invalid: [
|
|
||||||
'a', null, false, true, {}, [], () => {},
|
|
||||||
new Uint8Array(15),
|
|
||||||
new Uint8Array(17),
|
|
||||||
new ArrayBuffer(16),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Unknown options are ignored entirely for any value type
|
|
||||||
key: 'ignored',
|
|
||||||
valid: ['a', null, false, true, {}, [], () => {}],
|
|
||||||
invalid: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const { key, valid, invalid } of cases) {
|
|
||||||
for (const value of valid) {
|
|
||||||
const options = {};
|
|
||||||
options[key] = value;
|
|
||||||
new quic.Endpoint(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const value of invalid) {
|
|
||||||
const options = {};
|
|
||||||
options[key] = value;
|
|
||||||
throws(() => new quic.Endpoint(options), {
|
|
||||||
code: 'ERR_INVALID_ARG_VALUE',
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
it('valid options', async () => {
|
||||||
|
// Just Works... using all defaults
|
||||||
|
new Endpoint(callbackConfig, {});
|
||||||
|
new Endpoint(callbackConfig);
|
||||||
|
new Endpoint(callbackConfig, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('various cases', async () => {
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
key: 'retryTokenExpiration',
|
||||||
|
valid: [
|
||||||
|
1, 10, 100, 1000, 10000, 10000n,
|
||||||
|
],
|
||||||
|
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tokenExpiration',
|
||||||
|
valid: [
|
||||||
|
1, 10, 100, 1000, 10000, 10000n,
|
||||||
|
],
|
||||||
|
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'maxConnectionsPerHost',
|
||||||
|
valid: [
|
||||||
|
1, 10, 100, 1000, 10000, 10000n,
|
||||||
|
],
|
||||||
|
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'maxConnectionsTotal',
|
||||||
|
valid: [
|
||||||
|
1, 10, 100, 1000, 10000, 10000n,
|
||||||
|
],
|
||||||
|
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'maxStatelessResetsPerHost',
|
||||||
|
valid: [
|
||||||
|
1, 10, 100, 1000, 10000, 10000n,
|
||||||
|
],
|
||||||
|
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'addressLRUSize',
|
||||||
|
valid: [
|
||||||
|
1, 10, 100, 1000, 10000, 10000n,
|
||||||
|
],
|
||||||
|
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'maxRetries',
|
||||||
|
valid: [
|
||||||
|
1, 10, 100, 1000, 10000, 10000n,
|
||||||
|
],
|
||||||
|
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'maxPayloadSize',
|
||||||
|
valid: [
|
||||||
|
1, 10, 100, 1000, 10000, 10000n,
|
||||||
|
],
|
||||||
|
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'unacknowledgedPacketThreshold',
|
||||||
|
valid: [
|
||||||
|
1, 10, 100, 1000, 10000, 10000n,
|
||||||
|
],
|
||||||
|
invalid: [-1, -1n, 'a', null, false, true, {}, [], () => {}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'validateAddress',
|
||||||
|
valid: [true, false, 0, 1, 'a'],
|
||||||
|
invalid: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'disableStatelessReset',
|
||||||
|
valid: [true, false, 0, 1, 'a'],
|
||||||
|
invalid: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ipv6Only',
|
||||||
|
valid: [true, false, 0, 1, 'a'],
|
||||||
|
invalid: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'cc',
|
||||||
|
valid: [
|
||||||
|
Endpoint.CC_ALGO_RENO,
|
||||||
|
Endpoint.CC_ALGO_CUBIC,
|
||||||
|
Endpoint.CC_ALGO_BBR,
|
||||||
|
Endpoint.CC_ALGO_RENO_STR,
|
||||||
|
Endpoint.CC_ALGO_CUBIC_STR,
|
||||||
|
Endpoint.CC_ALGO_BBR_STR,
|
||||||
|
],
|
||||||
|
invalid: [-1, 4, 1n, 'a', null, false, true, {}, [], () => {}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'udpReceiveBufferSize',
|
||||||
|
valid: [0, 1, 2, 3, 4, 1000],
|
||||||
|
invalid: [-1, 'a', null, false, true, {}, [], () => {}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'udpSendBufferSize',
|
||||||
|
valid: [0, 1, 2, 3, 4, 1000],
|
||||||
|
invalid: [-1, 'a', null, false, true, {}, [], () => {}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'udpTTL',
|
||||||
|
valid: [0, 1, 2, 3, 4, 255],
|
||||||
|
invalid: [-1, 256, 'a', null, false, true, {}, [], () => {}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'resetTokenSecret',
|
||||||
|
valid: [
|
||||||
|
new Uint8Array(16),
|
||||||
|
new Uint16Array(8),
|
||||||
|
new Uint32Array(4),
|
||||||
|
],
|
||||||
|
invalid: [
|
||||||
|
'a', null, false, true, {}, [], () => {},
|
||||||
|
new Uint8Array(15),
|
||||||
|
new Uint8Array(17),
|
||||||
|
new ArrayBuffer(16),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tokenSecret',
|
||||||
|
valid: [
|
||||||
|
new Uint8Array(16),
|
||||||
|
new Uint16Array(8),
|
||||||
|
new Uint32Array(4),
|
||||||
|
],
|
||||||
|
invalid: [
|
||||||
|
'a', null, false, true, {}, [], () => {},
|
||||||
|
new Uint8Array(15),
|
||||||
|
new Uint8Array(17),
|
||||||
|
new ArrayBuffer(16),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Unknown options are ignored entirely for any value type
|
||||||
|
key: 'ignored',
|
||||||
|
valid: ['a', null, false, true, {}, [], () => {}],
|
||||||
|
invalid: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { key, valid, invalid } of cases) {
|
||||||
|
for (const value of valid) {
|
||||||
|
const options = {};
|
||||||
|
options[key] = value;
|
||||||
|
new Endpoint(callbackConfig, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const value of invalid) {
|
||||||
|
const options = {};
|
||||||
|
options[key] = value;
|
||||||
|
throws(() => new Endpoint(callbackConfig, options), {
|
||||||
|
code: 'ERR_INVALID_ARG_VALUE',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('endpoint can be ref/unrefed without error', async () => {
|
||||||
|
const endpoint = new Endpoint(callbackConfig, {});
|
||||||
|
endpoint.unref();
|
||||||
|
endpoint.ref();
|
||||||
|
endpoint.close();
|
||||||
|
await endpoint.closed;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('endpoint can be inspected', async () => {
|
||||||
|
const endpoint = new Endpoint(callbackConfig, {});
|
||||||
|
strictEqual(typeof inspect(endpoint), 'string');
|
||||||
|
endpoint.close();
|
||||||
|
await endpoint.closed;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('endpoint with object address', () => {
|
||||||
|
new Endpoint(callbackConfig, {
|
||||||
|
address: { host: '127.0.0.1:0' },
|
||||||
|
});
|
||||||
|
throws(() => new Endpoint(callbackConfig, { address: '127.0.0.1:0' }), {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
@ -1,79 +1,245 @@
|
|||||||
// Flags: --expose-internals
|
// Flags: --expose-internals
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const common = require('../common');
|
const { hasQuic } = require('../common');
|
||||||
if (!common.hasQuic)
|
|
||||||
common.skip('missing quic');
|
|
||||||
const {
|
|
||||||
strictEqual,
|
|
||||||
} = require('node:assert');
|
|
||||||
|
|
||||||
const { internalBinding } = require('internal/test/binding');
|
|
||||||
const quic = internalBinding('quic');
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
IDX_STATS_ENDPOINT_CREATED_AT,
|
describe,
|
||||||
IDX_STATS_ENDPOINT_DESTROYED_AT,
|
it,
|
||||||
IDX_STATS_ENDPOINT_BYTES_RECEIVED,
|
} = require('node:test');
|
||||||
IDX_STATS_ENDPOINT_BYTES_SENT,
|
|
||||||
IDX_STATS_ENDPOINT_PACKETS_RECEIVED,
|
|
||||||
IDX_STATS_ENDPOINT_PACKETS_SENT,
|
|
||||||
IDX_STATS_ENDPOINT_SERVER_SESSIONS,
|
|
||||||
IDX_STATS_ENDPOINT_CLIENT_SESSIONS,
|
|
||||||
IDX_STATS_ENDPOINT_SERVER_BUSY_COUNT,
|
|
||||||
IDX_STATS_ENDPOINT_RETRY_COUNT,
|
|
||||||
IDX_STATS_ENDPOINT_VERSION_NEGOTIATION_COUNT,
|
|
||||||
IDX_STATS_ENDPOINT_STATELESS_RESET_COUNT,
|
|
||||||
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT,
|
|
||||||
IDX_STATS_ENDPOINT_COUNT,
|
|
||||||
IDX_STATE_ENDPOINT_BOUND,
|
|
||||||
IDX_STATE_ENDPOINT_BOUND_SIZE,
|
|
||||||
IDX_STATE_ENDPOINT_RECEIVING,
|
|
||||||
IDX_STATE_ENDPOINT_RECEIVING_SIZE,
|
|
||||||
IDX_STATE_ENDPOINT_LISTENING,
|
|
||||||
IDX_STATE_ENDPOINT_LISTENING_SIZE,
|
|
||||||
IDX_STATE_ENDPOINT_CLOSING,
|
|
||||||
IDX_STATE_ENDPOINT_CLOSING_SIZE,
|
|
||||||
IDX_STATE_ENDPOINT_BUSY,
|
|
||||||
IDX_STATE_ENDPOINT_BUSY_SIZE,
|
|
||||||
IDX_STATE_ENDPOINT_PENDING_CALLBACKS,
|
|
||||||
IDX_STATE_ENDPOINT_PENDING_CALLBACKS_SIZE,
|
|
||||||
} = quic;
|
|
||||||
|
|
||||||
const endpoint = new quic.Endpoint({});
|
describe('quic internal endpoint stats and state', { skip: !hasQuic }, () => {
|
||||||
|
const {
|
||||||
|
Endpoint,
|
||||||
|
QuicStreamState,
|
||||||
|
QuicStreamStats,
|
||||||
|
SessionState,
|
||||||
|
SessionStats,
|
||||||
|
kFinishClose,
|
||||||
|
} = require('internal/quic/quic');
|
||||||
|
|
||||||
const state = new DataView(endpoint.state);
|
const {
|
||||||
strictEqual(IDX_STATE_ENDPOINT_BOUND_SIZE, 1);
|
inspect,
|
||||||
strictEqual(IDX_STATE_ENDPOINT_RECEIVING_SIZE, 1);
|
} = require('util');
|
||||||
strictEqual(IDX_STATE_ENDPOINT_LISTENING_SIZE, 1);
|
|
||||||
strictEqual(IDX_STATE_ENDPOINT_CLOSING_SIZE, 1);
|
|
||||||
strictEqual(IDX_STATE_ENDPOINT_BUSY_SIZE, 1);
|
|
||||||
strictEqual(IDX_STATE_ENDPOINT_PENDING_CALLBACKS_SIZE, 8);
|
|
||||||
|
|
||||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_BOUND), 0);
|
const {
|
||||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_RECEIVING), 0);
|
deepStrictEqual,
|
||||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_LISTENING), 0);
|
strictEqual,
|
||||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_CLOSING), 0);
|
throws,
|
||||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_BUSY), 0);
|
} = require('node:assert');
|
||||||
strictEqual(state.getBigUint64(IDX_STATE_ENDPOINT_PENDING_CALLBACKS), 0n);
|
|
||||||
|
|
||||||
endpoint.markBusy(true);
|
it('endpoint state', () => {
|
||||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_BUSY), 1);
|
const endpoint = new Endpoint({
|
||||||
endpoint.markBusy(false);
|
onsession() {},
|
||||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_BUSY), 0);
|
session: {},
|
||||||
|
stream: {},
|
||||||
|
});
|
||||||
|
|
||||||
const stats = new BigUint64Array(endpoint.stats);
|
strictEqual(endpoint.state.isBound, false);
|
||||||
strictEqual(stats[IDX_STATS_ENDPOINT_CREATED_AT], 0n);
|
strictEqual(endpoint.state.isReceiving, false);
|
||||||
strictEqual(stats[IDX_STATS_ENDPOINT_DESTROYED_AT], 0n);
|
strictEqual(endpoint.state.isListening, false);
|
||||||
strictEqual(stats[IDX_STATS_ENDPOINT_BYTES_RECEIVED], 0n);
|
strictEqual(endpoint.state.isClosing, false);
|
||||||
strictEqual(stats[IDX_STATS_ENDPOINT_BYTES_SENT], 0n);
|
strictEqual(endpoint.state.isBusy, false);
|
||||||
strictEqual(stats[IDX_STATS_ENDPOINT_PACKETS_RECEIVED], 0n);
|
strictEqual(endpoint.state.pendingCallbacks, 0n);
|
||||||
strictEqual(stats[IDX_STATS_ENDPOINT_PACKETS_SENT], 0n);
|
|
||||||
strictEqual(stats[IDX_STATS_ENDPOINT_SERVER_SESSIONS], 0n);
|
deepStrictEqual(JSON.parse(JSON.stringify(endpoint.state)), {
|
||||||
strictEqual(stats[IDX_STATS_ENDPOINT_CLIENT_SESSIONS], 0n);
|
isBound: false,
|
||||||
strictEqual(stats[IDX_STATS_ENDPOINT_SERVER_BUSY_COUNT], 0n);
|
isReceiving: false,
|
||||||
strictEqual(stats[IDX_STATS_ENDPOINT_RETRY_COUNT], 0n);
|
isListening: false,
|
||||||
strictEqual(stats[IDX_STATS_ENDPOINT_VERSION_NEGOTIATION_COUNT], 0n);
|
isClosing: false,
|
||||||
strictEqual(stats[IDX_STATS_ENDPOINT_STATELESS_RESET_COUNT], 0n);
|
isBusy: false,
|
||||||
strictEqual(stats[IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT], 0n);
|
pendingCallbacks: '0',
|
||||||
strictEqual(IDX_STATS_ENDPOINT_COUNT, 13);
|
});
|
||||||
|
|
||||||
|
endpoint.busy = true;
|
||||||
|
strictEqual(endpoint.state.isBusy, true);
|
||||||
|
endpoint.busy = false;
|
||||||
|
strictEqual(endpoint.state.isBusy, false);
|
||||||
|
|
||||||
|
it('state can be inspected without errors', () => {
|
||||||
|
strictEqual(typeof inspect(endpoint.state), 'string');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('state is not readable after close', () => {
|
||||||
|
const endpoint = new Endpoint({
|
||||||
|
onsession() {},
|
||||||
|
session: {},
|
||||||
|
stream: {},
|
||||||
|
}, {});
|
||||||
|
endpoint.state[kFinishClose]();
|
||||||
|
throws(() => endpoint.state.isBound, {
|
||||||
|
name: 'Error',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('state constructor argument is ArrayBuffer', () => {
|
||||||
|
const endpoint = new Endpoint({
|
||||||
|
onsession() {},
|
||||||
|
session: {},
|
||||||
|
stream: {},
|
||||||
|
}, {});
|
||||||
|
const Cons = endpoint.state.constructor;
|
||||||
|
throws(() => new Cons(1), {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('endpoint stats', () => {
|
||||||
|
const endpoint = new Endpoint({
|
||||||
|
onsession() {},
|
||||||
|
session: {},
|
||||||
|
stream: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
strictEqual(typeof endpoint.stats.createdAt, 'bigint');
|
||||||
|
strictEqual(typeof endpoint.stats.destroyedAt, 'bigint');
|
||||||
|
strictEqual(typeof endpoint.stats.bytesReceived, 'bigint');
|
||||||
|
strictEqual(typeof endpoint.stats.bytesSent, 'bigint');
|
||||||
|
strictEqual(typeof endpoint.stats.packetsReceived, 'bigint');
|
||||||
|
strictEqual(typeof endpoint.stats.packetsSent, 'bigint');
|
||||||
|
strictEqual(typeof endpoint.stats.serverSessions, 'bigint');
|
||||||
|
strictEqual(typeof endpoint.stats.clientSessions, 'bigint');
|
||||||
|
strictEqual(typeof endpoint.stats.serverBusyCount, 'bigint');
|
||||||
|
strictEqual(typeof endpoint.stats.retryCount, 'bigint');
|
||||||
|
strictEqual(typeof endpoint.stats.versionNegotiationCount, 'bigint');
|
||||||
|
strictEqual(typeof endpoint.stats.statelessResetCount, 'bigint');
|
||||||
|
strictEqual(typeof endpoint.stats.immediateCloseCount, 'bigint');
|
||||||
|
|
||||||
|
deepStrictEqual(Object.keys(endpoint.stats.toJSON()), [
|
||||||
|
'createdAt',
|
||||||
|
'destroyedAt',
|
||||||
|
'bytesReceived',
|
||||||
|
'bytesSent',
|
||||||
|
'packetsReceived',
|
||||||
|
'packetsSent',
|
||||||
|
'serverSessions',
|
||||||
|
'clientSessions',
|
||||||
|
'serverBusyCount',
|
||||||
|
'retryCount',
|
||||||
|
'versionNegotiationCount',
|
||||||
|
'statelessResetCount',
|
||||||
|
'immediateCloseCount',
|
||||||
|
]);
|
||||||
|
|
||||||
|
it('stats can be inspected without errors', () => {
|
||||||
|
strictEqual(typeof inspect(endpoint.stats), 'string');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stats are still readble after close', () => {
|
||||||
|
const endpoint = new Endpoint({
|
||||||
|
onsession() {},
|
||||||
|
session: {},
|
||||||
|
stream: {},
|
||||||
|
}, {});
|
||||||
|
strictEqual(typeof endpoint.stats.toJSON(), 'object');
|
||||||
|
endpoint.stats[kFinishClose]();
|
||||||
|
strictEqual(typeof endpoint.stats.destroyedAt, 'bigint');
|
||||||
|
strictEqual(typeof endpoint.stats.toJSON(), 'object');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stats constructor argument is ArrayBuffer', () => {
|
||||||
|
const endpoint = new Endpoint({
|
||||||
|
onsession() {},
|
||||||
|
session: {},
|
||||||
|
stream: {},
|
||||||
|
}, {});
|
||||||
|
const Cons = endpoint.stats.constructor;
|
||||||
|
throws(() => new Cons(1), {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO(@jasnell): The following tests are largely incomplete.
|
||||||
|
// This is largely here to boost the code coverage numbers
|
||||||
|
// temporarily while the rest of the functionality is being
|
||||||
|
// implemented.
|
||||||
|
it('stream and session states', () => {
|
||||||
|
const streamState = new QuicStreamState(new ArrayBuffer(1024));
|
||||||
|
const sessionState = new SessionState(new ArrayBuffer(1024));
|
||||||
|
|
||||||
|
strictEqual(streamState.finSent, false);
|
||||||
|
strictEqual(streamState.finReceived, false);
|
||||||
|
strictEqual(streamState.readEnded, false);
|
||||||
|
strictEqual(streamState.writeEnded, false);
|
||||||
|
strictEqual(streamState.destroyed, false);
|
||||||
|
strictEqual(streamState.paused, false);
|
||||||
|
strictEqual(streamState.reset, false);
|
||||||
|
strictEqual(streamState.hasReader, false);
|
||||||
|
strictEqual(streamState.wantsBlock, false);
|
||||||
|
strictEqual(streamState.wantsHeaders, false);
|
||||||
|
strictEqual(streamState.wantsReset, false);
|
||||||
|
strictEqual(streamState.wantsTrailers, false);
|
||||||
|
|
||||||
|
strictEqual(sessionState.hasPathValidationListener, false);
|
||||||
|
strictEqual(sessionState.hasVersionNegotiationListener, false);
|
||||||
|
strictEqual(sessionState.hasDatagramListener, false);
|
||||||
|
strictEqual(sessionState.hasSessionTicketListener, false);
|
||||||
|
strictEqual(sessionState.isClosing, false);
|
||||||
|
strictEqual(sessionState.isGracefulClose, false);
|
||||||
|
strictEqual(sessionState.isSilentClose, false);
|
||||||
|
strictEqual(sessionState.isStatelessReset, false);
|
||||||
|
strictEqual(sessionState.isDestroyed, false);
|
||||||
|
strictEqual(sessionState.isHandshakeCompleted, false);
|
||||||
|
strictEqual(sessionState.isHandshakeConfirmed, false);
|
||||||
|
strictEqual(sessionState.isStreamOpenAllowed, false);
|
||||||
|
strictEqual(sessionState.isPrioritySupported, false);
|
||||||
|
strictEqual(sessionState.isWrapped, false);
|
||||||
|
strictEqual(sessionState.lastDatagramId, 0n);
|
||||||
|
|
||||||
|
strictEqual(typeof streamState.toJSON(), 'object');
|
||||||
|
strictEqual(typeof sessionState.toJSON(), 'object');
|
||||||
|
strictEqual(typeof inspect(streamState), 'string');
|
||||||
|
strictEqual(typeof inspect(sessionState), 'string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stream and session stats', () => {
|
||||||
|
const streamStats = new QuicStreamStats(new ArrayBuffer(1024));
|
||||||
|
const sessionStats = new SessionStats(new ArrayBuffer(1024));
|
||||||
|
strictEqual(streamStats.createdAt, undefined);
|
||||||
|
strictEqual(streamStats.receivedAt, undefined);
|
||||||
|
strictEqual(streamStats.ackedAt, undefined);
|
||||||
|
strictEqual(streamStats.closingAt, undefined);
|
||||||
|
strictEqual(streamStats.destroyedAt, undefined);
|
||||||
|
strictEqual(streamStats.bytesReceived, undefined);
|
||||||
|
strictEqual(streamStats.bytesSent, undefined);
|
||||||
|
strictEqual(streamStats.maxOffset, undefined);
|
||||||
|
strictEqual(streamStats.maxOffsetAcknowledged, undefined);
|
||||||
|
strictEqual(streamStats.maxOffsetReceived, undefined);
|
||||||
|
strictEqual(streamStats.finalSize, undefined);
|
||||||
|
strictEqual(typeof streamStats.toJSON(), 'object');
|
||||||
|
strictEqual(typeof inspect(streamStats), 'string');
|
||||||
|
streamStats[kFinishClose]();
|
||||||
|
|
||||||
|
strictEqual(typeof sessionStats.createdAt, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.closingAt, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.destroyedAt, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.handshakeCompletedAt, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.handshakeConfirmedAt, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.gracefulClosingAt, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.bytesReceived, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.bytesSent, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.bidiInStreamCount, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.bidiOutStreamCount, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.uniInStreamCount, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.uniOutStreamCount, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.lossRetransmitCount, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.maxBytesInFlights, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.bytesInFlight, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.blockCount, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.cwnd, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.latestRtt, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.minRtt, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.rttVar, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.smoothedRtt, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.ssthresh, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.datagramsReceived, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.datagramsSent, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.datagramsAcknowledged, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.datagramsLost, 'bigint');
|
||||||
|
strictEqual(typeof sessionStats.toJSON(), 'object');
|
||||||
|
strictEqual(typeof inspect(sessionStats), 'string');
|
||||||
|
streamStats[kFinishClose]();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -1,38 +1,47 @@
|
|||||||
// Flags: --expose-internals
|
// Flags: --expose-internals --no-warnings
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const common = require('../common');
|
const { hasQuic } = require('../common');
|
||||||
if (!common.hasQuic)
|
|
||||||
common.skip('missing quic');
|
|
||||||
const { internalBinding } = require('internal/test/binding');
|
|
||||||
const quic = internalBinding('quic');
|
|
||||||
|
|
||||||
const { throws } = require('assert');
|
const {
|
||||||
|
describe,
|
||||||
|
it,
|
||||||
|
} = require('node:test');
|
||||||
|
|
||||||
const callbacks = {
|
describe('quic internal setCallbacks', { skip: !hasQuic }, () => {
|
||||||
onEndpointClose() {},
|
const { internalBinding } = require('internal/test/binding');
|
||||||
onSessionNew() {},
|
const quic = internalBinding('quic');
|
||||||
onSessionClose() {},
|
|
||||||
onSessionDatagram() {},
|
it('require all callbacks to be set', (t) => {
|
||||||
onSessionDatagramStatus() {},
|
const callbacks = {
|
||||||
onSessionHandshake() {},
|
onEndpointClose() {},
|
||||||
onSessionPathValidation() {},
|
onSessionNew() {},
|
||||||
onSessionTicket() {},
|
onSessionClose() {},
|
||||||
onSessionVersionNegotiation() {},
|
onSessionDatagram() {},
|
||||||
onStreamCreated() {},
|
onSessionDatagramStatus() {},
|
||||||
onStreamBlocked() {},
|
onSessionHandshake() {},
|
||||||
onStreamClose() {},
|
onSessionPathValidation() {},
|
||||||
onStreamReset() {},
|
onSessionTicket() {},
|
||||||
onStreamHeaders() {},
|
onSessionVersionNegotiation() {},
|
||||||
onStreamTrailers() {},
|
onStreamCreated() {},
|
||||||
};
|
onStreamBlocked() {},
|
||||||
// Fail if any callback is missing
|
onStreamClose() {},
|
||||||
for (const fn of Object.keys(callbacks)) {
|
onStreamReset() {},
|
||||||
// eslint-disable-next-line no-unused-vars
|
onStreamHeaders() {},
|
||||||
const { [fn]: _, ...rest } = callbacks;
|
onStreamTrailers() {},
|
||||||
throws(() => quic.setCallbacks(rest), {
|
};
|
||||||
code: 'ERR_MISSING_ARGS',
|
// Fail if any callback is missing
|
||||||
|
for (const fn of Object.keys(callbacks)) {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const { [fn]: _, ...rest } = callbacks;
|
||||||
|
t.assert.throws(() => quic.setCallbacks(rest), {
|
||||||
|
code: 'ERR_MISSING_ARGS',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// If all callbacks are present it should work
|
||||||
|
quic.setCallbacks(callbacks);
|
||||||
|
|
||||||
|
// Multiple calls should just be ignored.
|
||||||
|
quic.setCallbacks(callbacks);
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
// If all callbacks are present it should work
|
|
||||||
quic.setCallbacks(callbacks);
|
|
||||||
|
Loading…
Reference in New Issue
Block a user