Skip to content

Commit

Permalink
quic: add more QUIC implementation
Browse files Browse the repository at this point in the history
* add TLSContext
* quic: add stat collection utilities
* add Packet
* add NgTcp2CallbackScope/NgHttp3CallbackScope

PR-URL: #47494
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
  • Loading branch information
jasnell authored and MoLow committed Jul 6, 2023
1 parent e96451e commit 75d7024
Show file tree
Hide file tree
Showing 10 changed files with 1,480 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/async_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ namespace node {
V(PROMISE) \
V(QUERYWRAP) \
V(QUIC_LOGSTREAM) \
V(QUIC_PACKET) \
V(SHUTDOWNWRAP) \
V(SIGNALWRAP) \
V(STATWATCHER) \
Expand Down
2 changes: 2 additions & 0 deletions src/node_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ void OOMErrorHandler(const char* location, bool is_heap_oom);
V(ERR_DLOPEN_FAILED, Error) \
V(ERR_ENCODING_INVALID_ENCODED_DATA, TypeError) \
V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, Error) \
V(ERR_ILLEGAL_CONSTRUCTOR, Error) \
V(ERR_INVALID_ADDRESS, Error) \
V(ERR_INVALID_ARG_VALUE, TypeError) \
V(ERR_OSSL_EVP_INVALID_DIGEST, Error) \
Expand Down Expand Up @@ -154,6 +155,7 @@ ERRORS_WITH_CODE(V)
V(ERR_DLOPEN_FAILED, "DLOpen failed") \
V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, \
"Context not associated with Node.js environment") \
V(ERR_ILLEGAL_CONSTRUCTOR, "Illegal constructor") \
V(ERR_INVALID_ADDRESS, "Invalid socket address") \
V(ERR_INVALID_MODULE, "No such module") \
V(ERR_INVALID_THIS, "Value of \"this\" is the wrong type") \
Expand Down
46 changes: 45 additions & 1 deletion src/quic/bindingdata.cc
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,15 @@ void BindingData::DecreaseAllocatedSize(size_t 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>(env->context(), target);
}

void BindingData::RegisterExternalReferences(
ExternalReferenceRegistry* registry) {
registry->Register(SetCallbacks);
registry->Register(FlushPacketFreelist);
}

BindingData::BindingData(Realm* realm, Local<Object> object)
Expand Down Expand Up @@ -140,7 +142,7 @@ QUIC_JS_CALLBACKS(V)
void BindingData::SetCallbacks(const FunctionCallbackInfo<Value>& args) {
auto env = Environment::GetCurrent(args);
auto isolate = env->isolate();
BindingData& state = BindingData::Get(env);
auto& state = BindingData::Get(env);
CHECK(args[0]->IsObject());
Local<Object> obj = args[0].As<Object>();

Expand All @@ -159,6 +161,48 @@ void BindingData::SetCallbacks(const FunctionCallbackInfo<Value>& args) {
#undef V
}

void BindingData::FlushPacketFreelist(const FunctionCallbackInfo<Value>& args) {
auto env = Environment::GetCurrent(args);
auto& state = BindingData::Get(env);
state.packet_freelist.clear();
}

NgTcp2CallbackScope::NgTcp2CallbackScope(Environment* env) : env(env) {
auto& binding = BindingData::Get(env);
CHECK(!binding.in_ngtcp2_callback_scope);
binding.in_ngtcp2_callback_scope = true;
}

NgTcp2CallbackScope::~NgTcp2CallbackScope() {
auto& binding = BindingData::Get(env);
binding.in_ngtcp2_callback_scope = false;
}

bool NgTcp2CallbackScope::in_ngtcp2_callback(Environment* env) {
auto& binding = BindingData::Get(env);
return binding.in_ngtcp2_callback_scope;
}

NgHttp3CallbackScope::NgHttp3CallbackScope(Environment* env) : env(env) {
auto& binding = BindingData::Get(env);
CHECK(!binding.in_nghttp3_callback_scope);
binding.in_nghttp3_callback_scope = true;
}

NgHttp3CallbackScope::~NgHttp3CallbackScope() {
auto& binding = BindingData::Get(env);
binding.in_nghttp3_callback_scope = false;
}

bool NgHttp3CallbackScope::in_nghttp3_callback(Environment* env) {
auto& binding = BindingData::Get(env);
return binding.in_nghttp3_callback_scope;
}

void IllegalConstructor(const FunctionCallbackInfo<Value>& args) {
THROW_ERR_ILLEGAL_CONSTRUCTOR(Environment::GetCurrent(args));
}

} // namespace quic
} // namespace node

Expand Down
51 changes: 44 additions & 7 deletions src/quic/bindingdata.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
#include <node.h>
#include <node_mem.h>
#include <v8.h>
#include <vector>

namespace node {
namespace quic {

class Endpoint;
class Packet;

enum class Side {
CLIENT = NGTCP2_CRYPTO_SIDE_CLIENT,
Expand Down Expand Up @@ -64,23 +66,37 @@ constexpr size_t kDefaultMaxPacketLength = NGTCP2_MAX_UDP_PAYLOAD_SIZE;
#define QUIC_STRINGS(V) \
V(ack_delay_exponent, "ackDelayExponent") \
V(active_connection_id_limit, "activeConnectionIDLimit") \
V(alpn, "alpn") \
V(ca, "ca") \
V(certs, "certs") \
V(crl, "crl") \
V(ciphers, "ciphers") \
V(disable_active_migration, "disableActiveMigration") \
V(enable_tls_trace, "tlsTrace") \
V(endpoint, "Endpoint") \
V(endpoint_udp, "Endpoint::UDP") \
V(groups, "groups") \
V(hostname, "hostname") \
V(http3_alpn, &NGHTTP3_ALPN_H3[1]) \
V(initial_max_data, "initialMaxData") \
V(initial_max_stream_data_bidi_local, "initialMaxStreamDataBidiLocal") \
V(initial_max_stream_data_bidi_remote, "initialMaxStreamDataBidiRemote") \
V(initial_max_stream_data_uni, "initialMaxStreamDataUni") \
V(initial_max_streams_bidi, "initialMaxStreamsBidi") \
V(initial_max_streams_uni, "initialMaxStreamsUni") \
V(keylog, "keylog") \
V(keys, "keys") \
V(logstream, "LogStream") \
V(max_ack_delay, "maxAckDelay") \
V(max_datagram_frame_size, "maxDatagramFrameSize") \
V(max_idle_timeout, "maxIdleTimeout") \
V(packetwrap, "PacketWrap") \
V(reject_unauthorized, "rejectUnauthorized") \
V(request_peer_certificate, "requestPeerCertificate") \
V(session, "Session") \
V(stream, "Stream")
V(session_id_ctx, "sessionIDContext") \
V(stream, "Stream") \
V(verify_hostname_identity, "verifyHostnameIdentity")

// =============================================================================
// The BindingState object holds state for the internalBinding('quic') binding
Expand Down Expand Up @@ -115,12 +131,14 @@ class BindingData final
// bridge out to the JS API.
static void SetCallbacks(const v8::FunctionCallbackInfo<v8::Value>& args);

// TODO(@jasnell) This will be added when Endpoint is implemented.
// // A set of listening Endpoints. We maintain this to ensure that the
// Endpoint
// // cannot be gc'd while it is still listening and there are active
// // connections.
// std::unordered_map<Endpoint*, BaseObjectPtr<Endpoint>> listening_endpoints;
std::vector<BaseObjectPtr<BaseObject>> packet_freelist;

// Purge the packet free list to free up memory.
static void FlushPacketFreelist(
const v8::FunctionCallbackInfo<v8::Value>& args);

bool in_ngtcp2_callback_scope = false;
bool in_nghttp3_callback_scope = false;

// The following set up various storage and accessors for common strings,
// construction templates, and callbacks stored on the BindingData. These
Expand Down Expand Up @@ -166,6 +184,25 @@ class BindingData final
#undef V
};

void IllegalConstructor(const v8::FunctionCallbackInfo<v8::Value>& args);

// The ngtcp2 and nghttp3 callbacks have certain restrictions
// that forbid re-entry. We provide the following scopes for
// use in those to help protect against it.
struct NgTcp2CallbackScope {
Environment* env;
explicit NgTcp2CallbackScope(Environment* env);
~NgTcp2CallbackScope();
static bool in_ngtcp2_callback(Environment* env);
};

struct NgHttp3CallbackScope {
Environment* env;
explicit NgHttp3CallbackScope(Environment* env);
~NgHttp3CallbackScope();
static bool in_nghttp3_callback(Environment* env);
};

} // namespace quic
} // namespace node

Expand Down
48 changes: 48 additions & 0 deletions src/quic/defs.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
#pragma once

#include <aliased_struct.h>
#include <env.h>
#include <node_errors.h>
#include <uv.h>
#include <v8.h>

namespace node {
namespace quic {

template <typename Opt, std::string 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()) {
Utf8Value utf8(env->isolate(), value);
options->*member = *utf8;
}
return true;
}

template <typename Opt, bool Opt::*member>
bool SetOption(Environment* env,
Opt* options,
Expand Down Expand Up @@ -50,5 +66,37 @@ bool SetOption(Environment* env,
return true;
}

// Utilities used to update the stats for Endpoint, Session, and Stream
// objects. The stats themselves are maintained in an AliasedStruct within
// each of the relevant classes.

template <typename Stats, uint64_t Stats::*member>
void IncrementStat(Stats* stats, uint64_t amt = 1) {
stats->*member += amt;
}

template <typename Stats, uint64_t Stats::*member>
void RecordTimestampStat(Stats* stats) {
stats->*member = uv_hrtime();
}

template <typename Stats, uint64_t Stats::*member>
void SetStat(Stats* stats, uint64_t val) {
stats->*member = val;
}

template <typename Stats, uint64_t Stats::*member>
uint64_t GetStat(Stats* stats) {
return stats->*member;
}

#define STAT_INCREMENT(Type, name) IncrementStat<Type, &Type::name>(&stats_);
#define STAT_INCREMENT_N(Type, name, amt) \
IncrementStat<Type, &Type::name>(&stats_, amt);
#define STAT_RECORD_TIMESTAMP(Type, name) \
RecordTimestampStat<Type, &Type::name>(&stats_);
#define STAT_SET(Type, name, val) SetStat<Type, &Type::name>(&stats_, val);
#define STAT_GET(Type, name) GetStat<Type, &Type::name>(&stats_);

} // namespace quic
} // namespace node

0 comments on commit 75d7024

Please sign in to comment.