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/**",
|
||||
"tools/**",
|
||||
"benchmark/**",
|
||||
"deps/**"
|
||||
"deps/**",
|
||||
],
|
||||
"reporter": [
|
||||
"html",
|
||||
|
@ -3644,6 +3644,42 @@ removed: v10.0.0
|
||||
|
||||
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>
|
||||
|
||||
### `ERR_SOCKET_CANNOT_SEND`
|
||||
|
@ -1644,6 +1644,9 @@ E('ERR_PARSE_ARGS_UNKNOWN_OPTION', (option, allowPositionals) => {
|
||||
E('ERR_PERFORMANCE_INVALID_TIMESTAMP',
|
||||
'%d is not a valid timestamp', 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_ESM',
|
||||
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/streams/lazy_transform",
|
||||
#endif // !HAVE_OPENSSL
|
||||
#if !NODE_OPENSSL_HAS_QUIC
|
||||
"internal/quic/quic",
|
||||
#endif // !NODE_OPENSSL_HAS_QUIC
|
||||
"sqlite", // Experimental.
|
||||
"sys", // Deprecated.
|
||||
"wasi", // Experimental.
|
||||
|
@ -233,6 +233,7 @@ bool SetOption(Environment* env,
|
||||
Maybe<Endpoint::Options> Endpoint::Options::From(Environment* env,
|
||||
Local<Value> value) {
|
||||
if (value.IsEmpty() || !value->IsObject()) {
|
||||
if (value->IsUndefined()) return Just(Endpoint::Options());
|
||||
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
|
||||
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_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(),
|
||||
target,
|
||||
"Endpoint",
|
||||
@ -682,6 +702,7 @@ Endpoint::Endpoint(Environment* env,
|
||||
udp_(this),
|
||||
addrLRU_(options_.address_lru_size) {
|
||||
MakeWeak();
|
||||
STAT_RECORD_TIMESTAMP(Stats, created_at);
|
||||
IF_QUIC_DEBUG(env) {
|
||||
Debug(this, "Endpoint created. Options %s", options.ToString());
|
||||
}
|
||||
@ -704,6 +725,7 @@ SocketAddress Endpoint::local_address() const {
|
||||
|
||||
void Endpoint::MarkAsBusy(bool on) {
|
||||
Debug(this, "Marking endpoint as %s", on ? "busy" : "not busy");
|
||||
if (on) STAT_INCREMENT(Stats, server_busy_count);
|
||||
state_->busy = on ? 1 : 0;
|
||||
}
|
||||
|
||||
@ -1087,6 +1109,7 @@ void Endpoint::Destroy(CloseContext context, int status) {
|
||||
state_->bound = 0;
|
||||
state_->receiving = 0;
|
||||
BindingData::Get(env()).listening_endpoints.erase(this);
|
||||
STAT_RECORD_TIMESTAMP(Stats, destroyed_at);
|
||||
|
||||
EmitClose(close_context_, close_status_);
|
||||
}
|
||||
|
@ -1682,10 +1682,16 @@ void Session::EmitStream(BaseObjectPtr<Stream> stream) {
|
||||
if (is_destroyed()) return;
|
||||
if (!env()->can_call_into_js()) return;
|
||||
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");
|
||||
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,
|
||||
@ -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_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,
|
||||
enum SessionStatsIdx { SESSION_STATS(V) IDX_STATS_SESSION_COUNT };
|
||||
#undef V
|
||||
|
@ -274,7 +274,7 @@ crypto::SSLCtxPointer TLSContext::Initialize() {
|
||||
break;
|
||||
}
|
||||
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);
|
||||
|
||||
SSL_CTX_set_session_cache_mode(
|
||||
|
@ -1,76 +1,76 @@
|
||||
// Flags: --expose-internals
|
||||
// Flags: --expose-internals --no-warnings
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasQuic)
|
||||
common.skip('missing quic');
|
||||
|
||||
const { internalBinding } = require('internal/test/binding');
|
||||
const {
|
||||
ok,
|
||||
strictEqual,
|
||||
deepStrictEqual,
|
||||
} = require('node:assert');
|
||||
const { hasQuic } = require('../common');
|
||||
|
||||
const {
|
||||
SocketAddress: _SocketAddress,
|
||||
AF_INET,
|
||||
} = internalBinding('block_list');
|
||||
const quic = internalBinding('quic');
|
||||
describe,
|
||||
it,
|
||||
} = require('node:test');
|
||||
|
||||
quic.setCallbacks({
|
||||
onEndpointClose: common.mustCall((...args) => {
|
||||
deepStrictEqual(args, [0, 0]);
|
||||
}),
|
||||
describe('quic internal endpoint listen defaults', { skip: !hasQuic }, async () => {
|
||||
const {
|
||||
ok,
|
||||
strictEqual,
|
||||
throws,
|
||||
} = require('node:assert');
|
||||
|
||||
// The following are unused in this test
|
||||
onSessionNew() {},
|
||||
onSessionClose() {},
|
||||
onSessionDatagram() {},
|
||||
onSessionDatagramStatus() {},
|
||||
onSessionHandshake() {},
|
||||
onSessionPathValidation() {},
|
||||
onSessionTicket() {},
|
||||
onSessionVersionNegotiation() {},
|
||||
onStreamCreated() {},
|
||||
onStreamBlocked() {},
|
||||
onStreamClose() {},
|
||||
onStreamReset() {},
|
||||
onStreamHeaders() {},
|
||||
onStreamTrailers() {},
|
||||
const {
|
||||
SocketAddress,
|
||||
} = require('net');
|
||||
|
||||
const {
|
||||
Endpoint,
|
||||
} = require('internal/quic/quic');
|
||||
|
||||
it('are reasonable and work as expected', async () => {
|
||||
const endpoint = new Endpoint({
|
||||
onsession() {},
|
||||
session: {},
|
||||
stream: {},
|
||||
}, {});
|
||||
|
||||
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
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasQuic)
|
||||
common.skip('missing quic');
|
||||
const { hasQuic } = require('../common');
|
||||
|
||||
const {
|
||||
throws,
|
||||
} = require('node:assert');
|
||||
describe,
|
||||
it,
|
||||
} = require('node:test');
|
||||
|
||||
const { internalBinding } = require('internal/test/binding');
|
||||
const quic = internalBinding('quic');
|
||||
describe('quic internal endpoint options', { skip: !hasQuic }, async () => {
|
||||
const {
|
||||
strictEqual,
|
||||
throws,
|
||||
} = require('node:assert');
|
||||
|
||||
quic.setCallbacks({
|
||||
onEndpointClose() {},
|
||||
onSessionNew() {},
|
||||
onSessionClose() {},
|
||||
onSessionDatagram() {},
|
||||
onSessionDatagramStatus() {},
|
||||
onSessionHandshake() {},
|
||||
onSessionPathValidation() {},
|
||||
onSessionTicket() {},
|
||||
onSessionVersionNegotiation() {},
|
||||
onStreamCreated() {},
|
||||
onStreamBlocked() {},
|
||||
onStreamClose() {},
|
||||
onStreamReset() {},
|
||||
onStreamHeaders() {},
|
||||
onStreamTrailers() {},
|
||||
});
|
||||
const {
|
||||
Endpoint,
|
||||
} = require('internal/quic/quic');
|
||||
|
||||
throws(() => new quic.Endpoint(), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'options must be an object'
|
||||
});
|
||||
const {
|
||||
inspect,
|
||||
} = require('util');
|
||||
|
||||
throws(() => new quic.Endpoint('a'), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'options must be an object'
|
||||
});
|
||||
const callbackConfig = {
|
||||
onsession() {},
|
||||
session: {},
|
||||
stream: {},
|
||||
};
|
||||
|
||||
throws(() => new quic.Endpoint(null), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'options must be an object'
|
||||
});
|
||||
|
||||
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('invalid options', async () => {
|
||||
['a', null, false, NaN].forEach((i) => {
|
||||
throws(() => new Endpoint(callbackConfig, i), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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
|
||||
'use strict';
|
||||
|
||||
const common = 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 { hasQuic } = require('../common');
|
||||
|
||||
const {
|
||||
IDX_STATS_ENDPOINT_CREATED_AT,
|
||||
IDX_STATS_ENDPOINT_DESTROYED_AT,
|
||||
IDX_STATS_ENDPOINT_BYTES_RECEIVED,
|
||||
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;
|
||||
describe,
|
||||
it,
|
||||
} = require('node:test');
|
||||
|
||||
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);
|
||||
strictEqual(IDX_STATE_ENDPOINT_BOUND_SIZE, 1);
|
||||
strictEqual(IDX_STATE_ENDPOINT_RECEIVING_SIZE, 1);
|
||||
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);
|
||||
const {
|
||||
inspect,
|
||||
} = require('util');
|
||||
|
||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_BOUND), 0);
|
||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_RECEIVING), 0);
|
||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_LISTENING), 0);
|
||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_CLOSING), 0);
|
||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_BUSY), 0);
|
||||
strictEqual(state.getBigUint64(IDX_STATE_ENDPOINT_PENDING_CALLBACKS), 0n);
|
||||
const {
|
||||
deepStrictEqual,
|
||||
strictEqual,
|
||||
throws,
|
||||
} = require('node:assert');
|
||||
|
||||
endpoint.markBusy(true);
|
||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_BUSY), 1);
|
||||
endpoint.markBusy(false);
|
||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_BUSY), 0);
|
||||
it('endpoint state', () => {
|
||||
const endpoint = new Endpoint({
|
||||
onsession() {},
|
||||
session: {},
|
||||
stream: {},
|
||||
});
|
||||
|
||||
const stats = new BigUint64Array(endpoint.stats);
|
||||
strictEqual(stats[IDX_STATS_ENDPOINT_CREATED_AT], 0n);
|
||||
strictEqual(stats[IDX_STATS_ENDPOINT_DESTROYED_AT], 0n);
|
||||
strictEqual(stats[IDX_STATS_ENDPOINT_BYTES_RECEIVED], 0n);
|
||||
strictEqual(stats[IDX_STATS_ENDPOINT_BYTES_SENT], 0n);
|
||||
strictEqual(stats[IDX_STATS_ENDPOINT_PACKETS_RECEIVED], 0n);
|
||||
strictEqual(stats[IDX_STATS_ENDPOINT_PACKETS_SENT], 0n);
|
||||
strictEqual(stats[IDX_STATS_ENDPOINT_SERVER_SESSIONS], 0n);
|
||||
strictEqual(stats[IDX_STATS_ENDPOINT_CLIENT_SESSIONS], 0n);
|
||||
strictEqual(stats[IDX_STATS_ENDPOINT_SERVER_BUSY_COUNT], 0n);
|
||||
strictEqual(stats[IDX_STATS_ENDPOINT_RETRY_COUNT], 0n);
|
||||
strictEqual(stats[IDX_STATS_ENDPOINT_VERSION_NEGOTIATION_COUNT], 0n);
|
||||
strictEqual(stats[IDX_STATS_ENDPOINT_STATELESS_RESET_COUNT], 0n);
|
||||
strictEqual(stats[IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT], 0n);
|
||||
strictEqual(IDX_STATS_ENDPOINT_COUNT, 13);
|
||||
strictEqual(endpoint.state.isBound, false);
|
||||
strictEqual(endpoint.state.isReceiving, false);
|
||||
strictEqual(endpoint.state.isListening, false);
|
||||
strictEqual(endpoint.state.isClosing, false);
|
||||
strictEqual(endpoint.state.isBusy, false);
|
||||
strictEqual(endpoint.state.pendingCallbacks, 0n);
|
||||
|
||||
deepStrictEqual(JSON.parse(JSON.stringify(endpoint.state)), {
|
||||
isBound: false,
|
||||
isReceiving: false,
|
||||
isListening: false,
|
||||
isClosing: false,
|
||||
isBusy: false,
|
||||
pendingCallbacks: '0',
|
||||
});
|
||||
|
||||
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';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasQuic)
|
||||
common.skip('missing quic');
|
||||
const { internalBinding } = require('internal/test/binding');
|
||||
const quic = internalBinding('quic');
|
||||
const { hasQuic } = require('../common');
|
||||
|
||||
const { throws } = require('assert');
|
||||
const {
|
||||
describe,
|
||||
it,
|
||||
} = require('node:test');
|
||||
|
||||
const callbacks = {
|
||||
onEndpointClose() {},
|
||||
onSessionNew() {},
|
||||
onSessionClose() {},
|
||||
onSessionDatagram() {},
|
||||
onSessionDatagramStatus() {},
|
||||
onSessionHandshake() {},
|
||||
onSessionPathValidation() {},
|
||||
onSessionTicket() {},
|
||||
onSessionVersionNegotiation() {},
|
||||
onStreamCreated() {},
|
||||
onStreamBlocked() {},
|
||||
onStreamClose() {},
|
||||
onStreamReset() {},
|
||||
onStreamHeaders() {},
|
||||
onStreamTrailers() {},
|
||||
};
|
||||
// Fail if any callback is missing
|
||||
for (const fn of Object.keys(callbacks)) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { [fn]: _, ...rest } = callbacks;
|
||||
throws(() => quic.setCallbacks(rest), {
|
||||
code: 'ERR_MISSING_ARGS',
|
||||
describe('quic internal setCallbacks', { skip: !hasQuic }, () => {
|
||||
const { internalBinding } = require('internal/test/binding');
|
||||
const quic = internalBinding('quic');
|
||||
|
||||
it('require all callbacks to be set', (t) => {
|
||||
const callbacks = {
|
||||
onEndpointClose() {},
|
||||
onSessionNew() {},
|
||||
onSessionClose() {},
|
||||
onSessionDatagram() {},
|
||||
onSessionDatagramStatus() {},
|
||||
onSessionHandshake() {},
|
||||
onSessionPathValidation() {},
|
||||
onSessionTicket() {},
|
||||
onSessionVersionNegotiation() {},
|
||||
onStreamCreated() {},
|
||||
onStreamBlocked() {},
|
||||
onStreamClose() {},
|
||||
onStreamReset() {},
|
||||
onStreamHeaders() {},
|
||||
onStreamTrailers() {},
|
||||
};
|
||||
// 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