mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
quic: add quic internalBinding, refine Endpoint, add types
PR-URL: https://github.com/nodejs/node/pull/51112 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
This commit is contained in:
parent
20c63134fc
commit
c3664227a8
1
node.gyp
1
node.gyp
@ -377,6 +377,7 @@
|
||||
'src/quic/tlscontext.h',
|
||||
'src/quic/tokens.h',
|
||||
'src/quic/transportparams.h',
|
||||
'src/quic/quic.cc',
|
||||
],
|
||||
'node_cctest_sources': [
|
||||
'src/node_snapshot_stub.cc',
|
||||
|
@ -89,7 +89,8 @@
|
||||
NODE_BUILTIN_STANDARD_BINDINGS(V) \
|
||||
NODE_BUILTIN_OPENSSL_BINDINGS(V) \
|
||||
NODE_BUILTIN_ICU_BINDINGS(V) \
|
||||
NODE_BUILTIN_PROFILER_BINDINGS(V)
|
||||
NODE_BUILTIN_PROFILER_BINDINGS(V) \
|
||||
NODE_BUILTIN_QUIC_BINDINGS(V)
|
||||
|
||||
// This is used to load built-in bindings. Instead of using
|
||||
// __attribute__((constructor)), we call the _register_<modname>
|
||||
|
@ -30,6 +30,12 @@ static_assert(static_cast<int>(NM_F_LINKED) ==
|
||||
#define NODE_BUILTIN_ICU_BINDINGS(V)
|
||||
#endif
|
||||
|
||||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
#define NODE_BUILTIN_QUIC_BINDINGS(V) V(quic)
|
||||
#else
|
||||
#define NODE_BUILTIN_QUIC_BINDINGS(V)
|
||||
#endif
|
||||
|
||||
#define NODE_BINDINGS_WITH_PER_ISOLATE_INIT(V) \
|
||||
V(async_wrap) \
|
||||
V(blob) \
|
||||
@ -47,7 +53,8 @@ static_assert(static_cast<int>(NM_F_LINKED) ==
|
||||
V(timers) \
|
||||
V(url) \
|
||||
V(worker) \
|
||||
NODE_BUILTIN_ICU_BINDINGS(V)
|
||||
NODE_BUILTIN_ICU_BINDINGS(V) \
|
||||
NODE_BUILTIN_QUIC_BINDINGS(V)
|
||||
|
||||
#define NODE_BINDING_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags) \
|
||||
static node::node_module _module = { \
|
||||
|
@ -154,11 +154,18 @@ class ExternalReferenceRegistry {
|
||||
#define EXTERNAL_REFERENCE_BINDING_LIST_CRYPTO(V)
|
||||
#endif // HAVE_OPENSSL
|
||||
|
||||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
#define EXTERNAL_REFERENCE_BINDING_LIST_QUIC(V) V(quic)
|
||||
#else
|
||||
#define EXTERNAL_REFERENCE_BINDING_LIST_QUIC(V)
|
||||
#endif
|
||||
|
||||
#define EXTERNAL_REFERENCE_BINDING_LIST(V) \
|
||||
EXTERNAL_REFERENCE_BINDING_LIST_BASE(V) \
|
||||
EXTERNAL_REFERENCE_BINDING_LIST_INSPECTOR(V) \
|
||||
EXTERNAL_REFERENCE_BINDING_LIST_I18N(V) \
|
||||
EXTERNAL_REFERENCE_BINDING_LIST_CRYPTO(V)
|
||||
EXTERNAL_REFERENCE_BINDING_LIST_CRYPTO(V) \
|
||||
EXTERNAL_REFERENCE_BINDING_LIST_QUIC(V)
|
||||
|
||||
} // namespace node
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
#include "node_bob.h"
|
||||
#include "uv.h"
|
||||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
|
||||
#include <node_sockaddr-inl.h>
|
||||
#include <v8.h>
|
||||
#include "application.h"
|
||||
#include <node_bob.h>
|
||||
#include <node_sockaddr-inl.h>
|
||||
#include <uv.h>
|
||||
#include <v8.h>
|
||||
#include "defs.h"
|
||||
#include "endpoint.h"
|
||||
#include "packet.h"
|
||||
@ -38,14 +38,23 @@ const Session::Application_Options Session::Application_Options::kDefault = {};
|
||||
|
||||
Maybe<Session::Application_Options> Session::Application_Options::From(
|
||||
Environment* env, Local<Value> value) {
|
||||
if (value.IsEmpty() || !value->IsObject()) {
|
||||
if (value.IsEmpty()) {
|
||||
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
|
||||
return Nothing<Application_Options>();
|
||||
}
|
||||
|
||||
auto& state = BindingData::Get(env);
|
||||
auto params = value.As<Object>();
|
||||
Application_Options options;
|
||||
auto& state = BindingData::Get(env);
|
||||
if (value->IsUndefined()) {
|
||||
return Just<Application_Options>(options);
|
||||
}
|
||||
|
||||
if (!value->IsObject()) {
|
||||
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
|
||||
return Nothing<Application_Options>();
|
||||
}
|
||||
|
||||
auto params = value.As<Object>();
|
||||
|
||||
#define SET(name) \
|
||||
SetOption<Session::Application_Options, \
|
||||
|
@ -56,10 +56,11 @@ void BindingData::DecreaseAllocatedSize(size_t size) {
|
||||
current_ngtcp2_memory_ -= size;
|
||||
}
|
||||
|
||||
void BindingData::Initialize(Environment* env, Local<Object> target) {
|
||||
SetMethod(env->context(), target, "setCallbacks", SetCallbacks);
|
||||
SetMethod(env->context(), target, "flushPacketFreelist", FlushPacketFreelist);
|
||||
Realm::GetCurrent(env->context())->AddBindingData<BindingData>(target);
|
||||
void BindingData::InitPerContext(Realm* realm, Local<Object> target) {
|
||||
SetMethod(realm->context(), target, "setCallbacks", SetCallbacks);
|
||||
SetMethod(
|
||||
realm->context(), target, "flushPacketFreelist", FlushPacketFreelist);
|
||||
Realm::GetCurrent(realm->context())->AddBindingData<BindingData>(target);
|
||||
}
|
||||
|
||||
void BindingData::RegisterExternalReferences(
|
||||
|
@ -118,11 +118,14 @@ constexpr size_t kMaxVectorCount = 16;
|
||||
V(address_lru_size, "addressLRUSize") \
|
||||
V(alpn, "alpn") \
|
||||
V(application_options, "application") \
|
||||
V(bbr, "bbr") \
|
||||
V(bbr2, "bbr2") \
|
||||
V(ca, "ca") \
|
||||
V(certs, "certs") \
|
||||
V(cc_algorithm, "cc") \
|
||||
V(crl, "crl") \
|
||||
V(ciphers, "ciphers") \
|
||||
V(cubic, "cubic") \
|
||||
V(disable_active_migration, "disableActiveMigration") \
|
||||
V(disable_stateless_reset, "disableStatelessReset") \
|
||||
V(enable_tls_trace, "tlsTrace") \
|
||||
@ -162,6 +165,7 @@ constexpr size_t kMaxVectorCount = 16;
|
||||
V(qpack_encoder_max_dtable_capacity, "qpackEncoderMaxDTableCapacity") \
|
||||
V(qpack_max_dtable_capacity, "qpackMaxDTableCapacity") \
|
||||
V(reject_unauthorized, "rejectUnauthorized") \
|
||||
V(reno, "reno") \
|
||||
V(retry_token_expiration, "retryTokenExpiration") \
|
||||
V(request_peer_certificate, "requestPeerCertificate") \
|
||||
V(reset_token_secret, "resetTokenSecret") \
|
||||
@ -194,7 +198,7 @@ class BindingData final
|
||||
public mem::NgLibMemoryManager<BindingData, ngtcp2_mem> {
|
||||
public:
|
||||
SET_BINDING_ID(quic_binding_data)
|
||||
static void Initialize(Environment* env, v8::Local<v8::Object> target);
|
||||
static void InitPerContext(Realm* realm, v8::Local<v8::Object> target);
|
||||
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
|
||||
|
||||
static BindingData& Get(Environment* env);
|
||||
|
@ -28,6 +28,17 @@ bool SetOption(Environment* env,
|
||||
}
|
||||
|
||||
template <typename Opt, bool Opt::*member>
|
||||
bool SetOption(Environment* env,
|
||||
Opt* options,
|
||||
const v8::Local<v8::Object>& object,
|
||||
const v8::Local<v8::String>& name) {
|
||||
v8::Local<v8::Value> value;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
|
||||
options->*member = value->BooleanValue(env->isolate());
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Opt, uint32_t Opt::*member>
|
||||
bool SetOption(Environment* env,
|
||||
Opt* options,
|
||||
const v8::Local<v8::Object>& object,
|
||||
@ -35,8 +46,20 @@ bool SetOption(Environment* env,
|
||||
v8::Local<v8::Value> value;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
|
||||
if (!value->IsUndefined()) {
|
||||
CHECK(value->IsBoolean());
|
||||
options->*member = value->IsTrue();
|
||||
if (!value->IsUint32()) {
|
||||
Utf8Value nameStr(env->isolate(), name);
|
||||
THROW_ERR_INVALID_ARG_VALUE(
|
||||
env, "The %s option must be an uint32", *nameStr);
|
||||
return false;
|
||||
}
|
||||
v8::Local<v8::Uint32> num;
|
||||
if (!value->ToUint32(env->context()).ToLocal(&num)) {
|
||||
Utf8Value nameStr(env->isolate(), name);
|
||||
THROW_ERR_INVALID_ARG_VALUE(
|
||||
env, "The %s option must be an uint32", *nameStr);
|
||||
return false;
|
||||
}
|
||||
options->*member = num->Value();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -50,7 +73,13 @@ bool SetOption(Environment* env,
|
||||
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
|
||||
|
||||
if (!value->IsUndefined()) {
|
||||
CHECK_IMPLIES(!value->IsBigInt(), value->IsNumber());
|
||||
if (!value->IsBigInt() && !value->IsNumber()) {
|
||||
Utf8Value nameStr(env->isolate(), name);
|
||||
THROW_ERR_INVALID_ARG_VALUE(
|
||||
env, "option %s must be a bigint or number", *nameStr);
|
||||
return false;
|
||||
}
|
||||
DCHECK_IMPLIES(!value->IsBigInt(), value->IsNumber());
|
||||
|
||||
uint64_t val = 0;
|
||||
if (value->IsBigInt()) {
|
||||
@ -58,12 +87,17 @@ bool SetOption(Environment* env,
|
||||
val = value.As<v8::BigInt>()->Uint64Value(&lossless);
|
||||
if (!lossless) {
|
||||
Utf8Value label(env->isolate(), name);
|
||||
THROW_ERR_OUT_OF_RANGE(
|
||||
env, ("options." + label.ToString() + " is out of range").c_str());
|
||||
THROW_ERR_INVALID_ARG_VALUE(env, "option %s is out of range", *label);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
val = static_cast<int64_t>(value.As<v8::Number>()->Value());
|
||||
double dbl = value.As<v8::Number>()->Value();
|
||||
if (dbl < 0) {
|
||||
Utf8Value label(env->isolate(), name);
|
||||
THROW_ERR_INVALID_ARG_VALUE(env, "option %s is out of range", *label);
|
||||
return false;
|
||||
}
|
||||
val = static_cast<uint64_t>(dbl);
|
||||
}
|
||||
options->*member = val;
|
||||
}
|
||||
|
@ -7,12 +7,15 @@
|
||||
#include <memory_tracker-inl.h>
|
||||
#include <ngtcp2/ngtcp2.h>
|
||||
#include <node_errors.h>
|
||||
#include <node_external_reference.h>
|
||||
#include <node_sockaddr-inl.h>
|
||||
#include <req_wrap-inl.h>
|
||||
#include <util-inl.h>
|
||||
#include <uv.h>
|
||||
#include <v8.h>
|
||||
#include <limits>
|
||||
#include "application.h"
|
||||
#include "bindingdata.h"
|
||||
#include "defs.h"
|
||||
|
||||
namespace node {
|
||||
@ -30,8 +33,10 @@ using v8::Maybe;
|
||||
using v8::Nothing;
|
||||
using v8::Number;
|
||||
using v8::Object;
|
||||
using v8::ObjectTemplate;
|
||||
using v8::PropertyAttribute;
|
||||
using v8::String;
|
||||
using v8::Uint32;
|
||||
using v8::Value;
|
||||
|
||||
namespace quic {
|
||||
@ -43,14 +48,12 @@ namespace quic {
|
||||
V(RECEIVING, receiving, uint8_t) \
|
||||
/* Listening as a QUIC server */ \
|
||||
V(LISTENING, listening, uint8_t) \
|
||||
/* In the process of closing down */ \
|
||||
V(CLOSING, closing, uint8_t) \
|
||||
/* In the process of closing down, waiting for pending send callbacks */ \
|
||||
V(WAITING_FOR_CALLBACKS, waiting_for_callbacks, uint8_t) \
|
||||
V(CLOSING, closing, uint8_t) \
|
||||
/* Temporarily paused serving new initial requests */ \
|
||||
V(BUSY, busy, uint8_t) \
|
||||
/* The number of pending send callbacks */ \
|
||||
V(PENDING_CALLBACKS, pending_callbacks, size_t)
|
||||
V(PENDING_CALLBACKS, pending_callbacks, uint64_t)
|
||||
|
||||
#define ENDPOINT_STATS(V) \
|
||||
V(CREATED_AT, created_at) \
|
||||
@ -67,6 +70,12 @@ namespace quic {
|
||||
V(STATELESS_RESET_COUNT, stateless_reset_count) \
|
||||
V(IMMEDIATE_CLOSE_COUNT, immediate_close_count)
|
||||
|
||||
#define ENDPOINT_CC(V) \
|
||||
V(RENO, reno) \
|
||||
V(CUBIC, cubic) \
|
||||
V(BBR, bbr) \
|
||||
V(BBR2, bbr2)
|
||||
|
||||
struct Endpoint::State {
|
||||
#define V(_, name, type) type name;
|
||||
ENDPOINT_STATE(V)
|
||||
@ -76,7 +85,7 @@ struct Endpoint::State {
|
||||
STAT_STRUCT(Endpoint, ENDPOINT)
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// Endpoint::Options
|
||||
namespace {
|
||||
#ifdef DEBUG
|
||||
bool is_diagnostic_packet_loss(double probability) {
|
||||
@ -87,71 +96,107 @@ bool is_diagnostic_packet_loss(double probability) {
|
||||
}
|
||||
#endif // DEBUG
|
||||
|
||||
Maybe<ngtcp2_cc_algo> getAlgoFromString(Environment* env, Local<String> input) {
|
||||
auto& state = BindingData::Get(env);
|
||||
#define V(name, str) \
|
||||
if (input->StringEquals(state.str##_string())) { \
|
||||
return Just(NGTCP2_CC_ALGO_##name); \
|
||||
}
|
||||
|
||||
ENDPOINT_CC(V)
|
||||
|
||||
#undef V
|
||||
return Nothing<ngtcp2_cc_algo>();
|
||||
}
|
||||
|
||||
template <typename Opt, ngtcp2_cc_algo Opt::*member>
|
||||
bool SetOption(Environment* env,
|
||||
Opt* options,
|
||||
const v8::Local<v8::Object>& object,
|
||||
const v8::Local<v8::String>& name) {
|
||||
v8::Local<v8::Value> value;
|
||||
const Local<Object>& object,
|
||||
const Local<String>& name) {
|
||||
Local<Value> value;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
|
||||
if (!value->IsUndefined()) {
|
||||
int num = value.As<Int32>()->Value();
|
||||
switch (num) {
|
||||
case NGTCP2_CC_ALGO_RENO:
|
||||
[[fallthrough]];
|
||||
case NGTCP2_CC_ALGO_CUBIC:
|
||||
[[fallthrough]];
|
||||
case NGTCP2_CC_ALGO_BBR:
|
||||
[[fallthrough]];
|
||||
case NGTCP2_CC_ALGO_BBR2:
|
||||
break;
|
||||
default:
|
||||
THROW_ERR_INVALID_ARG_VALUE(env, "The cc_algorithm is invalid");
|
||||
ngtcp2_cc_algo algo;
|
||||
if (value->IsString()) {
|
||||
if (!getAlgoFromString(env, value.As<String>()).To(&algo)) {
|
||||
THROW_ERR_INVALID_ARG_VALUE(env, "The cc_algorithm option is invalid");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!value->IsInt32()) {
|
||||
THROW_ERR_INVALID_ARG_VALUE(
|
||||
env, "The cc_algorithm option must be a string or an integer");
|
||||
return false;
|
||||
}
|
||||
Local<Int32> num;
|
||||
if (!value->ToInt32(env->context()).ToLocal(&num)) {
|
||||
THROW_ERR_INVALID_ARG_VALUE(env, "The cc_algorithm option is invalid");
|
||||
return false;
|
||||
}
|
||||
switch (num->Value()) {
|
||||
#define V(name, _) \
|
||||
case NGTCP2_CC_ALGO_##name: \
|
||||
break;
|
||||
ENDPOINT_CC(V)
|
||||
#undef V
|
||||
default:
|
||||
THROW_ERR_INVALID_ARG_VALUE(env,
|
||||
"The cc_algorithm option is invalid");
|
||||
return false;
|
||||
}
|
||||
algo = static_cast<ngtcp2_cc_algo>(num->Value());
|
||||
}
|
||||
options->*member = static_cast<ngtcp2_cc_algo>(num);
|
||||
options->*member = algo;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
template <typename Opt, double Opt::*member>
|
||||
bool SetOption(Environment* env,
|
||||
Opt* options,
|
||||
const v8::Local<v8::Object>& object,
|
||||
const v8::Local<v8::String>& name) {
|
||||
v8::Local<v8::Value> value;
|
||||
const Local<Object>& object,
|
||||
const Local<String>& name) {
|
||||
Local<Value> value;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
|
||||
if (!value->IsUndefined()) {
|
||||
CHECK(value->IsNumber());
|
||||
options->*member = value.As<Number>()->Value();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Opt, uint32_t Opt::*member>
|
||||
bool SetOption(Environment* env,
|
||||
Opt* options,
|
||||
const v8::Local<v8::Object>& object,
|
||||
const v8::Local<v8::String>& name) {
|
||||
v8::Local<v8::Value> value;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
|
||||
if (!value->IsUndefined()) {
|
||||
CHECK(value->IsNumber());
|
||||
options->*member = value.As<Int32>()->Value();
|
||||
Local<Number> num;
|
||||
if (!value->ToNumber(env->context()).ToLocal(&num)) {
|
||||
Utf8Value nameStr(env->isolate(), name);
|
||||
THROW_ERR_INVALID_ARG_VALUE(
|
||||
env, "The %s option must be a number", *nameStr);
|
||||
return false;
|
||||
}
|
||||
options->*member = num->Value();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif // DEBUG
|
||||
|
||||
template <typename Opt, uint8_t Opt::*member>
|
||||
bool SetOption(Environment* env,
|
||||
Opt* options,
|
||||
const v8::Local<v8::Object>& object,
|
||||
const v8::Local<v8::String>& name) {
|
||||
v8::Local<v8::Value> value;
|
||||
const Local<Object>& object,
|
||||
const Local<String>& name) {
|
||||
Local<Value> value;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
|
||||
if (!value->IsUndefined()) {
|
||||
CHECK(value->IsNumber());
|
||||
options->*member = value.As<Int32>()->Value();
|
||||
if (!value->IsUint32()) {
|
||||
Utf8Value nameStr(env->isolate(), name);
|
||||
THROW_ERR_INVALID_ARG_VALUE(
|
||||
env, "The %s option must be an uint8", *nameStr);
|
||||
return false;
|
||||
}
|
||||
Local<Uint32> num;
|
||||
if (!value->ToUint32(env->context()).ToLocal(&num) ||
|
||||
num->Value() > std::numeric_limits<uint8_t>::max()) {
|
||||
Utf8Value nameStr(env->isolate(), name);
|
||||
THROW_ERR_INVALID_ARG_VALUE(
|
||||
env, "The %s option must be an uint8", *nameStr);
|
||||
return false;
|
||||
}
|
||||
options->*member = num->Value();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -159,16 +204,30 @@ bool SetOption(Environment* env,
|
||||
template <typename Opt, TokenSecret Opt::*member>
|
||||
bool SetOption(Environment* env,
|
||||
Opt* options,
|
||||
const v8::Local<v8::Object>& object,
|
||||
const v8::Local<v8::String>& name) {
|
||||
v8::Local<v8::Value> value;
|
||||
const Local<Object>& object,
|
||||
const Local<String>& name) {
|
||||
Local<Value> value;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
|
||||
if (!value->IsUndefined()) {
|
||||
CHECK(value->IsArrayBufferView());
|
||||
if (!value->IsArrayBufferView()) {
|
||||
Utf8Value nameStr(env->isolate(), name);
|
||||
THROW_ERR_INVALID_ARG_VALUE(
|
||||
env, "The %s option must be an ArrayBufferView", *nameStr);
|
||||
return false;
|
||||
}
|
||||
Store store(value.As<ArrayBufferView>());
|
||||
CHECK_EQ(store.length(), TokenSecret::QUIC_TOKENSECRET_LEN);
|
||||
if (store.length() != TokenSecret::QUIC_TOKENSECRET_LEN) {
|
||||
Utf8Value nameStr(env->isolate(), name);
|
||||
THROW_ERR_INVALID_ARG_VALUE(
|
||||
env,
|
||||
"The %s option must be an ArrayBufferView of length %d",
|
||||
*nameStr,
|
||||
TokenSecret::QUIC_TOKENSECRET_LEN);
|
||||
return false;
|
||||
}
|
||||
ngtcp2_vec buf = store;
|
||||
options->*member = buf.base;
|
||||
TokenSecret secret(buf.base);
|
||||
options->*member = secret;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -204,6 +263,26 @@ Maybe<Endpoint::Options> Endpoint::Options::From(Environment* env,
|
||||
return Nothing<Options>();
|
||||
}
|
||||
|
||||
Local<Value> address;
|
||||
if (!params->Get(env->context(), env->address_string()).ToLocal(&address)) {
|
||||
return Nothing<Options>();
|
||||
}
|
||||
if (!address->IsUndefined()) {
|
||||
if (!SocketAddressBase::HasInstance(env, address)) {
|
||||
THROW_ERR_INVALID_ARG_TYPE(env,
|
||||
"The address option must be a SocketAddress");
|
||||
return Nothing<Options>();
|
||||
}
|
||||
auto addr = FromJSObject<SocketAddressBase>(address.As<v8::Object>());
|
||||
options.local_address = addr->address();
|
||||
} else {
|
||||
options.local_address = std::make_shared<SocketAddress>();
|
||||
if (!SocketAddress::New("127.0.0.1", 0, options.local_address.get())) {
|
||||
THROW_ERR_INVALID_ADDRESS(env);
|
||||
return Nothing<Options>();
|
||||
}
|
||||
}
|
||||
|
||||
return Just<Options>(options);
|
||||
|
||||
#undef SET
|
||||
@ -233,16 +312,15 @@ class Endpoint::UDP::Impl final : public HandleWrap {
|
||||
return tmpl;
|
||||
}
|
||||
|
||||
static BaseObjectPtr<Impl> Create(Endpoint* endpoint) {
|
||||
static Impl* Create(Endpoint* endpoint) {
|
||||
Local<Object> obj;
|
||||
if (!GetConstructorTemplate(endpoint->env())
|
||||
->InstanceTemplate()
|
||||
->NewInstance(endpoint->env()->context())
|
||||
.ToLocal(&obj)) {
|
||||
return BaseObjectPtr<Impl>();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return MakeDetachedBaseObject<Impl>(endpoint, obj);
|
||||
return new Impl(endpoint, obj);
|
||||
}
|
||||
|
||||
static Impl* From(uv_udp_t* handle) {
|
||||
@ -268,10 +346,6 @@ class Endpoint::UDP::Impl final : public HandleWrap {
|
||||
SET_SELF_SIZE(Impl)
|
||||
|
||||
private:
|
||||
static void ClosedCb(uv_handle_t* handle) {
|
||||
std::unique_ptr<Impl> ptr(From(handle));
|
||||
}
|
||||
|
||||
static void OnAlloc(uv_handle_t* handle,
|
||||
size_t suggested_size,
|
||||
uv_buf_t* buf) {
|
||||
@ -309,7 +383,7 @@ class Endpoint::UDP::Impl final : public HandleWrap {
|
||||
};
|
||||
|
||||
Endpoint::UDP::UDP(Endpoint* endpoint) : impl_(Impl::Create(endpoint)) {
|
||||
endpoint->env()->AddCleanupHook(CleanupHook, this);
|
||||
DCHECK(impl_);
|
||||
}
|
||||
|
||||
Endpoint::UDP::~UDP() {
|
||||
@ -318,12 +392,12 @@ Endpoint::UDP::~UDP() {
|
||||
|
||||
int Endpoint::UDP::Bind(const Endpoint::Options& options) {
|
||||
if (is_bound_) return UV_EALREADY;
|
||||
if (is_closed() || impl_->IsHandleClosing()) return UV_EBADF;
|
||||
if (is_closed_or_closing()) return UV_EBADF;
|
||||
|
||||
int flags = 0;
|
||||
if (options.local_address.family() == AF_INET6 && options.ipv6_only)
|
||||
if (options.local_address->family() == AF_INET6 && options.ipv6_only)
|
||||
flags |= UV_UDP_IPV6ONLY;
|
||||
int err = uv_udp_bind(&impl_->handle_, options.local_address.data(), flags);
|
||||
int err = uv_udp_bind(&impl_->handle_, options.local_address->data(), flags);
|
||||
int size;
|
||||
|
||||
if (!err) {
|
||||
@ -361,7 +435,7 @@ void Endpoint::UDP::Unref() {
|
||||
}
|
||||
|
||||
int Endpoint::UDP::Start() {
|
||||
if (is_closed() || impl_->IsHandleClosing()) return UV_EBADF;
|
||||
if (is_closed_or_closing()) return UV_EBADF;
|
||||
if (is_started_) return 0;
|
||||
int err = uv_udp_recv_start(&impl_->handle_, Impl::OnAlloc, Impl::OnReceive);
|
||||
is_started_ = (err == 0);
|
||||
@ -369,16 +443,17 @@ int Endpoint::UDP::Start() {
|
||||
}
|
||||
|
||||
void Endpoint::UDP::Stop() {
|
||||
if (is_closed() || impl_->IsHandleClosing() || !is_started_) return;
|
||||
if (is_closed_or_closing() || !is_started_) return;
|
||||
USE(uv_udp_recv_stop(&impl_->handle_));
|
||||
is_started_ = false;
|
||||
}
|
||||
|
||||
void Endpoint::UDP::Close() {
|
||||
if (is_closed() || impl_->IsHandleClosing()) return;
|
||||
if (is_closed_or_closing()) return;
|
||||
DCHECK(impl_);
|
||||
Stop();
|
||||
is_bound_ = false;
|
||||
impl_->env()->RemoveCleanupHook(CleanupHook, this);
|
||||
is_closed_ = true;
|
||||
impl_->Close();
|
||||
impl_.reset();
|
||||
}
|
||||
@ -388,20 +463,26 @@ bool Endpoint::UDP::is_bound() const {
|
||||
}
|
||||
|
||||
bool Endpoint::UDP::is_closed() const {
|
||||
return !impl_;
|
||||
return is_closed_;
|
||||
}
|
||||
|
||||
bool Endpoint::UDP::is_closed_or_closing() const {
|
||||
if (is_closed() || !impl_) return true;
|
||||
return impl_->IsHandleClosing();
|
||||
}
|
||||
|
||||
Endpoint::UDP::operator bool() const {
|
||||
return !impl_;
|
||||
return impl_;
|
||||
}
|
||||
|
||||
SocketAddress Endpoint::UDP::local_address() const {
|
||||
CHECK(!is_closed() && is_bound());
|
||||
DCHECK(!is_closed() && is_bound());
|
||||
return SocketAddress::FromSockName(impl_->handle_);
|
||||
}
|
||||
|
||||
int Endpoint::UDP::Send(BaseObjectPtr<Packet> packet) {
|
||||
if (is_closed() || impl_->IsHandleClosing()) return UV_EBADF;
|
||||
CHECK(packet && !packet->is_sending());
|
||||
if (is_closed_or_closing()) return UV_EBADF;
|
||||
DCHECK(packet && !packet->is_sending());
|
||||
uv_buf_t buf = *packet;
|
||||
return packet->Dispatch(
|
||||
uv_udp_send,
|
||||
@ -419,10 +500,6 @@ void Endpoint::UDP::MemoryInfo(MemoryTracker* tracker) const {
|
||||
if (impl_) tracker->TrackField("impl", impl_);
|
||||
}
|
||||
|
||||
void Endpoint::UDP::CleanupHook(void* data) {
|
||||
static_cast<UDP*>(data)->Close();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool Endpoint::HasInstance(Environment* env, Local<Value> value) {
|
||||
@ -434,7 +511,7 @@ Local<FunctionTemplate> Endpoint::GetConstructorTemplate(Environment* env) {
|
||||
auto tmpl = state.endpoint_constructor_template();
|
||||
if (tmpl.IsEmpty()) {
|
||||
auto isolate = env->isolate();
|
||||
tmpl = NewFunctionTemplate(isolate, IllegalConstructor);
|
||||
tmpl = NewFunctionTemplate(isolate, New);
|
||||
tmpl->Inherit(AsyncWrap::GetConstructorTemplate(env));
|
||||
tmpl->SetClassName(state.endpoint_string());
|
||||
tmpl->InstanceTemplate()->SetInternalFieldCount(
|
||||
@ -444,54 +521,70 @@ Local<FunctionTemplate> Endpoint::GetConstructorTemplate(Environment* env) {
|
||||
SetProtoMethod(isolate, tmpl, "connect", DoConnect);
|
||||
SetProtoMethod(isolate, tmpl, "markBusy", MarkBusy);
|
||||
SetProtoMethod(isolate, tmpl, "ref", Ref);
|
||||
SetProtoMethod(isolate, tmpl, "unref", Unref);
|
||||
SetProtoMethodNoSideEffect(isolate, tmpl, "address", LocalAddress);
|
||||
state.set_endpoint_constructor_template(tmpl);
|
||||
}
|
||||
return tmpl;
|
||||
}
|
||||
|
||||
void Endpoint::Initialize(Environment* env, Local<Object> target) {
|
||||
SetMethod(env->context(), target, "createEndpoint", CreateEndpoint);
|
||||
void Endpoint::InitPerIsolate(IsolateData* data, Local<ObjectTemplate> target) {
|
||||
// TODO(@jasnell): Implement the per-isolate state
|
||||
}
|
||||
|
||||
void Endpoint::InitPerContext(Realm* realm, Local<Object> target) {
|
||||
#define V(name, str) \
|
||||
NODE_DEFINE_CONSTANT(target, QUIC_CC_ALGO_##name); \
|
||||
NODE_DEFINE_STRING_CONSTANT(target, "QUIC_CC_ALGO_" #name "_STR", #str);
|
||||
ENDPOINT_CC(V)
|
||||
#undef V
|
||||
|
||||
#define V(name, _) IDX_STATS_ENDPOINT_##name,
|
||||
enum EndpointStatsIdx { ENDPOINT_STATS(V) IDX_STATS_ENDPOINT_COUNT };
|
||||
enum IDX_STATS_ENDPONT { ENDPOINT_STATS(V) IDX_STATS_ENDPOINT_COUNT };
|
||||
NODE_DEFINE_CONSTANT(target, IDX_STATS_ENDPOINT_COUNT);
|
||||
#undef V
|
||||
|
||||
#define V(name, key, __) \
|
||||
auto IDX_STATE_ENDPOINT_##name = offsetof(Endpoint::State, key);
|
||||
#define V(name, key) NODE_DEFINE_CONSTANT(target, IDX_STATS_ENDPOINT_##name);
|
||||
ENDPOINT_STATS(V);
|
||||
#undef V
|
||||
|
||||
#define V(name, key, type) \
|
||||
static constexpr auto IDX_STATE_ENDPOINT_##name = \
|
||||
offsetof(Endpoint::State, key); \
|
||||
static constexpr auto IDX_STATE_ENDPOINT_##name##_SIZE = sizeof(type); \
|
||||
NODE_DEFINE_CONSTANT(target, IDX_STATE_ENDPOINT_##name); \
|
||||
NODE_DEFINE_CONSTANT(target, IDX_STATE_ENDPOINT_##name##_SIZE);
|
||||
ENDPOINT_STATE(V)
|
||||
#undef V
|
||||
|
||||
#define V(name, _) NODE_DEFINE_CONSTANT(target, IDX_STATS_ENDPOINT_##name);
|
||||
ENDPOINT_STATS(V)
|
||||
#undef V
|
||||
#define V(name, _, __) NODE_DEFINE_CONSTANT(target, IDX_STATE_ENDPOINT_##name);
|
||||
ENDPOINT_STATE(V)
|
||||
#undef V
|
||||
NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_CONNECTIONS);
|
||||
NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_CONNECTIONS_PER_HOST);
|
||||
NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_SOCKETADDRESS_LRU_SIZE);
|
||||
NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_STATELESS_RESETS);
|
||||
NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_RETRY_LIMIT);
|
||||
|
||||
static constexpr auto DEFAULT_RETRYTOKEN_EXPIRATION =
|
||||
RetryToken::QUIC_DEFAULT_RETRYTOKEN_EXPIRATION / NGTCP2_SECONDS;
|
||||
static constexpr auto DEFAULT_REGULARTOKEN_EXPIRATION =
|
||||
RegularToken::QUIC_DEFAULT_REGULARTOKEN_EXPIRATION / NGTCP2_SECONDS;
|
||||
static constexpr auto DEFAULT_MAX_PACKET_LENGTH = kDefaultMaxPacketLength;
|
||||
NODE_DEFINE_CONSTANT(target, DEFAULT_RETRYTOKEN_EXPIRATION);
|
||||
NODE_DEFINE_CONSTANT(target, DEFAULT_REGULARTOKEN_EXPIRATION);
|
||||
NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_PACKET_LENGTH);
|
||||
|
||||
SetConstructorFunction(realm->context(),
|
||||
target,
|
||||
"Endpoint",
|
||||
GetConstructorTemplate(realm->env()));
|
||||
}
|
||||
|
||||
void Endpoint::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||
registry->Register(CreateEndpoint);
|
||||
registry->Register(New);
|
||||
registry->Register(DoConnect);
|
||||
registry->Register(DoListen);
|
||||
registry->Register(DoCloseGracefully);
|
||||
registry->Register(LocalAddress);
|
||||
registry->Register(Ref);
|
||||
registry->Register(Unref);
|
||||
}
|
||||
|
||||
BaseObjectPtr<Endpoint> Endpoint::Create(Environment* env,
|
||||
const Endpoint::Options& options) {
|
||||
Local<Object> obj;
|
||||
if (!GetConstructorTemplate(env)
|
||||
->InstanceTemplate()
|
||||
->NewInstance(env->context())
|
||||
.ToLocal(&obj)) {
|
||||
return BaseObjectPtr<Endpoint>();
|
||||
}
|
||||
|
||||
return MakeDetachedBaseObject<Endpoint>(env, obj, options);
|
||||
registry->Register(MarkBusy);
|
||||
}
|
||||
|
||||
Endpoint::Endpoint(Environment* env,
|
||||
@ -500,7 +593,7 @@ Endpoint::Endpoint(Environment* env,
|
||||
: AsyncWrap(env, object, AsyncWrap::PROVIDER_QUIC_ENDPOINT),
|
||||
stats_(env->isolate()),
|
||||
state_(env->isolate()),
|
||||
options_(std::move(options)),
|
||||
options_(options),
|
||||
udp_(this),
|
||||
addrLRU_(options_.address_lru_size) {
|
||||
MakeWeak();
|
||||
@ -516,15 +609,8 @@ Endpoint::Endpoint(Environment* env,
|
||||
defineProperty(env->stats_string(), stats_.GetArrayBuffer());
|
||||
}
|
||||
|
||||
Endpoint::~Endpoint() {
|
||||
udp_.Close();
|
||||
DCHECK_EQ(state_->pending_callbacks, 0);
|
||||
DCHECK(sessions_.empty());
|
||||
DCHECK(is_closed());
|
||||
}
|
||||
|
||||
SocketAddress Endpoint::local_address() const {
|
||||
CHECK(!is_closed());
|
||||
DCHECK(!is_closed() && !is_closing());
|
||||
return udp_.local_address();
|
||||
}
|
||||
|
||||
@ -562,7 +648,7 @@ void Endpoint::RemoveSession(const CID& cid) {
|
||||
if (!session) return;
|
||||
DecrementSocketAddressCounter(session->remote_address());
|
||||
sessions_.erase(cid);
|
||||
if (state_->waiting_for_callbacks == 1) MaybeDestroy();
|
||||
if (state_->closing == 1) MaybeDestroy();
|
||||
}
|
||||
|
||||
BaseObjectPtr<Session> Endpoint::FindSession(const CID& cid) {
|
||||
@ -764,18 +850,17 @@ BaseObjectPtr<Session> Endpoint::Connect(
|
||||
}
|
||||
|
||||
void Endpoint::MaybeDestroy() {
|
||||
if (!is_closing() && sessions_.empty() && state_->pending_callbacks == 0 &&
|
||||
if (!is_closed() && sessions_.empty() && state_->pending_callbacks == 0 &&
|
||||
state_->listening == 0) {
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void Endpoint::Destroy(CloseContext context, int status) {
|
||||
if (is_closed() || is_closing()) return;
|
||||
if (is_closed()) return;
|
||||
|
||||
STAT_RECORD_TIMESTAMP(Stats, destroyed_at);
|
||||
|
||||
state_->closing = 1;
|
||||
state_->listening = 0;
|
||||
|
||||
close_context_ = context;
|
||||
@ -790,7 +875,7 @@ void Endpoint::Destroy(CloseContext context, int status) {
|
||||
for (auto& session : sessions)
|
||||
session.second->Close(Session::CloseMethod::SILENT);
|
||||
sessions.clear();
|
||||
CHECK(sessions_.empty());
|
||||
DCHECK(sessions_.empty());
|
||||
token_map_.clear();
|
||||
dcid_to_scid_.clear();
|
||||
|
||||
@ -804,10 +889,10 @@ void Endpoint::Destroy(CloseContext context, int status) {
|
||||
}
|
||||
|
||||
void Endpoint::CloseGracefully() {
|
||||
if (!is_closed() && !is_closing() && state_->waiting_for_callbacks == 0) {
|
||||
state_->listening = 0;
|
||||
state_->waiting_for_callbacks = 1;
|
||||
}
|
||||
if (is_closed() || is_closing()) return;
|
||||
|
||||
state_->listening = 0;
|
||||
state_->closing = 1;
|
||||
|
||||
// Maybe we can go ahead and destroy now?
|
||||
MaybeDestroy();
|
||||
@ -1188,7 +1273,7 @@ void Endpoint::PacketDone(int status) {
|
||||
if (is_closed()) return;
|
||||
state_->pending_callbacks--;
|
||||
// Can we go ahead and close now?
|
||||
if (state_->waiting_for_callbacks == 1) {
|
||||
if (state_->closing == 1) {
|
||||
// MaybeDestroy potentially creates v8 handles so let's make sure
|
||||
// we have a HandleScope on the stack.
|
||||
HandleScope scope(env()->isolate());
|
||||
@ -1267,18 +1352,17 @@ void Endpoint::EmitClose(CloseContext context, int status) {
|
||||
// ======================================================================================
|
||||
// Endpoint JavaScript API
|
||||
|
||||
void Endpoint::CreateEndpoint(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK(!args.IsConstructCall());
|
||||
void Endpoint::New(const FunctionCallbackInfo<Value>& args) {
|
||||
DCHECK(args.IsConstructCall());
|
||||
auto env = Environment::GetCurrent(args);
|
||||
CHECK(args[0]->IsObject());
|
||||
Options options;
|
||||
// Options::From will validate that args[0] is the correct type.
|
||||
if (!Options::From(env, args[0]).To(&options)) {
|
||||
// There was an error. Just exit to propagate.
|
||||
return;
|
||||
}
|
||||
|
||||
auto endpoint = Endpoint::Create(env, options);
|
||||
if (endpoint) args.GetReturnValue().Set(endpoint->object());
|
||||
new Endpoint(env, args.This(), options);
|
||||
}
|
||||
|
||||
void Endpoint::DoConnect(const FunctionCallbackInfo<Value>& args) {
|
||||
@ -1342,23 +1426,21 @@ void Endpoint::LocalAddress(const FunctionCallbackInfo<Value>& args) {
|
||||
auto env = Environment::GetCurrent(args);
|
||||
Endpoint* endpoint;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&endpoint, args.Holder());
|
||||
if (endpoint->is_closed()) return;
|
||||
auto local_address = endpoint->local_address();
|
||||
if (endpoint->is_closed() || !endpoint->udp_.is_bound()) return;
|
||||
auto addr = SocketAddressBase::Create(
|
||||
env, std::make_shared<SocketAddress>(local_address));
|
||||
env, std::make_shared<SocketAddress>(endpoint->local_address()));
|
||||
if (addr) args.GetReturnValue().Set(addr->object());
|
||||
}
|
||||
|
||||
void Endpoint::Ref(const FunctionCallbackInfo<Value>& args) {
|
||||
Endpoint* endpoint;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&endpoint, args.Holder());
|
||||
endpoint->udp_.Ref();
|
||||
}
|
||||
|
||||
void Endpoint::Unref(const FunctionCallbackInfo<Value>& args) {
|
||||
Endpoint* endpoint;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&endpoint, args.Holder());
|
||||
endpoint->udp_.Unref();
|
||||
auto env = Environment::GetCurrent(args);
|
||||
if (args[0]->BooleanValue(env->isolate())) {
|
||||
endpoint->udp_.Ref();
|
||||
} else {
|
||||
endpoint->udp_.Unref();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace quic
|
||||
|
@ -12,7 +12,6 @@
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include "bindingdata.h"
|
||||
#include "defs.h"
|
||||
#include "packet.h"
|
||||
#include "session.h"
|
||||
#include "sessionticket.h"
|
||||
@ -26,20 +25,25 @@ namespace quic {
|
||||
// client and server simultaneously.
|
||||
class Endpoint final : public AsyncWrap, public Packet::Listener {
|
||||
public:
|
||||
static constexpr size_t DEFAULT_MAX_CONNECTIONS =
|
||||
std::min<size_t>(kMaxSizeT, static_cast<size_t>(kMaxSafeJsInteger));
|
||||
static constexpr size_t DEFAULT_MAX_CONNECTIONS_PER_HOST = 100;
|
||||
static constexpr size_t DEFAULT_MAX_SOCKETADDRESS_LRU_SIZE =
|
||||
static constexpr uint64_t DEFAULT_MAX_CONNECTIONS =
|
||||
std::min<uint64_t>(kMaxSizeT, static_cast<uint64_t>(kMaxSafeJsInteger));
|
||||
static constexpr uint64_t DEFAULT_MAX_CONNECTIONS_PER_HOST = 100;
|
||||
static constexpr uint64_t DEFAULT_MAX_SOCKETADDRESS_LRU_SIZE =
|
||||
(DEFAULT_MAX_CONNECTIONS_PER_HOST * 10);
|
||||
static constexpr size_t DEFAULT_MAX_STATELESS_RESETS = 10;
|
||||
static constexpr size_t DEFAULT_MAX_RETRY_LIMIT = 10;
|
||||
static constexpr uint64_t DEFAULT_MAX_STATELESS_RESETS = 10;
|
||||
static constexpr uint64_t DEFAULT_MAX_RETRY_LIMIT = 10;
|
||||
|
||||
static constexpr auto QUIC_CC_ALGO_RENO = NGTCP2_CC_ALGO_RENO;
|
||||
static constexpr auto QUIC_CC_ALGO_CUBIC = NGTCP2_CC_ALGO_CUBIC;
|
||||
static constexpr auto QUIC_CC_ALGO_BBR = NGTCP2_CC_ALGO_BBR;
|
||||
static constexpr auto QUIC_CC_ALGO_BBR2 = NGTCP2_CC_ALGO_BBR2;
|
||||
|
||||
// Endpoint configuration options
|
||||
struct Options final : public MemoryRetainer {
|
||||
// The local socket address to which the UDP port will be bound. The port
|
||||
// may be 0 to have Node.js select an available port. IPv6 or IPv4 addresses
|
||||
// may be used. When using IPv6, dual mode will be supported by default.
|
||||
SocketAddress local_address;
|
||||
std::shared_ptr<SocketAddress> local_address;
|
||||
|
||||
// Retry tokens issued by the Endpoint are time-limited. By default, retry
|
||||
// tokens expire after DEFAULT_RETRYTOKEN_EXPIRATION *seconds*. This is an
|
||||
@ -134,14 +138,15 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
|
||||
// is the better of the two for our needs.
|
||||
ngtcp2_cc_algo cc_algorithm = NGTCP2_CC_ALGO_CUBIC;
|
||||
|
||||
// By default, when Node.js starts, it will generate a reset_token_secret at
|
||||
// random. This is a secret used in generating stateless reset tokens. In
|
||||
// order for stateless reset to be effective, however, it is necessary to
|
||||
// use a deterministic secret that persists across ngtcp2 endpoints and
|
||||
// sessions.
|
||||
// By default, when the endpoint is created, it will generate a
|
||||
// reset_token_secret at random. This is a secret used in generating
|
||||
// stateless reset tokens. In order for stateless reset to be effective,
|
||||
// however, it is necessary to use a deterministic secret that persists
|
||||
// across ngtcp2 endpoints and sessions. This means that the endpoint
|
||||
// configuration really should have a reset token secret passed in.
|
||||
TokenSecret reset_token_secret;
|
||||
|
||||
// The secret used for generating new tokens.
|
||||
// The secret used for generating new regular tokens.
|
||||
TokenSecret token_secret;
|
||||
|
||||
// When the local_address specifies an IPv6 local address to bind to, the
|
||||
@ -169,16 +174,14 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
|
||||
bool HasInstance(Environment* env, v8::Local<v8::Value> value);
|
||||
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
|
||||
Environment* env);
|
||||
static void Initialize(Environment* env, v8::Local<v8::Object> target);
|
||||
static void InitPerIsolate(IsolateData* data,
|
||||
v8::Local<v8::ObjectTemplate> target);
|
||||
static void InitPerContext(Realm* realm, v8::Local<v8::Object> target);
|
||||
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
|
||||
|
||||
static BaseObjectPtr<Endpoint> Create(Environment* env,
|
||||
const Endpoint::Options& config);
|
||||
|
||||
Endpoint(Environment* env,
|
||||
v8::Local<v8::Object> object,
|
||||
const Endpoint::Options& options);
|
||||
~Endpoint() override;
|
||||
|
||||
inline const Options& options() const {
|
||||
return options_;
|
||||
@ -289,6 +292,7 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
|
||||
|
||||
bool is_bound() const;
|
||||
bool is_closed() const;
|
||||
bool is_closed_or_closing() const;
|
||||
operator bool() const;
|
||||
|
||||
void Ref();
|
||||
@ -301,11 +305,10 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
|
||||
private:
|
||||
class Impl;
|
||||
|
||||
static void CleanupHook(void* data);
|
||||
|
||||
BaseObjectPtr<Impl> impl_;
|
||||
BaseObjectWeakPtr<Impl> impl_;
|
||||
bool is_bound_ = false;
|
||||
bool is_started_ = false;
|
||||
bool is_closed_ = false;
|
||||
};
|
||||
|
||||
bool is_closed() const;
|
||||
@ -349,10 +352,9 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
|
||||
|
||||
// JavaScript API
|
||||
|
||||
// Create a new Endpoint instance. `createEndpoint()` is exposed as a method
|
||||
// on the internalBinding('quic') object.
|
||||
// Create a new Endpoint.
|
||||
// @param Endpoint::Options options - Options to configure the Endpoint.
|
||||
static void CreateEndpoint(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
// Methods on the Endpoint instance:
|
||||
|
||||
@ -373,6 +375,7 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
|
||||
// packets.
|
||||
// @param bool on - If true, mark the Endpoint as busy.
|
||||
static void MarkBusy(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void FastMarkBusy(v8::Local<v8::Object> receiver, bool on);
|
||||
|
||||
// DoCloseGracefully is the signal that endpoint should close. Any packets
|
||||
// that are already in the queue or in flight will be allowed to finish, but
|
||||
@ -387,9 +390,7 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
|
||||
|
||||
// Ref() causes a listening Endpoint to keep the event loop active.
|
||||
static void Ref(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
// Unref() allows the event loop to close even if the Endpoint is listening.
|
||||
static void Unref(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void FastRef(v8::Local<v8::Object> receiver, bool on);
|
||||
|
||||
void Receive(const uv_buf_t& buf, const SocketAddress& from);
|
||||
|
||||
|
@ -156,13 +156,17 @@ void PreferredAddress::Set(ngtcp2_transport_params* params,
|
||||
|
||||
Maybe<PreferredAddress::Policy> PreferredAddress::tryGetPolicy(
|
||||
Environment* env, Local<Value> value) {
|
||||
if (value->IsNumber()) {
|
||||
if (value->IsUndefined()) {
|
||||
return Just(PreferredAddress::Policy::USE_PREFERRED_ADDRESS);
|
||||
}
|
||||
if (value->IsUint32()) {
|
||||
auto val = value.As<Uint32>()->Value();
|
||||
if (val == static_cast<uint32_t>(Policy::IGNORE_PREFERRED_ADDRESS))
|
||||
return Just(Policy::IGNORE_PREFERRED_ADDRESS);
|
||||
if (val == static_cast<uint32_t>(Policy::USE_PREFERRED_ADDRESS))
|
||||
return Just(Policy::USE_PREFERRED_ADDRESS);
|
||||
}
|
||||
THROW_ERR_INVALID_ARG_VALUE(env, "invalid preferred address policy");
|
||||
return Nothing<PreferredAddress::Policy>();
|
||||
}
|
||||
|
||||
|
50
src/quic/quic.cc
Normal file
50
src/quic/quic.cc
Normal file
@ -0,0 +1,50 @@
|
||||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
|
||||
#include <base_object-inl.h>
|
||||
#include <env-inl.h>
|
||||
#include <memory_tracker-inl.h>
|
||||
#include <node_realm-inl.h>
|
||||
#include <node_sockaddr-inl.h>
|
||||
#include <v8.h>
|
||||
#include "bindingdata.h"
|
||||
#include "endpoint.h"
|
||||
#include "node_external_reference.h"
|
||||
|
||||
namespace node {
|
||||
|
||||
using v8::Context;
|
||||
using v8::Local;
|
||||
using v8::Object;
|
||||
using v8::ObjectTemplate;
|
||||
using v8::Value;
|
||||
|
||||
namespace quic {
|
||||
|
||||
void CreatePerIsolateProperties(IsolateData* isolate_data,
|
||||
Local<ObjectTemplate> target) {
|
||||
Endpoint::InitPerIsolate(isolate_data, target);
|
||||
}
|
||||
|
||||
void CreatePerContextProperties(Local<Object> target,
|
||||
Local<Value> unused,
|
||||
Local<Context> context,
|
||||
void* priv) {
|
||||
Realm* realm = Realm::GetCurrent(context);
|
||||
BindingData::InitPerContext(realm, target);
|
||||
Endpoint::InitPerContext(realm, target);
|
||||
}
|
||||
|
||||
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||
BindingData::RegisterExternalReferences(registry);
|
||||
Endpoint::RegisterExternalReferences(registry);
|
||||
}
|
||||
|
||||
} // namespace quic
|
||||
} // namespace node
|
||||
|
||||
NODE_BINDING_CONTEXT_AWARE_INTERNAL(quic,
|
||||
node::quic::CreatePerContextProperties)
|
||||
NODE_BINDING_PER_ISOLATE_INIT(quic, node::quic::CreatePerIsolateProperties)
|
||||
NODE_BINDING_EXTERNAL_REFERENCE(quic, node::quic::RegisterExternalReferences)
|
||||
|
||||
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
@ -211,31 +211,19 @@ void ngtcp2_debug_log(void* user_data, const char* fmt, ...) {
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
template <typename Opt, uint32_t Opt::*member>
|
||||
bool SetOption(Environment* env,
|
||||
Opt* options,
|
||||
const v8::Local<Object>& object,
|
||||
const v8::Local<String>& name) {
|
||||
Local<Value> value;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
|
||||
if (!value->IsUndefined()) {
|
||||
DCHECK(value->IsNumber());
|
||||
options->*member = value.As<Uint32>()->Value();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Opt, PreferredAddress::Policy Opt::*member>
|
||||
bool SetOption(Environment* env,
|
||||
Opt* options,
|
||||
const v8::Local<Object>& object,
|
||||
const v8::Local<String>& name) {
|
||||
Local<Value> value;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
|
||||
// If the policy specified is invalid, we will just ignore it.
|
||||
auto maybePolicy = PreferredAddress::tryGetPolicy(env, value);
|
||||
if (!maybePolicy.IsJust()) return false;
|
||||
options->*member = maybePolicy.FromJust();
|
||||
PreferredAddress::Policy policy =
|
||||
PreferredAddress::Policy::USE_PREFERRED_ADDRESS;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value) ||
|
||||
!PreferredAddress::tryGetPolicy(env, value).To(&policy)) {
|
||||
return false;
|
||||
}
|
||||
options->*member = policy;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -245,10 +233,12 @@ bool SetOption(Environment* env,
|
||||
const v8::Local<Object>& object,
|
||||
const v8::Local<String>& name) {
|
||||
Local<Value> value;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
|
||||
auto maybeOptions = TLSContext::Options::From(env, value);
|
||||
if (!maybeOptions.IsJust()) return false;
|
||||
options->*member = maybeOptions.FromJust();
|
||||
TLSContext::Options opts;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value) ||
|
||||
!TLSContext::Options::From(env, value).To(&opts)) {
|
||||
return false;
|
||||
}
|
||||
options->*member = opts;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -258,10 +248,12 @@ bool SetOption(Environment* env,
|
||||
const v8::Local<Object>& object,
|
||||
const v8::Local<String>& name) {
|
||||
Local<Value> value;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
|
||||
auto maybeOptions = Session::Application_Options::From(env, value);
|
||||
if (!maybeOptions.IsJust()) return false;
|
||||
options->*member = maybeOptions.FromJust();
|
||||
Session::Application_Options opts;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value) ||
|
||||
!Session::Application_Options::From(env, value).To(&opts)) {
|
||||
return false;
|
||||
}
|
||||
options->*member = opts;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -271,10 +263,12 @@ bool SetOption(Environment* env,
|
||||
const v8::Local<Object>& object,
|
||||
const v8::Local<String>& name) {
|
||||
Local<Value> value;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
|
||||
auto maybeOptions = TransportParams::Options::From(env, value);
|
||||
if (!maybeOptions.IsJust()) return false;
|
||||
options->*member = maybeOptions.FromJust();
|
||||
TransportParams::Options opts;
|
||||
if (!object->Get(env->context(), name).ToLocal(&value) ||
|
||||
!TransportParams::Options::From(env, value).To(&opts)) {
|
||||
return false;
|
||||
}
|
||||
options->*member = opts;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
|
||||
|
@ -277,6 +277,7 @@ bool SetOption(Environment* env,
|
||||
ASSIGN_OR_RETURN_UNWRAP(&handle, item, false);
|
||||
(options->*member).push_back(handle->Data());
|
||||
} else {
|
||||
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
|
||||
return false;
|
||||
}
|
||||
} else if constexpr (std::is_same<T, Store>::value) {
|
||||
@ -285,6 +286,7 @@ bool SetOption(Environment* env,
|
||||
} else if (item->IsArrayBuffer()) {
|
||||
(options->*member).emplace_back(item.As<v8::ArrayBuffer>());
|
||||
} else {
|
||||
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -297,6 +299,7 @@ bool SetOption(Environment* env,
|
||||
ASSIGN_OR_RETURN_UNWRAP(&handle, value, false);
|
||||
(options->*member).push_back(handle->Data());
|
||||
} else {
|
||||
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
|
||||
return false;
|
||||
}
|
||||
} else if constexpr (std::is_same<T, Store>::value) {
|
||||
@ -305,6 +308,7 @@ bool SetOption(Environment* env,
|
||||
} else if (value->IsArrayBuffer()) {
|
||||
(options->*member).emplace_back(value.As<v8::ArrayBuffer>());
|
||||
} else {
|
||||
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -545,16 +549,25 @@ ngtcp2_conn* TLSContext::getConnection(ngtcp2_crypto_conn_ref* ref) {
|
||||
return *context->session_;
|
||||
}
|
||||
|
||||
Maybe<const TLSContext::Options> TLSContext::Options::From(Environment* env,
|
||||
Local<Value> value) {
|
||||
if (value.IsEmpty() || !value->IsObject()) {
|
||||
Maybe<TLSContext::Options> TLSContext::Options::From(Environment* env,
|
||||
Local<Value> value) {
|
||||
if (value.IsEmpty()) {
|
||||
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
|
||||
return Nothing<const Options>();
|
||||
return Nothing<Options>();
|
||||
}
|
||||
|
||||
auto& state = BindingData::Get(env);
|
||||
auto params = value.As<Object>();
|
||||
Options options;
|
||||
auto& state = BindingData::Get(env);
|
||||
|
||||
if (value->IsUndefined()) {
|
||||
return Just<Options>(options);
|
||||
}
|
||||
if (!value->IsObject()) {
|
||||
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
|
||||
return Nothing<Options>();
|
||||
}
|
||||
|
||||
auto params = value.As<Object>();
|
||||
|
||||
#define SET_VECTOR(Type, name) \
|
||||
SetOption<Type, TLSContext::Options, &TLSContext::Options::name>( \
|
||||
@ -571,10 +584,10 @@ Maybe<const TLSContext::Options> TLSContext::Options::From(Environment* env,
|
||||
!SET_VECTOR(std::shared_ptr<crypto::KeyObjectData>, keys) ||
|
||||
!SET_VECTOR(Store, certs) || !SET_VECTOR(Store, ca) ||
|
||||
!SET_VECTOR(Store, crl)) {
|
||||
return Nothing<const Options>();
|
||||
return Nothing<Options>();
|
||||
}
|
||||
|
||||
return Just<const Options>(options);
|
||||
return Just<Options>(options);
|
||||
}
|
||||
|
||||
} // namespace quic
|
||||
|
@ -91,8 +91,8 @@ class TLSContext final : public MemoryRetainer {
|
||||
|
||||
static const Options kDefault;
|
||||
|
||||
static v8::Maybe<const Options> From(Environment* env,
|
||||
v8::Local<v8::Value> value);
|
||||
static v8::Maybe<Options> From(Environment* env,
|
||||
v8::Local<v8::Value> value);
|
||||
};
|
||||
|
||||
static const Options kDefaultOptions;
|
||||
|
@ -15,24 +15,6 @@ namespace quic {
|
||||
// TokenSecret
|
||||
|
||||
TokenSecret::TokenSecret() : buf_() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
TokenSecret::TokenSecret(const uint8_t* secret) : buf_() {
|
||||
*this = secret;
|
||||
}
|
||||
|
||||
TokenSecret& TokenSecret::operator=(const uint8_t* other) {
|
||||
CHECK_NOT_NULL(other);
|
||||
memcpy(buf_, other, QUIC_TOKENSECRET_LEN);
|
||||
return *this;
|
||||
}
|
||||
|
||||
TokenSecret::operator const uint8_t*() const {
|
||||
return buf_;
|
||||
}
|
||||
|
||||
void TokenSecret::Reset() {
|
||||
// As a performance optimization later, we could consider creating an entropy
|
||||
// cache here similar to what we use for random CIDs so that we do not have
|
||||
// to engage CSPRNG on every call. That, however, is suboptimal for secrets.
|
||||
@ -42,6 +24,25 @@ void TokenSecret::Reset() {
|
||||
CHECK(crypto::CSPRNG(buf_, QUIC_TOKENSECRET_LEN).is_ok());
|
||||
}
|
||||
|
||||
TokenSecret::TokenSecret(const uint8_t* secret) : buf_() {
|
||||
CHECK_NOT_NULL(secret);
|
||||
memcpy(buf_, secret, QUIC_TOKENSECRET_LEN);
|
||||
}
|
||||
|
||||
TokenSecret::~TokenSecret() {
|
||||
memset(buf_, 0, QUIC_TOKENSECRET_LEN);
|
||||
}
|
||||
|
||||
TokenSecret::operator const uint8_t*() const {
|
||||
return buf_;
|
||||
}
|
||||
|
||||
uint8_t TokenSecret::operator[](int pos) const {
|
||||
CHECK_GE(pos, 0);
|
||||
CHECK_LT(pos, QUIC_TOKENSECRET_LEN);
|
||||
return buf_[pos];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// StatelessResetToken
|
||||
|
||||
|
@ -31,17 +31,15 @@ class TokenSecret final : public MemoryRetainer {
|
||||
// the length is not verified so care must be taken
|
||||
// when this constructor is used.
|
||||
explicit TokenSecret(const uint8_t* secret);
|
||||
~TokenSecret();
|
||||
|
||||
TokenSecret(const TokenSecret& other) = default;
|
||||
TokenSecret& operator=(const TokenSecret& other) = default;
|
||||
TokenSecret& operator=(const uint8_t* other);
|
||||
|
||||
TokenSecret& operator=(TokenSecret&& other) = delete;
|
||||
TokenSecret(const TokenSecret&) = default;
|
||||
TokenSecret& operator=(const TokenSecret&) = default;
|
||||
TokenSecret(TokenSecret&&) = delete;
|
||||
TokenSecret& operator=(TokenSecret&&) = delete;
|
||||
|
||||
operator const uint8_t*() const;
|
||||
|
||||
// Resets the secret to a random value.
|
||||
void Reset();
|
||||
uint8_t operator[](int pos) const;
|
||||
|
||||
SET_NO_MEMORY_INFO()
|
||||
SET_MEMORY_INFO_NAME(TokenSecret)
|
||||
|
@ -31,15 +31,26 @@ TransportParams::Config::Config(Side side,
|
||||
const CID& retry_scid)
|
||||
: side(side), ocid(ocid), retry_scid(retry_scid) {}
|
||||
|
||||
Maybe<const TransportParams::Options> TransportParams::Options::From(
|
||||
Maybe<TransportParams::Options> TransportParams::Options::From(
|
||||
Environment* env, Local<Value> value) {
|
||||
if (value.IsEmpty() || !value->IsObject()) {
|
||||
return Nothing<const Options>();
|
||||
if (value.IsEmpty()) {
|
||||
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
|
||||
return Nothing<Options>();
|
||||
}
|
||||
|
||||
auto& state = BindingData::Get(env);
|
||||
auto params = value.As<Object>();
|
||||
Options options;
|
||||
auto& state = BindingData::Get(env);
|
||||
|
||||
if (value->IsUndefined()) {
|
||||
return Just<Options>(options);
|
||||
}
|
||||
|
||||
if (!value->IsObject()) {
|
||||
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object");
|
||||
return Nothing<Options>();
|
||||
}
|
||||
|
||||
auto params = value.As<Object>();
|
||||
|
||||
#define SET(name) \
|
||||
SetOption<TransportParams::Options, &TransportParams::Options::name>( \
|
||||
@ -52,12 +63,12 @@ Maybe<const TransportParams::Options> TransportParams::Options::From(
|
||||
!SET(max_idle_timeout) || !SET(active_connection_id_limit) ||
|
||||
!SET(ack_delay_exponent) || !SET(max_ack_delay) ||
|
||||
!SET(max_datagram_frame_size) || !SET(disable_active_migration)) {
|
||||
return Nothing<const Options>();
|
||||
return Nothing<Options>();
|
||||
}
|
||||
|
||||
#undef SET
|
||||
|
||||
return Just<const Options>(options);
|
||||
return Just<Options>(options);
|
||||
}
|
||||
|
||||
void TransportParams::Options::MemoryInfo(MemoryTracker* tracker) const {
|
||||
|
@ -116,8 +116,8 @@ class TransportParams final {
|
||||
SET_MEMORY_INFO_NAME(TransportParams::Options)
|
||||
SET_SELF_SIZE(Options)
|
||||
|
||||
static v8::Maybe<const Options> From(Environment* env,
|
||||
v8::Local<v8::Value> value);
|
||||
static v8::Maybe<Options> From(Environment* env,
|
||||
v8::Local<v8::Value> value);
|
||||
};
|
||||
|
||||
explicit TransportParams(Type type);
|
||||
|
@ -14,6 +14,21 @@ using node::quic::RetryToken;
|
||||
using node::quic::StatelessResetToken;
|
||||
using node::quic::TokenSecret;
|
||||
|
||||
TEST(TokenScret, Basics) {
|
||||
uint8_t secret[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6};
|
||||
TokenSecret fixed_secret(secret);
|
||||
|
||||
for (int n = 0; n < TokenSecret::QUIC_TOKENSECRET_LEN; n++) {
|
||||
CHECK_EQ(fixed_secret[n], secret[n]);
|
||||
}
|
||||
|
||||
// Copy assignment works
|
||||
TokenSecret other = fixed_secret;
|
||||
for (int n = 0; n < TokenSecret::QUIC_TOKENSECRET_LEN; n++) {
|
||||
CHECK_EQ(other[n], secret[n]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StatelessResetToken, Basic) {
|
||||
ngtcp2_cid cid_;
|
||||
uint8_t secret[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6};
|
||||
|
76
test/parallel/test-quic-internal-endpoint-listen-defaults.js
Normal file
76
test/parallel/test-quic-internal-endpoint-listen-defaults.js
Normal file
@ -0,0 +1,76 @@
|
||||
// Flags: --expose-internals
|
||||
'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 {
|
||||
SocketAddress: _SocketAddress,
|
||||
AF_INET,
|
||||
} = internalBinding('block_list');
|
||||
const quic = internalBinding('quic');
|
||||
|
||||
quic.setCallbacks({
|
||||
onEndpointClose: common.mustCall((...args) => {
|
||||
deepStrictEqual(args, [0, 0]);
|
||||
}),
|
||||
|
||||
// The following are unused in this test
|
||||
onSessionNew() {},
|
||||
onSessionClose() {},
|
||||
onSessionDatagram() {},
|
||||
onSessionDatagramStatus() {},
|
||||
onSessionHandshake() {},
|
||||
onSessionPathValidation() {},
|
||||
onSessionTicket() {},
|
||||
onSessionVersionNegotiation() {},
|
||||
onStreamCreated() {},
|
||||
onStreamBlocked() {},
|
||||
onStreamClose() {},
|
||||
onStreamReset() {},
|
||||
onStreamHeaders() {},
|
||||
onStreamTrailers() {},
|
||||
});
|
||||
|
||||
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);
|
215
test/parallel/test-quic-internal-endpoint-options.js
Normal file
215
test/parallel/test-quic-internal-endpoint-options.js
Normal file
@ -0,0 +1,215 @@
|
||||
// Flags: --expose-internals
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasQuic)
|
||||
common.skip('missing quic');
|
||||
const {
|
||||
throws,
|
||||
} = require('node:assert');
|
||||
|
||||
const { internalBinding } = require('internal/test/binding');
|
||||
const quic = internalBinding('quic');
|
||||
|
||||
quic.setCallbacks({
|
||||
onEndpointClose() {},
|
||||
onSessionNew() {},
|
||||
onSessionClose() {},
|
||||
onSessionDatagram() {},
|
||||
onSessionDatagramStatus() {},
|
||||
onSessionHandshake() {},
|
||||
onSessionPathValidation() {},
|
||||
onSessionTicket() {},
|
||||
onSessionVersionNegotiation() {},
|
||||
onStreamCreated() {},
|
||||
onStreamBlocked() {},
|
||||
onStreamClose() {},
|
||||
onStreamReset() {},
|
||||
onStreamHeaders() {},
|
||||
onStreamTrailers() {},
|
||||
});
|
||||
|
||||
throws(() => new quic.Endpoint(), {
|
||||
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'
|
||||
});
|
||||
|
||||
{
|
||||
// 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.QUIC_CC_ALGO_RENO,
|
||||
quic.QUIC_CC_ALGO_CUBIC,
|
||||
quic.QUIC_CC_ALGO_BBR,
|
||||
quic.QUIC_CC_ALGO_BBR2,
|
||||
quic.QUIC_CC_ALGO_RENO_STR,
|
||||
quic.QUIC_CC_ALGO_CUBIC_STR,
|
||||
quic.QUIC_CC_ALGO_BBR_STR,
|
||||
quic.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',
|
||||
});
|
||||
}
|
||||
}
|
79
test/parallel/test-quic-internal-endpoint-stats-state.js
Normal file
79
test/parallel/test-quic-internal-endpoint-stats-state.js
Normal file
@ -0,0 +1,79 @@
|
||||
// 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 {
|
||||
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;
|
||||
|
||||
const endpoint = new quic.Endpoint({});
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
endpoint.markBusy(true);
|
||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_BUSY), 1);
|
||||
endpoint.markBusy(false);
|
||||
strictEqual(state.getUint8(IDX_STATE_ENDPOINT_BUSY), 0);
|
||||
|
||||
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);
|
38
test/parallel/test-quic-internal-setcallbacks.js
Normal file
38
test/parallel/test-quic-internal-setcallbacks.js
Normal file
@ -0,0 +1,38 @@
|
||||
// Flags: --expose-internals
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasQuic)
|
||||
common.skip('missing quic');
|
||||
const { internalBinding } = require('internal/test/binding');
|
||||
const quic = internalBinding('quic');
|
||||
|
||||
const { throws } = require('assert');
|
||||
|
||||
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',
|
||||
});
|
||||
}
|
||||
// If all callbacks are present it should work
|
||||
quic.setCallbacks(callbacks);
|
100
typings/internalBinding/quic.d.ts
vendored
Normal file
100
typings/internalBinding/quic.d.ts
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
interface QuicCallbacks {
|
||||
onEndpointClose: (context: number, status: number) => void;
|
||||
onSessionNew: (session: Session) => void;
|
||||
onSessionClose: (type: number, code: bigint, reason?: string) => void;
|
||||
onSessionDatagram: (datagram: Uint8Array, early: boolean) => void;);
|
||||
onSessionDatagramStatus: (id: bigint, status: string) => void;
|
||||
onSessionHandshake: (sni: string,
|
||||
alpn: string,
|
||||
cipher: string,
|
||||
cipherVersion: string,
|
||||
validationReason?: string,
|
||||
validationCode?: string) => void;
|
||||
onSessionPathValidation: (result: string,
|
||||
local: SocketAddress,
|
||||
remote: SocketAddress,
|
||||
preferred: boolean) => void;
|
||||
onSessionTicket: (ticket: ArrayBuffer) => void;
|
||||
onSessionVersionNegotiation: (version: number,
|
||||
versions: number[],
|
||||
supports: number[]) => void;
|
||||
onStreamCreated: (stream: Stream) => void;
|
||||
onStreamBlocked: () => void;
|
||||
onStreamClose: (error: [number,bigint,string]) => void;
|
||||
onStreamReset: (error: [number,bigint,string]) => void;
|
||||
onStreamHeaders: (headers: string[], kind: number) => void;
|
||||
onStreamTrailers: () => void;
|
||||
}
|
||||
|
||||
interface EndpointOptions {
|
||||
address?: SocketAddress;
|
||||
retryTokenExpiration?: number|bigint;
|
||||
tokenExpiration?: number|bigint;
|
||||
maxConnectionsPerHost?: number|bigint;
|
||||
maxConnectionsTotal?: number|bigint;
|
||||
maxStatelessResetsPerHost?: number|bigint;
|
||||
addressLRUSize?: number|bigint;
|
||||
maxRetries?: number|bigint;
|
||||
maxPayloadSize?: number|bigint;
|
||||
unacknowledgedPacketThreshold?: number|bigint;
|
||||
validateAddress?: boolean;
|
||||
disableStatelessReset?: boolean;
|
||||
ipv6Only?: boolean;
|
||||
udpReceiveBufferSize?: number;
|
||||
udpSendBufferSize?: number;
|
||||
udpTTL?: number;
|
||||
resetTokenSecret?: ArrayBufferView;
|
||||
tokenSecret?: ArrayBufferView;
|
||||
cc?: 'reno'|'cubic'|'pcc'|'bbr'| 0 | 2 | 3 | 4;
|
||||
}
|
||||
|
||||
interface SessionOptions {}
|
||||
interface SocketAddress {}
|
||||
|
||||
interface Session {}
|
||||
interface Stream {}
|
||||
|
||||
interface Endpoint {
|
||||
listen(options: SessionOptions): void;
|
||||
connect(address: SocketAddress, options: SessionOptions): Session;
|
||||
closeGracefully(): void;
|
||||
markBusy(on?: boolean): void;
|
||||
ref(on?: boolean): void;
|
||||
address(): SocketAddress|void;
|
||||
readonly state: ArrayBuffer;
|
||||
readonly stats: ArrayBuffer;
|
||||
}
|
||||
|
||||
export interface QuicBinding {
|
||||
setCallbacks(callbacks: QuicCallbacks): void;
|
||||
flushPacketFreeList(): void;
|
||||
|
||||
readonly IDX_STATS_ENDPOINT_CREATED_AT: number;
|
||||
readonly IDX_STATS_ENDPOINT_DESTROYED_AT: number;
|
||||
readonly IDX_STATS_ENDPOINT_BYTES_RECEIVED: number;
|
||||
readonly IDX_STATS_ENDPOINT_BYTES_SENT: number;
|
||||
readonly IDX_STATS_ENDPOINT_PACKETS_RECEIVED: number;
|
||||
readonly IDX_STATS_ENDPOINT_PACKETS_SENT: number;
|
||||
readonly IDX_STATS_ENDPOINT_SERVER_SESSIONS: number;
|
||||
readonly IDX_STATS_ENDPOINT_CLIENT_SESSIONS: number;
|
||||
readonly IDX_STATS_ENDPOINT_SERVER_BUSY_COUNT: number;
|
||||
readonly IDX_STATS_ENDPOINT_RETRY_COUNT: number;
|
||||
readonly IDX_STATS_ENDPOINT_VERSION_NEGOTIATION_COUNT: number;
|
||||
readonly IDX_STATS_ENDPOINT_STATELESS_RESET_COUNT: number;
|
||||
readonly IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT: number;
|
||||
readonly IDX_STATS_ENDPOINT_COUNT: number;
|
||||
readonly IDX_STATE_ENDPOINT_BOUND: number;
|
||||
readonly IDX_STATE_ENDPOINT_BOUND_SIZE: number;
|
||||
readonly IDX_STATE_ENDPOINT_RECEIVING: number;
|
||||
readonly IDX_STATE_ENDPOINT_RECEIVING_SIZE: number;
|
||||
readonly IDX_STATE_ENDPOINT_LISTENING: number;
|
||||
readonly IDX_STATE_ENDPOINT_LISTENING_SIZE: number;
|
||||
readonly IDX_STATE_ENDPOINT_CLOSING: number;
|
||||
readonly IDX_STATE_ENDPOINT_CLOSING_SIZE: number;
|
||||
readonly IDX_STATE_ENDPOINT_WAITING_FOR_CALLBACKS: number;
|
||||
readonly IDX_STATE_ENDPOINT_WAITING_FOR_CALLBACKS_SIZE: number;
|
||||
readonly IDX_STATE_ENDPOINT_BUSY: number;
|
||||
readonly IDX_STATE_ENDPOINT_BUSY_SIZE: number;
|
||||
readonly IDX_STATE_ENDPOINT_PENDING_CALLBACKS: number;
|
||||
readonly IDX_STATE_ENDPOINT_PENDING_CALLBACKS_SIZE: number;
|
||||
}
|
Loading…
Reference in New Issue
Block a user