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 { hasQuic } = require('../common');
|
||||
|
||||
const { internalBinding } = require('internal/test/binding');
|
||||
const {
|
||||
describe,
|
||||
it,
|
||||
} = require('node:test');
|
||||
|
||||
describe('quic internal endpoint listen defaults', { skip: !hasQuic }, async () => {
|
||||
const {
|
||||
ok,
|
||||
strictEqual,
|
||||
deepStrictEqual,
|
||||
} = require('node:assert');
|
||||
throws,
|
||||
} = require('node:assert');
|
||||
|
||||
const {
|
||||
SocketAddress: _SocketAddress,
|
||||
AF_INET,
|
||||
} = internalBinding('block_list');
|
||||
const quic = internalBinding('quic');
|
||||
const {
|
||||
SocketAddress,
|
||||
} = require('net');
|
||||
|
||||
quic.setCallbacks({
|
||||
onEndpointClose: common.mustCall((...args) => {
|
||||
deepStrictEqual(args, [0, 0]);
|
||||
}),
|
||||
const {
|
||||
Endpoint,
|
||||
} = require('internal/quic/quic');
|
||||
|
||||
// The following are unused in this test
|
||||
onSessionNew() {},
|
||||
onSessionClose() {},
|
||||
onSessionDatagram() {},
|
||||
onSessionDatagramStatus() {},
|
||||
onSessionHandshake() {},
|
||||
onSessionPathValidation() {},
|
||||
onSessionTicket() {},
|
||||
onSessionVersionNegotiation() {},
|
||||
onStreamCreated() {},
|
||||
onStreamBlocked() {},
|
||||
onStreamClose() {},
|
||||
onStreamReset() {},
|
||||
onStreamHeaders() {},
|
||||
onStreamTrailers() {},
|
||||
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,60 +1,50 @@
|
||||
// Flags: --expose-internals
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasQuic)
|
||||
common.skip('missing quic');
|
||||
const { hasQuic } = require('../common');
|
||||
|
||||
const {
|
||||
describe,
|
||||
it,
|
||||
} = require('node:test');
|
||||
|
||||
describe('quic internal endpoint options', { skip: !hasQuic }, async () => {
|
||||
const {
|
||||
strictEqual,
|
||||
throws,
|
||||
} = require('node:assert');
|
||||
} = require('node:assert');
|
||||
|
||||
const { internalBinding } = require('internal/test/binding');
|
||||
const quic = internalBinding('quic');
|
||||
const {
|
||||
Endpoint,
|
||||
} = require('internal/quic/quic');
|
||||
|
||||
quic.setCallbacks({
|
||||
onEndpointClose() {},
|
||||
onSessionNew() {},
|
||||
onSessionClose() {},
|
||||
onSessionDatagram() {},
|
||||
onSessionDatagramStatus() {},
|
||||
onSessionHandshake() {},
|
||||
onSessionPathValidation() {},
|
||||
onSessionTicket() {},
|
||||
onSessionVersionNegotiation() {},
|
||||
onStreamCreated() {},
|
||||
onStreamBlocked() {},
|
||||
onStreamClose() {},
|
||||
onStreamReset() {},
|
||||
onStreamHeaders() {},
|
||||
onStreamTrailers() {},
|
||||
});
|
||||
const {
|
||||
inspect,
|
||||
} = require('util');
|
||||
|
||||
throws(() => new quic.Endpoint(), {
|
||||
const callbackConfig = {
|
||||
onsession() {},
|
||||
session: {},
|
||||
stream: {},
|
||||
};
|
||||
|
||||
it('invalid options', async () => {
|
||||
['a', null, false, NaN].forEach((i) => {
|
||||
throws(() => new Endpoint(callbackConfig, i), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'options must be an object'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
throws(() => new quic.Endpoint('a'), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'options must be an object'
|
||||
});
|
||||
|
||||
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'
|
||||
});
|
||||
|
||||
{
|
||||
it('valid options', async () => {
|
||||
// Just Works... using all defaults
|
||||
new quic.Endpoint({});
|
||||
}
|
||||
new Endpoint(callbackConfig, {});
|
||||
new Endpoint(callbackConfig);
|
||||
new Endpoint(callbackConfig, undefined);
|
||||
});
|
||||
|
||||
const cases = [
|
||||
it('various cases', async () => {
|
||||
const cases = [
|
||||
{
|
||||
key: 'retryTokenExpiration',
|
||||
valid: [
|
||||
@ -136,14 +126,12 @@ const cases = [
|
||||
{
|
||||
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,
|
||||
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, {}, [], () => {}],
|
||||
},
|
||||
@ -196,20 +184,47 @@ const cases = [
|
||||
valid: ['a', null, false, true, {}, [], () => {}],
|
||||
invalid: [],
|
||||
},
|
||||
];
|
||||
];
|
||||
|
||||
for (const { key, valid, invalid } of cases) {
|
||||
for (const { key, valid, invalid } of cases) {
|
||||
for (const value of valid) {
|
||||
const options = {};
|
||||
options[key] = value;
|
||||
new quic.Endpoint(options);
|
||||
new Endpoint(callbackConfig, options);
|
||||
}
|
||||
|
||||
for (const value of invalid) {
|
||||
const options = {};
|
||||
options[key] = value;
|
||||
throws(() => new quic.Endpoint(options), {
|
||||
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 { hasQuic } = require('../common');
|
||||
|
||||
const {
|
||||
describe,
|
||||
it,
|
||||
} = require('node:test');
|
||||
|
||||
describe('quic internal endpoint stats and state', { skip: !hasQuic }, () => {
|
||||
const {
|
||||
Endpoint,
|
||||
QuicStreamState,
|
||||
QuicStreamStats,
|
||||
SessionState,
|
||||
SessionStats,
|
||||
kFinishClose,
|
||||
} = require('internal/quic/quic');
|
||||
|
||||
const {
|
||||
inspect,
|
||||
} = require('util');
|
||||
|
||||
const {
|
||||
deepStrictEqual,
|
||||
strictEqual,
|
||||
} = require('node:assert');
|
||||
throws,
|
||||
} = require('node:assert');
|
||||
|
||||
const { internalBinding } = require('internal/test/binding');
|
||||
const quic = internalBinding('quic');
|
||||
it('endpoint state', () => {
|
||||
const endpoint = new Endpoint({
|
||||
onsession() {},
|
||||
session: {},
|
||||
stream: {},
|
||||
});
|
||||
|
||||
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;
|
||||
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);
|
||||
|
||||
const endpoint = new quic.Endpoint({});
|
||||
deepStrictEqual(JSON.parse(JSON.stringify(endpoint.state)), {
|
||||
isBound: false,
|
||||
isReceiving: false,
|
||||
isListening: false,
|
||||
isClosing: false,
|
||||
isBusy: false,
|
||||
pendingCallbacks: '0',
|
||||
});
|
||||
|
||||
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);
|
||||
endpoint.busy = true;
|
||||
strictEqual(endpoint.state.isBusy, true);
|
||||
endpoint.busy = false;
|
||||
strictEqual(endpoint.state.isBusy, false);
|
||||
|
||||
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);
|
||||
it('state can be inspected without errors', () => {
|
||||
strictEqual(typeof inspect(endpoint.state), 'string');
|
||||
});
|
||||
});
|
||||
|
||||
endpoint.markBusy(true);
|
||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_BUSY), 1);
|
||||
endpoint.markBusy(false);
|
||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_BUSY), 0);
|
||||
it('state is not readable after close', () => {
|
||||
const endpoint = new Endpoint({
|
||||
onsession() {},
|
||||
session: {},
|
||||
stream: {},
|
||||
}, {});
|
||||
endpoint.state[kFinishClose]();
|
||||
throws(() => endpoint.state.isBound, {
|
||||
name: 'Error',
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
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,15 +1,19 @@
|
||||
// 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 = {
|
||||
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() {},
|
||||
@ -25,14 +29,19 @@ const callbacks = {
|
||||
onStreamReset() {},
|
||||
onStreamHeaders() {},
|
||||
onStreamTrailers() {},
|
||||
};
|
||||
// Fail if any callback is missing
|
||||
for (const fn of Object.keys(callbacks)) {
|
||||
};
|
||||
// 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), {
|
||||
t.assert.throws(() => quic.setCallbacks(rest), {
|
||||
code: 'ERR_MISSING_ARGS',
|
||||
});
|
||||
}
|
||||
// If all callbacks are present it should work
|
||||
quic.setCallbacks(callbacks);
|
||||
}
|
||||
// If all callbacks are present it should work
|
||||
quic.setCallbacks(callbacks);
|
||||
|
||||
// Multiple calls should just be ignored.
|
||||
quic.setCallbacks(callbacks);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user