Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJames M Snell <jasnell@gmail.com>2021-01-25 21:49:03 +0300
committerMichaël Zasso <targos@protonmail.com>2021-02-02 12:50:12 +0300
commit11dd2672cdc8fe7ba2968401de3c1c212596c4cc (patch)
tree858ed45bb0a6a1db81d20e3772f593e77643adf2 /src
parentf74b3765965614124c4df860adc008e0d9a92479 (diff)
quic: remove quic
PR-URL: https://github.com/nodejs/node/pull/37067 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/async_wrap.h6
-rw-r--r--src/env.h43
-rw-r--r--src/node_binding.cc7
-rw-r--r--src/node_errors.h6
-rw-r--r--src/node_http_common.h2
-rw-r--r--src/node_mem.h2
-rw-r--r--src/node_metadata.cc10
-rw-r--r--src/node_metadata.h7
-rw-r--r--src/node_native_module.cc4
-rw-r--r--src/quic/node_quic.cc271
-rw-r--r--src/quic/node_quic_buffer-inl.h98
-rw-r--r--src/quic/node_quic_buffer.cc166
-rw-r--r--src/quic/node_quic_buffer.h239
-rw-r--r--src/quic/node_quic_crypto.cc748
-rw-r--r--src/quic/node_quic_crypto.h144
-rw-r--r--src/quic/node_quic_default_application.cc177
-rw-r--r--src/quic/node_quic_default_application.h60
-rw-r--r--src/quic/node_quic_http3_application.cc941
-rw-r--r--src/quic/node_quic_http3_application.h331
-rw-r--r--src/quic/node_quic_session-inl.h447
-rw-r--r--src/quic/node_quic_session.cc3963
-rw-r--r--src/quic/node_quic_session.h1531
-rw-r--r--src/quic/node_quic_socket-inl.h192
-rw-r--r--src/quic/node_quic_socket.cc1202
-rw-r--r--src/quic/node_quic_socket.h609
-rw-r--r--src/quic/node_quic_state.h82
-rw-r--r--src/quic/node_quic_stream-inl.h180
-rw-r--r--src/quic/node_quic_stream.cc548
-rw-r--r--src/quic/node_quic_stream.h409
-rw-r--r--src/quic/node_quic_util-inl.h435
-rw-r--r--src/quic/node_quic_util.h394
31 files changed, 5 insertions, 13249 deletions
diff --git a/src/async_wrap.h b/src/async_wrap.h
index 76de08053b5..522779d57a0 100644
--- a/src/async_wrap.h
+++ b/src/async_wrap.h
@@ -60,12 +60,6 @@ namespace node {
V(PROCESSWRAP) \
V(PROMISE) \
V(QUERYWRAP) \
- V(QLOGSTREAM) \
- V(QUICCLIENTSESSION) \
- V(QUICSERVERSESSION) \
- V(QUICSENDWRAP) \
- V(QUICSOCKET) \
- V(QUICSTREAM) \
V(SHUTDOWNWRAP) \
V(SIGNALWRAP) \
V(STATWATCHER) \
diff --git a/src/env.h b/src/env.h
index cf93622ea89..38c087b650e 100644
--- a/src/env.h
+++ b/src/env.h
@@ -366,7 +366,6 @@ constexpr size_t kFsStatsBufferLength =
V(pubkey_string, "pubkey") \
V(public_exponent_string, "publicExponent") \
V(query_string, "query") \
- V(http3_alpn_string, "h3-29") \
V(rate_string, "rate") \
V(raw_string, "raw") \
V(read_host_object_string, "_readHostObject") \
@@ -435,16 +434,6 @@ constexpr size_t kFsStatsBufferLength =
V(x_forwarded_string, "x-forwarded-for") \
V(zero_return_string, "ZERO_RETURN")
-#if defined(NODE_EXPERIMENTAL_QUIC) && NODE_EXPERIMENTAL_QUIC
-# define QUIC_ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) \
- V(quicclientsession_instance_template, v8::ObjectTemplate) \
- V(quicserversession_instance_template, v8::ObjectTemplate) \
- V(quicserverstream_instance_template, v8::ObjectTemplate) \
- V(quicsocketsendwrap_instance_template, v8::ObjectTemplate)
-#else
-# define QUIC_ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V)
-#endif
-
#define ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) \
V(async_wrap_ctor_template, v8::FunctionTemplate) \
V(async_wrap_object_ctor_template, v8::FunctionTemplate) \
@@ -479,34 +468,7 @@ constexpr size_t kFsStatsBufferLength =
V(tty_constructor_template, v8::FunctionTemplate) \
V(write_wrap_template, v8::ObjectTemplate) \
V(worker_heap_snapshot_taker_template, v8::ObjectTemplate) \
- V(x509_constructor_template, v8::FunctionTemplate) \
- QUIC_ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V)
-
-#if defined(NODE_EXPERIMENTAL_QUIC) && NODE_EXPERIMENTAL_QUIC
-# define QUIC_ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) \
- V(quic_on_socket_close_function, v8::Function) \
- V(quic_on_socket_server_busy_function, v8::Function) \
- V(quic_on_session_cert_function, v8::Function) \
- V(quic_on_session_client_hello_function, v8::Function) \
- V(quic_on_session_close_function, v8::Function) \
- V(quic_on_session_handshake_function, v8::Function) \
- V(quic_on_session_keylog_function, v8::Function) \
- V(quic_on_session_path_validation_function, v8::Function) \
- V(quic_on_session_use_preferred_address_function, v8::Function) \
- V(quic_on_session_qlog_function, v8::Function) \
- V(quic_on_session_ready_function, v8::Function) \
- V(quic_on_session_status_function, v8::Function) \
- V(quic_on_session_ticket_function, v8::Function) \
- V(quic_on_session_version_negotiation_function, v8::Function) \
- V(quic_on_stream_close_function, v8::Function) \
- V(quic_on_stream_error_function, v8::Function) \
- V(quic_on_stream_ready_function, v8::Function) \
- V(quic_on_stream_reset_function, v8::Function) \
- V(quic_on_stream_headers_function, v8::Function) \
- V(quic_on_stream_blocked_function, v8::Function)
-#else
-# define QUIC_ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
-#endif
+ V(x509_constructor_template, v8::FunctionTemplate)
#define ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) \
V(async_hooks_after_function, v8::Function) \
@@ -563,8 +525,7 @@ constexpr size_t kFsStatsBufferLength =
V(tls_wrap_constructor_function, v8::Function) \
V(trace_category_state_function, v8::Function) \
V(udp_constructor_function, v8::Function) \
- V(url_constructor_function, v8::Function) \
- QUIC_ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
+ V(url_constructor_function, v8::Function)
class Environment;
struct AllocatedBuffer;
diff --git a/src/node_binding.cc b/src/node_binding.cc
index 0db930adff1..6c7ab4b21ed 100644
--- a/src/node_binding.cc
+++ b/src/node_binding.cc
@@ -14,12 +14,6 @@
#define NODE_BUILTIN_OPENSSL_MODULES(V)
#endif
-#if defined(NODE_EXPERIMENTAL_QUIC) && NODE_EXPERIMENTAL_QUIC
-#define NODE_BUILTIN_QUIC_MODULES(V) V(quic)
-#else
-#define NODE_BUILTIN_QUIC_MODULES(V)
-#endif
-
#if NODE_HAVE_I18N_SUPPORT
#define NODE_BUILTIN_ICU_MODULES(V) V(icu)
#else
@@ -98,7 +92,6 @@
#define NODE_BUILTIN_MODULES(V) \
NODE_BUILTIN_STANDARD_MODULES(V) \
NODE_BUILTIN_OPENSSL_MODULES(V) \
- NODE_BUILTIN_QUIC_MODULES(V) \
NODE_BUILTIN_ICU_MODULES(V) \
NODE_BUILTIN_PROFILER_MODULES(V) \
NODE_BUILTIN_DTRACE_MODULES(V)
diff --git a/src/node_errors.h b/src/node_errors.h
index 6158a968d27..984603c42e2 100644
--- a/src/node_errors.h
+++ b/src/node_errors.h
@@ -78,8 +78,6 @@ void OnFatalError(const char* location, const char* message);
V(ERR_WASI_NOT_STARTED, Error) \
V(ERR_WORKER_INIT_FAILED, Error) \
V(ERR_PROTO_ACCESS, Error) \
- V(ERR_QUIC_CANNOT_SET_GROUPS, Error) \
- V(ERR_QUIC_FAILURE_SETTING_SNI_CONTEXT, Error)
#define V(code, type) \
inline v8::Local<v8::Value> code(v8::Isolate* isolate, \
@@ -156,9 +154,7 @@ void OnFatalError(const char* location, const char* message);
V(ERR_WORKER_INIT_FAILED, "Worker initialization failure") \
V(ERR_PROTO_ACCESS, \
"Accessing Object.prototype.__proto__ has been " \
- "disallowed with --disable-proto=throw") \
- V(ERR_QUIC_CANNOT_SET_GROUPS, "Cannot set groups") \
- V(ERR_QUIC_FAILURE_SETTING_SNI_CONTEXT, "Failure setting SNI context")
+ "disallowed with --disable-proto=throw")
#define V(code, message) \
inline v8::Local<v8::Value> code(v8::Isolate* isolate) { \
diff --git a/src/node_http_common.h b/src/node_http_common.h
index 8017c0d7aad..ad9f2a864e0 100644
--- a/src/node_http_common.h
+++ b/src/node_http_common.h
@@ -270,7 +270,7 @@ class NgHeaders {
MaybeStackBuffer<char, 3000> buf_;
};
-// The ng libraries (nghttp2 and nghttp3) each use nearly identical
+// The ng libraries use nearly identical
// reference counted structures for retaining header name and value
// information in memory until the application is done with it.
// The NgRcBufPointer is an intelligent pointer capable of working
diff --git a/src/node_mem.h b/src/node_mem.h
index f8cdc20848f..332d1cd1eff 100644
--- a/src/node_mem.h
+++ b/src/node_mem.h
@@ -8,7 +8,7 @@
namespace node {
namespace mem {
-// Both ngtcp2 and nghttp2 allow custom allocators that
+// nghttp2 allows custom allocators that
// follow exactly the same structure and behavior, but
// use different struct names. To allow for code re-use,
// the NgLibMemoryManager template class can be used for both.
diff --git a/src/node_metadata.cc b/src/node_metadata.cc
index e8864d35527..8d0a725de45 100644
--- a/src/node_metadata.cc
+++ b/src/node_metadata.cc
@@ -13,11 +13,6 @@
#include <openssl/opensslv.h>
#endif // HAVE_OPENSSL
-#if defined(NODE_EXPERIMENTAL_QUIC) && NODE_EXPERIMENTAL_QUIC
-#include <ngtcp2/version.h>
-#include <nghttp3/version.h>
-#endif
-
#ifdef NODE_HAVE_I18N_SUPPORT
#include <unicode/timezone.h>
#include <unicode/ulocdata.h>
@@ -96,11 +91,6 @@ Metadata::Versions::Versions() {
openssl = GetOpenSSLVersion();
#endif
-#if defined(NODE_EXPERIMENTAL_QUIC) && NODE_EXPERIMENTAL_QUIC
- ngtcp2 = NGTCP2_VERSION;
- nghttp3 = NGHTTP3_VERSION;
-#endif
-
#ifdef NODE_HAVE_I18N_SUPPORT
icu = U_ICU_VERSION;
unicode = U_UNICODE_VERSION;
diff --git a/src/node_metadata.h b/src/node_metadata.h
index 2a4571883d8..bf7e5d3ff4e 100644
--- a/src/node_metadata.h
+++ b/src/node_metadata.h
@@ -38,12 +38,6 @@ namespace node {
#define NODE_VERSIONS_KEY_CRYPTO(V)
#endif
-#if defined(NODE_EXPERIMENTAL_QUIC) && NODE_EXPERIMENTAL_QUIC
-#define NODE_VERSIONS_KEY_QUIC(V) V(ngtcp2) V(nghttp3)
-#else
-#define NODE_VERSIONS_KEY_QUIC(V)
-#endif
-
#ifdef NODE_HAVE_I18N_SUPPORT
#define NODE_VERSIONS_KEY_INTL(V) \
V(cldr) \
@@ -57,7 +51,6 @@ namespace node {
#define NODE_VERSIONS_KEYS(V) \
NODE_VERSIONS_KEYS_BASE(V) \
NODE_VERSIONS_KEY_CRYPTO(V) \
- NODE_VERSIONS_KEY_QUIC(V) \
NODE_VERSIONS_KEY_INTL(V)
class Metadata {
diff --git a/src/node_native_module.cc b/src/node_native_module.cc
index f7d73544d2d..5a8cf12347d 100644
--- a/src/node_native_module.cc
+++ b/src/node_native_module.cc
@@ -105,10 +105,6 @@ void NativeModuleLoader::InitializeModuleCategories() {
"internal/process/policy",
"internal/streams/lazy_transform",
#endif // !HAVE_OPENSSL
-#if !NODE_EXPERIMENTAL_QUIC
- "internal/quic/core",
- "internal/quic/util",
-#endif
"sys", // Deprecated.
"wasi", // Experimental.
"internal/test/binding",
diff --git a/src/quic/node_quic.cc b/src/quic/node_quic.cc
deleted file mode 100644
index f34c8669f94..00000000000
--- a/src/quic/node_quic.cc
+++ /dev/null
@@ -1,271 +0,0 @@
-#include "debug_utils-inl.h"
-#include "node.h"
-#include "env-inl.h"
-#include "crypto/crypto_context.h"
-#include "crypto/crypto_common.h"
-#include "node_errors.h"
-#include "node_process.h"
-#include "node_quic_crypto.h"
-#include "node_quic_session-inl.h"
-#include "node_quic_socket-inl.h"
-#include "node_quic_stream-inl.h"
-#include "node_quic_state.h"
-#include "node_quic_util-inl.h"
-#include "node_sockaddr-inl.h"
-#include "nghttp3/nghttp3.h"
-
-#include <memory>
-#include <utility>
-
-namespace node {
-
-using crypto::SecureContext;
-using v8::Context;
-using v8::Function;
-using v8::FunctionCallbackInfo;
-using v8::HandleScope;
-using v8::Isolate;
-using v8::Local;
-using v8::Object;
-using v8::Value;
-
-namespace quic {
-
-constexpr FastStringKey QuicState::binding_data_name;
-
-void QuicState::MemoryInfo(MemoryTracker* tracker) const {
- tracker->TrackField("root_buffer", root_buffer);
-}
-
-namespace {
-// Register the JavaScript callbacks the internal binding will use to report
-// status and updates. This is called only once when the quic module is loaded.
-void QuicSetCallbacks(const FunctionCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
- CHECK(args[0]->IsObject());
- Local<Object> obj = args[0].As<Object>();
-
-#define SETFUNCTION(name, callback) \
- do { \
- Local<Value> fn; \
- CHECK(obj->Get(env->context(), \
- FIXED_ONE_BYTE_STRING(env->isolate(), name)).ToLocal(&fn));\
- CHECK(fn->IsFunction()); \
- env->set_quic_on_##callback##_function(fn.As<Function>()); \
- } while (0)
-
- SETFUNCTION("onSocketClose", socket_close);
- SETFUNCTION("onSessionReady", session_ready);
- SETFUNCTION("onSessionCert", session_cert);
- SETFUNCTION("onSessionClientHello", session_client_hello);
- SETFUNCTION("onSessionClose", session_close);
- SETFUNCTION("onSessionHandshake", session_handshake);
- SETFUNCTION("onSessionKeylog", session_keylog);
- SETFUNCTION("onSessionUsePreferredAddress", session_use_preferred_address);
- SETFUNCTION("onSessionPathValidation", session_path_validation);
- SETFUNCTION("onSessionQlog", session_qlog);
- SETFUNCTION("onSessionStatus", session_status);
- SETFUNCTION("onSessionTicket", session_ticket);
- SETFUNCTION("onSessionVersionNegotiation", session_version_negotiation);
- SETFUNCTION("onStreamReady", stream_ready);
- SETFUNCTION("onStreamClose", stream_close);
- SETFUNCTION("onStreamError", stream_error);
- SETFUNCTION("onStreamReset", stream_reset);
- SETFUNCTION("onSocketServerBusy", socket_server_busy);
- SETFUNCTION("onStreamHeaders", stream_headers);
- SETFUNCTION("onStreamBlocked", stream_blocked);
-
-#undef SETFUNCTION
-}
-
-// Sets QUIC specific configuration options for the SecureContext.
-// It's entirely likely that there's a better way to do this, but
-// for now this works.
-template <ngtcp2_crypto_side side>
-void QuicInitSecureContext(const FunctionCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
- CHECK(args[0]->IsObject()); // Secure Context
- CHECK(args[1]->IsString()); // groups
- CHECK(args[2]->IsBoolean()); // early data
-
- SecureContext* sc;
- ASSIGN_OR_RETURN_UNWRAP(&sc, args[0].As<Object>(),
- args.GetReturnValue().Set(UV_EBADF));
- const node::Utf8Value groups(env->isolate(), args[1]);
-
- bool early_data = args[2]->BooleanValue(env->isolate());
-
- InitializeSecureContext(
- BaseObjectPtr<SecureContext>(sc),
- early_data,
- side);
-
- if (!crypto::SetGroups(sc, *groups))
- THROW_ERR_QUIC_CANNOT_SET_GROUPS(env);
-}
-} // namespace
-
-void Initialize(Local<Object> target,
- Local<Value> unused,
- Local<Context> context,
- void* priv) {
- Environment* env = Environment::GetCurrent(context);
- Isolate* isolate = env->isolate();
- HandleScope handle_scope(isolate);
-
- HistogramBase::Initialize(env);
-
- QuicState* const state =
- env->AddBindingData<QuicState>(context, target);
- if (state == nullptr) return;
-
-#define SET_STATE_TYPEDARRAY(name, field) \
- target->Set(context, \
- FIXED_ONE_BYTE_STRING(isolate, (name)), \
- (field.GetJSArray())).Check()
- SET_STATE_TYPEDARRAY("sessionConfig", state->quicsessionconfig_buffer);
- SET_STATE_TYPEDARRAY("http3Config", state->http3config_buffer);
-#undef SET_STATE_TYPEDARRAY
-
- QuicSocket::Initialize(env, target, context);
- QuicEndpoint::Initialize(env, target, context);
- QuicSession::Initialize(env, target, context);
- QuicStream::Initialize(env, target, context);
-
- env->SetMethod(target,
- "setCallbacks",
- QuicSetCallbacks);
- env->SetMethod(target,
- "initSecureContext",
- QuicInitSecureContext<NGTCP2_CRYPTO_SIDE_SERVER>);
- env->SetMethod(target,
- "initSecureContextClient",
- QuicInitSecureContext<NGTCP2_CRYPTO_SIDE_CLIENT>);
-
- Local<Object> constants = Object::New(env->isolate());
-
-// TODO(@jasnell): Audit which constants are actually being used in JS
-#define QUIC_CONSTANTS(V) \
- V(DEFAULT_MAX_STREAM_DATA_BIDI_LOCAL) \
- V(DEFAULT_RETRYTOKEN_EXPIRATION) \
- V(DEFAULT_MAX_CONNECTIONS) \
- V(DEFAULT_MAX_CONNECTIONS_PER_HOST) \
- V(DEFAULT_MAX_STATELESS_RESETS_PER_HOST) \
- V(IDX_HTTP3_QPACK_MAX_TABLE_CAPACITY) \
- V(IDX_HTTP3_QPACK_BLOCKED_STREAMS) \
- V(IDX_HTTP3_MAX_HEADER_LIST_SIZE) \
- V(IDX_HTTP3_MAX_PUSHES) \
- V(IDX_HTTP3_MAX_HEADER_PAIRS) \
- V(IDX_HTTP3_MAX_HEADER_LENGTH) \
- V(IDX_HTTP3_CONFIG_COUNT) \
- V(IDX_QUIC_SESSION_ACTIVE_CONNECTION_ID_LIMIT) \
- V(IDX_QUIC_SESSION_MAX_IDLE_TIMEOUT) \
- V(IDX_QUIC_SESSION_MAX_DATA) \
- V(IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_LOCAL) \
- V(IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_REMOTE) \
- V(IDX_QUIC_SESSION_MAX_STREAM_DATA_UNI) \
- V(IDX_QUIC_SESSION_MAX_STREAMS_BIDI) \
- V(IDX_QUIC_SESSION_MAX_STREAMS_UNI) \
- V(IDX_QUIC_SESSION_MAX_UDP_PAYLOAD_SIZE) \
- V(IDX_QUIC_SESSION_ACK_DELAY_EXPONENT) \
- V(IDX_QUIC_SESSION_DISABLE_MIGRATION) \
- V(IDX_QUIC_SESSION_MAX_ACK_DELAY) \
- V(IDX_QUIC_SESSION_CC_ALGO) \
- V(IDX_QUIC_SESSION_CONFIG_COUNT) \
- V(MAX_RETRYTOKEN_EXPIRATION) \
- V(MIN_RETRYTOKEN_EXPIRATION) \
- V(NGTCP2_APP_NOERROR) \
- V(NGTCP2_PATH_VALIDATION_RESULT_FAILURE) \
- V(NGTCP2_PATH_VALIDATION_RESULT_SUCCESS) \
- V(NGTCP2_CC_ALGO_CUBIC) \
- V(NGTCP2_CC_ALGO_RENO) \
- V(QUIC_ERROR_APPLICATION) \
- V(QUIC_ERROR_CRYPTO) \
- V(QUIC_ERROR_SESSION) \
- V(QUIC_PREFERRED_ADDRESS_USE) \
- V(QUIC_PREFERRED_ADDRESS_IGNORE) \
- V(QUICCLIENTSESSION_OPTION_REQUEST_OCSP) \
- V(QUICCLIENTSESSION_OPTION_VERIFY_HOSTNAME_IDENTITY) \
- V(QUICSERVERSESSION_OPTION_REJECT_UNAUTHORIZED) \
- V(QUICSERVERSESSION_OPTION_REQUEST_CERT) \
- V(QUICSOCKET_OPTIONS_VALIDATE_ADDRESS) \
- V(QUICSTREAM_HEADER_FLAGS_NONE) \
- V(QUICSTREAM_HEADER_FLAGS_TERMINAL) \
- V(QUICSTREAM_HEADERS_KIND_NONE) \
- V(QUICSTREAM_HEADERS_KIND_INFORMATIONAL) \
- V(QUICSTREAM_HEADERS_KIND_PUSH) \
- V(QUICSTREAM_HEADERS_KIND_INITIAL) \
- V(QUICSTREAM_HEADERS_KIND_TRAILING) \
- V(ERR_FAILED_TO_CREATE_SESSION) \
- V(UV_EBADF)
-
-#define V(id, _, __) \
- NODE_DEFINE_CONSTANT(constants, IDX_QUICSESSION_STATE_##id);
- QUICSESSION_SHARED_STATE(V)
-#undef V
-
-#define V(id, _, __) \
- NODE_DEFINE_CONSTANT(constants, IDX_QUICSOCKET_STATE_##id);
- QUICSOCKET_SHARED_STATE(V)
-#undef V
-
-#define V(id, _, __) \
- NODE_DEFINE_CONSTANT(constants, IDX_QUICSTREAM_STATE_##id);
- QUICSTREAM_SHARED_STATE(V)
-#undef V
-
-#define V(name, _, __) \
- NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_##name);
- SESSION_STATS(V)
-#undef V
-
-#define V(name, _, __) \
- NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SOCKET_STATS_##name);
- SOCKET_STATS(V)
-#undef V
-
-#define V(name, _, __) \
- NODE_DEFINE_CONSTANT(constants, IDX_QUIC_STREAM_STATS_##name);
- STREAM_STATS(V)
-#undef V
-
-#define V(name) NODE_DEFINE_CONSTANT(constants, name);
- QUIC_CONSTANTS(V)
-#undef V
-
- NODE_DEFINE_CONSTANT(constants, NGTCP2_DEFAULT_MAX_PKTLEN);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_PROTO_VER);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_DEFAULT_MAX_ACK_DELAY);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_MAX_CIDLEN);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_MIN_CIDLEN);
-
- NODE_DEFINE_CONSTANT(constants, NGTCP2_NO_ERROR);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_INTERNAL_ERROR);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_CONNECTION_REFUSED);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_FLOW_CONTROL_ERROR);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_STREAM_LIMIT_ERROR);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_STREAM_STATE_ERROR);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_FINAL_SIZE_ERROR);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_FRAME_ENCODING_ERROR);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_TRANSPORT_PARAMETER_ERROR);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_CONNECTION_ID_LIMIT_ERROR);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_PROTOCOL_VIOLATION);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_INVALID_TOKEN);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_APPLICATION_ERROR);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_CRYPTO_BUFFER_EXCEEDED);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_KEY_UPDATE_ERROR);
- NODE_DEFINE_CONSTANT(constants, NGTCP2_CRYPTO_ERROR);
-
- NODE_DEFINE_CONSTANT(constants, AF_INET);
- NODE_DEFINE_CONSTANT(constants, AF_INET6);
- NODE_DEFINE_STRING_CONSTANT(constants,
- NODE_STRINGIFY_HELPER(NGHTTP3_ALPN_H3),
- NGHTTP3_ALPN_H3);
-
- target->Set(context, env->constants_string(), constants).FromJust();
-}
-
-} // namespace quic
-} // namespace node
-
-NODE_MODULE_CONTEXT_AWARE_INTERNAL(quic, node::quic::Initialize)
diff --git a/src/quic/node_quic_buffer-inl.h b/src/quic/node_quic_buffer-inl.h
deleted file mode 100644
index 25f130ac9fe..00000000000
--- a/src/quic/node_quic_buffer-inl.h
+++ /dev/null
@@ -1,98 +0,0 @@
-#ifndef SRC_QUIC_NODE_QUIC_BUFFER_INL_H_
-#define SRC_QUIC_NODE_QUIC_BUFFER_INL_H_
-
-#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#include "node_quic_buffer.h"
-#include "node_bob-inl.h"
-#include "util-inl.h"
-#include "uv.h"
-
-#include <algorithm>
-
-namespace node {
-
-namespace quic {
-
-QuicBufferChunk::QuicBufferChunk(size_t len)
- : data_buf_(len, 0),
- buf_(uv_buf_init(reinterpret_cast<char*>(data_buf_.data()), len)),
- length_(len),
- done_called_(true) {}
-
-QuicBufferChunk::QuicBufferChunk(uv_buf_t buf, DoneCB done)
- : buf_(buf),
- length_(buf.len) {
- if (done != nullptr)
- done_ = std::move(done);
-}
-
-QuicBufferChunk::~QuicBufferChunk() {
- CHECK(done_called_);
-}
-
-size_t QuicBufferChunk::Seek(size_t amount) {
- amount = std::min(amount, remaining());
- buf_.base += amount;
- buf_.len -= amount;
- return amount;
-}
-
-size_t QuicBufferChunk::Consume(size_t amount) {
- amount = std::min(amount, length_);
- length_ -= amount;
- return amount;
-}
-
-void QuicBufferChunk::Done(int status) {
- if (done_called_) return;
- done_called_ = true;
- if (done_ != nullptr)
- std::move(done_)(status);
-}
-
-QuicBuffer::QuicBuffer(QuicBuffer&& src) noexcept
- : head_(src.head_),
- tail_(src.tail_),
- ended_(src.ended_),
- length_(src.length_) {
- root_ = std::move(src.root_);
- src.head_ = nullptr;
- src.tail_ = nullptr;
- src.length_ = 0;
- src.remaining_ = 0;
- src.ended_ = false;
-}
-
-QuicBuffer& QuicBuffer::operator=(QuicBuffer&& src) noexcept {
- if (this == &src) return *this;
- this->~QuicBuffer();
- return *new(this) QuicBuffer(std::move(src));
-}
-
-bool QuicBuffer::is_empty(uv_buf_t buf) {
- DCHECK_IMPLIES(buf.base == nullptr, buf.len == 0);
- return buf.len == 0;
-}
-
-size_t QuicBuffer::Consume(size_t amount) {
- return Consume(0, amount);
-}
-
-size_t QuicBuffer::Cancel(int status) {
- if (canceled_) return 0;
- canceled_ = true;
- size_t t = Consume(status, length());
- return t;
-}
-
-void QuicBuffer::Push(uv_buf_t buf, DoneCB done) {
- std::unique_ptr<QuicBufferChunk> chunk =
- std::make_unique<QuicBufferChunk>(buf, done);
- Push(std::move(chunk));
-}
-} // namespace quic
-} // namespace node
-
-#endif // NODE_WANT_INTERNALS
-#endif // SRC_QUIC_NODE_QUIC_BUFFER_INL_H_
diff --git a/src/quic/node_quic_buffer.cc b/src/quic/node_quic_buffer.cc
deleted file mode 100644
index ebe230271ac..00000000000
--- a/src/quic/node_quic_buffer.cc
+++ /dev/null
@@ -1,166 +0,0 @@
-#include "node_quic_buffer-inl.h" // NOLINT(build/include)
-#include "node_bob-inl.h"
-#include "util.h"
-#include "uv.h"
-
-#include <algorithm>
-#include <memory>
-#include <utility>
-
-namespace node {
-namespace quic {
-
-void QuicBufferChunk::MemoryInfo(MemoryTracker* tracker) const {
- tracker->TrackField("buf", data_buf_);
- tracker->TrackField("next", next_);
-}
-
-size_t QuicBuffer::Push(uv_buf_t* bufs, size_t nbufs, DoneCB done) {
- size_t len = 0;
- if (UNLIKELY(nbufs == 0)) {
- done(0);
- return 0;
- }
- DCHECK_NOT_NULL(bufs);
- size_t n = 0;
- while (nbufs > 1) {
- if (!is_empty(bufs[n])) {
- Push(bufs[n]);
- len += bufs[n].len;
- }
- n++;
- nbufs--;
- }
- if (!is_empty(bufs[n])) {
- Push(bufs[n], done);
- len += bufs[n].len;
- }
- // Special case if all the bufs were empty.
- if (UNLIKELY(len == 0))
- done(0);
-
- return len;
-}
-
-void QuicBuffer::Push(std::unique_ptr<QuicBufferChunk> chunk) {
- CHECK(!ended_);
- length_ += chunk->remaining();
- remaining_ += chunk->remaining();
- if (!tail_) {
- root_ = std::move(chunk);
- head_ = tail_ = root_.get();
- } else {
- tail_->next_ = std::move(chunk);
- tail_ = tail_->next_.get();
- if (!head_)
- head_ = tail_;
- }
-}
-
-size_t QuicBuffer::Seek(size_t amount) {
- size_t len = 0;
- while (head_ && amount > 0) {
- size_t amt = head_->Seek(amount);
- amount -= amt;
- len += amt;
- remaining_ -= amt;
- if (head_->remaining())
- break;
- head_ = head_->next_.get();
- }
- return len;
-}
-
-bool QuicBuffer::Pop(int status) {
- if (!root_)
- return false;
- std::unique_ptr<QuicBufferChunk> root(std::move(root_));
- root_ = std::move(root.get()->next_);
-
- if (head_ == root.get())
- head_ = root_.get();
- if (tail_ == root.get())
- tail_ = root_.get();
-
- root->Done(status);
- return true;
-}
-
-size_t QuicBuffer::Consume(int status, size_t amount) {
- size_t amt = std::min(amount, length_);
- size_t len = 0;
- while (root_ && amt > 0) {
- auto root = root_.get();
- size_t consumed = root->Consume(amt);
- len += consumed;
- length_ -= consumed;
- amt -= consumed;
- if (root->length() > 0)
- break;
- Pop(status);
- }
- return len;
-}
-
-void QuicBuffer::MemoryInfo(MemoryTracker* tracker) const {
- tracker->TrackField("root", root_);
-}
-
-int QuicBuffer::DoPull(
- bob::Next<ngtcp2_vec> next,
- int options,
- ngtcp2_vec* data,
- size_t count,
- size_t max_count_hint) {
- size_t len = 0;
- size_t numbytes = 0;
- int status = bob::Status::STATUS_CONTINUE;
-
- // There's no data to read.
- if (!remaining() || head_ == nullptr) {
- status = is_ended() ?
- bob::Status::STATUS_END :
- bob::Status::STATUS_BLOCK;
- std::move(next)(status, nullptr, 0, [](size_t len) {});
- return status;
- }
-
- // Ensure that there's storage space.
- MaybeStackBuffer<ngtcp2_vec, kMaxVectorCount> vec;
- if (data == nullptr || count == 0) {
- vec.AllocateSufficientStorage(max_count_hint);
- data = vec.out();
- } else {
- max_count_hint = std::min(count, max_count_hint);
- }
-
- // Build the list of buffers.
- QuicBufferChunk* pos = head_;
- while (pos != nullptr && len < max_count_hint) {
- data[len].base = reinterpret_cast<uint8_t*>(pos->buf().base);
- data[len].len = pos->buf().len;
- numbytes += data[len].len;
- len++;
- pos = pos->next_.get();
- }
-
- // If the buffer is ended, and the number of bytes
- // matches the total remaining and OPTIONS_END is
- // used, set the status to STATUS_END.
- if (is_ended() &&
- numbytes == remaining() &&
- options & bob::OPTIONS_END)
- status = bob::Status::STATUS_END;
-
- // Pass the data back out to the caller.
- std::move(next)(
- status,
- data,
- len,
- [this](size_t len) { Seek(len); });
-
- return status;
-}
-
-} // namespace quic
-} // namespace node
diff --git a/src/quic/node_quic_buffer.h b/src/quic/node_quic_buffer.h
deleted file mode 100644
index 3d81a28176d..00000000000
--- a/src/quic/node_quic_buffer.h
+++ /dev/null
@@ -1,239 +0,0 @@
-#ifndef SRC_QUIC_NODE_QUIC_BUFFER_H_
-#define SRC_QUIC_NODE_QUIC_BUFFER_H_
-
-#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#include "memory_tracker-inl.h"
-#include "ngtcp2/ngtcp2.h"
-#include "node.h"
-#include "node_bob.h"
-#include "node_internals.h"
-#include "util.h"
-#include "uv.h"
-
-#include <vector>
-
-namespace node {
-namespace quic {
-
-class QuicBuffer;
-
-constexpr size_t kMaxVectorCount = 16;
-
-using DoneCB = std::function<void(int)>;
-
-// When data is sent over QUIC, we are required to retain it in memory
-// until we receive an acknowledgement that it has been successfully
-// received. The QuicBuffer object is what we use to handle that
-// and track until it is acknowledged. To understand the QuicBuffer
-// object itself, it is important to understand how ngtcp2 and nghttp3
-// handle data that is given to it to serialize into QUIC packets.
-//
-// An individual QUIC packet may contain multiple QUIC frames. Whenever
-// we create a QUIC packet, we really have no idea what frames are going
-// to be encoded or how much buffered handshake or stream data is going
-// to be included within that QuicPacket. If there is buffered data
-// available for a stream, we provide an array of pointers to that data
-// and an indication about how much data is available, then we leave it
-// entirely up to ngtcp2 and nghttp3 to determine how much of the data
-// to encode into the QUIC packet. It is only *after* the QUIC packet
-// is encoded that we can know how much was actually written.
-//
-// Once written to a QUIC Packet, we have to keep the data in memory
-// until an acknowledgement is received. In QUIC, acknowledgements are
-// received per range of packets.
-//
-// QuicBuffer is complicated because it needs to be able to accomplish
-// three things: (a) buffering uv_buf_t instances passed down from
-// JavaScript without memcpy and keeping track of the Write callback
-// associated with each, (b) tracking what data has already been
-// encoded in a QUIC packet and what data is remaining to be read, and
-// (c) tracking which data has been acknowledged and which hasn't.
-// QuicBuffer is further complicated by design quirks and limitations
-// of the StreamBase API and how it interacts with the JavaScript side.
-//
-// QuicBuffer is a linked list of QuicBufferChunk instances.
-// A single QuicBufferChunk wraps a single non-zero-length uv_buf_t.
-// When the QuicBufferChunk is created, we capture the total length
-// of the buffer and the total number of bytes remaining to be sent.
-// Initially, these numbers are identical.
-//
-// When data is encoded into a QuicPacket, we advance the QuicBufferChunk's
-// remaining-to-be-read by the number of bytes actually encoded. If there
-// are no more bytes remaining to be encoded, we move to the next chunk
-// in the linked list.
-//
-// When an acknowledgement is received, we decrement the QuicBufferChunk's
-// length by the number of acknowledged bytes. Once the unacknowledged
-// length reaches 0, we invoke the callback function associated with the
-// QuicBufferChunk (if any).
-//
-// QuicStream is a StreamBase implementation. For every DoWrite call,
-// it receives one or more uv_buf_t instances in a single batch associated
-// with a single write callback. For each uv_buf_t DoWrite gets, a
-// corresponding QuicBufferChunk is added to the QuicBuffer, with the
-// callback associated with the final chunk added to the list.
-
-
-// A QuicBufferChunk contains the actual buffered data
-// along with a callback to be called when the data has
-// been consumed.
-//
-// Any given chunk has a remaining-to-be-acknowledged length (length()) and a
-// remaining-to-be-read-length (remaining()). The former tracks the number
-// of bytes that have yet to be acknowledged by the QUIC peer. Once the
-// remaining-to-be-acknowledged length reaches zero, the done callback
-// associated with the QuicBufferChunk can be called and the QuicBufferChunk
-// can be discarded. The remaining-to-be-read length is adjusted as data is
-// serialized into QUIC packets and sent.
-// The remaining-to-be-acknowledged length is adjusted using consume(),
-// while the remaining-to-be-ead length is adjusted using seek().
-class QuicBufferChunk final : public MemoryRetainer {
- public:
- // Default non-op done handler.
- static void default_done(int status) {}
-
- // In this variant, the QuicBufferChunk owns the underlying
- // data storage within a vector. The data will be
- // freed when the QuicBufferChunk is destroyed.
- inline explicit QuicBufferChunk(size_t len);
-
- // In this variant, the QuicBufferChunk only maintains a
- // pointer to the underlying data buffer. The QuicBufferChunk
- // does not take ownership of the buffer. The done callback
- // is invoked to let the caller know when the chunk is no
- // longer being used.
- inline QuicBufferChunk(uv_buf_t buf_, DoneCB done_);
-
- inline ~QuicBufferChunk() override;
-
- // Invokes the done callback associated with the QuicBufferChunk.
- inline void Done(int status);
-
- // length() provides the remaining-to-be-acknowledged length.
- // The QuicBufferChunk must be retained in memory while this
- // count is greater than zero. The length is adjusted by
- // calling Consume();
- inline size_t length() const { return length_; }
-
- // remaining() provides the remaining-to-be-read length number of bytes.
- // The length is adjusted by calling Seek()
- inline size_t remaining() const { return buf_.len; }
-
- // Consumes (acknowledges) the given number of bytes. If amount
- // is greater than length(), only length() bytes are consumed.
- // Returns the actual number of bytes consumed.
- inline size_t Consume(size_t amount);
-
- // Seeks (reads) the given number of bytes. If amount is greater
- // than remaining(), only remaining() bytes are read. Returns
- // the actual number of bytes read.
- inline size_t Seek(size_t amount);
-
- uint8_t* out() { return reinterpret_cast<uint8_t*>(buf_.base); }
- uv_buf_t buf() { return buf_; }
- const uv_buf_t buf() const { return buf_; }
-
- void MemoryInfo(MemoryTracker* tracker) const override;
- SET_MEMORY_INFO_NAME(QuicBufferChunk)
- SET_SELF_SIZE(QuicBufferChunk)
-
- private:
- std::vector<uint8_t> data_buf_;
- uv_buf_t buf_;
- DoneCB done_ = default_done;
- size_t length_ = 0;
- bool done_called_ = false;
- std::unique_ptr<QuicBufferChunk> next_;
-
- friend class QuicBuffer;
-};
-
-class QuicBuffer final : public bob::SourceImpl<ngtcp2_vec>,
- public MemoryRetainer {
- public:
- QuicBuffer() = default;
-
- inline QuicBuffer(QuicBuffer&& src) noexcept;
- inline QuicBuffer& operator=(QuicBuffer&& src) noexcept;
-
- ~QuicBuffer() override {
- Cancel(); // Cancel the remaining data
- CHECK_EQ(length_, 0);
- }
-
- // Marks the QuicBuffer as having ended, preventing new QuicBufferChunk
- // instances from being appended to the linked list and allowing the
- // Pull operation to know when to signal that the flow of data is
- // completed.
- void End() { ended_ = true; }
- bool is_ended() const { return ended_; }
-
- // Push one or more uv_buf_t instances into the buffer.
- // the DoneCB callback will be invoked when the last
- // uv_buf_t in the bufs array is consumed and popped out
- // of the internal linked list. Ownership of the uv_buf_t
- // remains with the caller.
- size_t Push(
- uv_buf_t* bufs,
- size_t nbufs,
- DoneCB done = QuicBufferChunk::default_done);
-
- // Pushes a single QuicBufferChunk into the linked list
- void Push(std::unique_ptr<QuicBufferChunk> chunk);
-
- // Consume the given number of bytes within the buffer. If amount
- // is greater than length(), length() bytes are consumed. Returns
- // the actual number of bytes consumed.
- inline size_t Consume(size_t amount);
-
- // Cancels the remaining bytes within the buffer.
- inline size_t Cancel(int status = UV_ECANCELED);
-
- // Seeks (reads) the given number of bytes. If amount is greater
- // than remaining(), seeks remaining() bytes. Returns the actual
- // number of bytes read.
- size_t Seek(size_t amount);
-
- // The total number of unacknowledged bytes remaining.
- size_t length() const { return length_; }
-
- // The total number of unread bytes remaining.
- size_t remaining() const { return remaining_; }
-
- void MemoryInfo(MemoryTracker* tracker) const override;
- SET_MEMORY_INFO_NAME(QuicBuffer);
- SET_SELF_SIZE(QuicBuffer);
-
- protected:
- int DoPull(
- bob::Next<ngtcp2_vec> next,
- int options,
- ngtcp2_vec* data,
- size_t count,
- size_t max_count_hint) override;
-
- private:
- inline static bool is_empty(uv_buf_t buf);
- size_t Consume(int status, size_t amount);
- bool Pop(int status = 0);
- inline void Push(uv_buf_t buf, DoneCB done = nullptr);
-
- std::unique_ptr<QuicBufferChunk> root_;
- QuicBufferChunk* head_ = nullptr; // Current Read Position
- QuicBufferChunk* tail_ = nullptr; // Current Write Position
-
- bool canceled_ = false;
- bool ended_ = false;
- size_t length_ = 0;
- size_t remaining_ = 0;
-
- friend class QuicBufferChunk;
-};
-
-} // namespace quic
-} // namespace node
-
-#endif // NODE_WANT_INTERNALS
-
-#endif // SRC_QUIC_NODE_QUIC_BUFFER_H_
diff --git a/src/quic/node_quic_crypto.cc b/src/quic/node_quic_crypto.cc
deleted file mode 100644
index 74bb4f18783..00000000000
--- a/src/quic/node_quic_crypto.cc
+++ /dev/null
@@ -1,748 +0,0 @@
-#include "node_quic_crypto.h"
-#include "env-inl.h"
-#include "node_crypto.h"
-#include "crypto/crypto_util.h"
-#include "crypto/crypto_context.h"
-#include "crypto/crypto_common.h"
-#include "node_process.h"
-#include "node_quic_session-inl.h"
-#include "node_quic_util-inl.h"
-#include "node_sockaddr-inl.h"
-#include "node_url.h"
-#include "string_bytes.h"
-#include "v8.h"
-#include "util-inl.h"
-
-#include <ngtcp2/ngtcp2.h>
-#include <ngtcp2/ngtcp2_crypto.h>
-#include <nghttp3/nghttp3.h> // NGHTTP3_ALPN_H3
-#include <openssl/bio.h>
-#include <openssl/err.h>
-#include <openssl/evp.h>
-#include <openssl/kdf.h>
-#include <openssl/ssl.h>
-#include <openssl/x509v3.h>
-
-#include <iterator>
-#include <numeric>
-#include <unordered_map>
-#include <string>
-#include <sstream>
-#include <vector>
-
-namespace node {
-
-using crypto::EntropySource;
-using v8::Local;
-using v8::Value;
-
-namespace quic {
-
-bool SessionTicketAppData::Set(const uint8_t* data, size_t len) {
- if (set_)
- return false;
- set_ = true;
- SSL_SESSION_set1_ticket_appdata(session_, data, len);
- return set_;
-}
-
-bool SessionTicketAppData::Get(uint8_t** data, size_t* len) const {
- return SSL_SESSION_get0_ticket_appdata(
- session_,
- reinterpret_cast<void**>(data),
- len) == 1;
-}
-
-namespace {
-constexpr int kCryptoTokenKeylen = 32;
-constexpr int kCryptoTokenIvlen = 32;
-
-// Used solely to derive the keys used to generate and
-// validate retry tokens. The implementation of this is
-// Node.js specific. We use the current implementation
-// because it is simple.
-bool DeriveTokenKey(
- uint8_t* token_key,
- uint8_t* token_iv,
- const uint8_t* rand_data,
- size_t rand_datalen,
- const ngtcp2_crypto_ctx& ctx,
- const uint8_t* token_secret) {
- static constexpr int kCryptoTokenSecretlen = 32;
- uint8_t secret[kCryptoTokenSecretlen];
-
- return
- NGTCP2_OK(ngtcp2_crypto_hkdf_extract(
- secret,
- &ctx.md,
- token_secret,
- kTokenSecretLen,
- rand_data,
- rand_datalen)) &&
- NGTCP2_OK(ngtcp2_crypto_derive_packet_protection_key(
- token_key,
- token_iv,
- nullptr,
- &ctx.aead,
- &ctx.md,
- secret,
- kCryptoTokenSecretlen));
-}
-
-// Retry tokens are generated only by QUIC servers. They
-// are opaque to QUIC clients and must not be guessable by
-// on- or off-path attackers. A QUIC server sends a RETRY
-// token as a way of initiating explicit path validation
-// with a client in response to an initial QUIC packet.
-// The client, upon receiving a RETRY, must abandon the
-// initial connection attempt and try again, including the
-// received retry token in the new initial packet sent to
-// the server. If the server is performing explicit
-// valiation, it will look for the presence of the retry
-// token and validate it if found. The internal structure
-// of the retry token must be meaningful to the server,
-// and the server must be able to validate the token without
-// relying on any state left over from the previous connection
-// attempt. The implementation here is entirely Node.js
-// specific.
-//
-// The token is generated by:
-// 1. Appending the raw bytes of given socket address, the current
-// timestamp, and the original CID together into a single byte
-// array.
-// 2. Generating a block of random data that is used together with
-// the token secret to cryptographically derive an encryption key.
-// 3. Encrypting the byte array from step 1 using the encryption key
-// from step 2.
-// 4. Appending random data generated in step 2 to the token.
-//
-// The token secret must be kept secret on the QUIC server that
-// generated the retry. When multiple QUIC servers are used in a
-// cluster, it cannot be guaranteed that the same QUIC server
-// instance will receive the subsequent new Initial packet. Therefore,
-// all QUIC servers in the cluster should either share or be aware
-// of the same token secret or a mechanism needs to be implemented
-// to ensure that subsequent packets are routed to the same QUIC
-// server instance.
-//
-// A malicious peer could attempt to guess the token secret by
-// sending a large number specially crafted RETRY-eliciting packets
-// to a server then analyzing the resulting retry tokens. To reduce
-// the possibility of such attacks, the current implementation of
-// QuicSocket generates the token secret randomly for each instance,
-// and the number of RETRY responses sent to a given remote address
-// should be limited. Such attacks should be of little actual value
-// in most cases.
-bool GenerateRetryToken(
- uint8_t* token,
- size_t* tokenlen,
- const SocketAddress& addr,
- const QuicCID& ocid,
- const uint8_t* token_secret) {
- std::array<uint8_t, 4096> plaintext;
- uint8_t rand_data[kTokenRandLen];
- uint8_t token_key[kCryptoTokenKeylen];
- uint8_t token_iv[kCryptoTokenIvlen];
-
- ngtcp2_crypto_ctx ctx;
- ngtcp2_crypto_ctx_initial(&ctx);
- size_t ivlen = ngtcp2_crypto_packet_protection_ivlen(&ctx.aead);
- uint64_t now = uv_hrtime();
-
- auto p = std::begin(plaintext);
- p = std::copy_n(addr.raw(), addr.length(), p);
- p = std::copy_n(reinterpret_cast<uint8_t*>(&now), sizeof(uint64_t), p);
- p = std::copy_n(ocid->data, ocid->datalen, p);
-
- EntropySource(rand_data, kTokenRandLen);
-
- if (!DeriveTokenKey(
- token_key,
- token_iv,
- rand_data,
- kTokenRandLen,
- ctx,
- token_secret)) {
- return false;
- }
-
- ngtcp2_crypto_aead_ctx aead_ctx;
- if (NGTCP2_ERR(ngtcp2_crypto_aead_ctx_encrypt_init(
- &aead_ctx,
- &ctx.aead,
- token_key,
- ivlen))) {
- return false;
- }
-
- size_t plaintextlen = std::distance(std::begin(plaintext), p);
- if (NGTCP2_ERR(ngtcp2_crypto_encrypt(
- token,
- &ctx.aead,
- &aead_ctx,
- plaintext.data(),
- plaintextlen,
- token_iv,
- ivlen,
- addr.raw(),
- addr.length()))) {
- return false;
- }
-
- *tokenlen = plaintextlen + ngtcp2_crypto_aead_taglen(&ctx.aead);
- memcpy(token + (*tokenlen), rand_data, kTokenRandLen);
- *tokenlen += kTokenRandLen;
- return true;
-}
-} // namespace
-
-// A stateless reset token is used when a QUIC endpoint receives a
-// QUIC packet with a short header but the associated connection ID
-// cannot be matched to any known QuicSession. In such cases, the
-// receiver may choose to send a subtle opaque indication to the
-// sending peer that state for the QuicSession has apparently been
-// lost. For any on- or off- path attacker, a stateless reset packet
-// resembles any other QUIC packet with a short header. In order to
-// be successfully handled as a stateless reset, the peer must have
-// already seen a reset token issued to it associated with the given
-// CID. The token itself is opaque to the peer that receives is but
-// must be possible to statelessly recreate by the peer that
-// originally created it. The actual implementation is Node.js
-// specific but we currently defer to a utility function provided
-// by ngtcp2.
-bool GenerateResetToken(
- uint8_t* token,
- const uint8_t* secret,
- const QuicCID& cid) {
- ngtcp2_crypto_ctx ctx;
- ngtcp2_crypto_ctx_initial(&ctx);
- return NGTCP2_OK(ngtcp2_crypto_generate_stateless_reset_token(
- token,
- &ctx.md,
- secret,
- NGTCP2_STATELESS_RESET_TOKENLEN,
- cid.cid()));
-}
-
-// Generates a RETRY packet. See the notes for GenerateRetryToken for details.
-std::unique_ptr<QuicPacket> GenerateRetryPacket(
- const uint8_t* token_secret,
- const QuicCID& dcid,
- const QuicCID& scid,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr) {
-
- uint8_t token[256];
- size_t tokenlen = sizeof(token);
-
- if (!GenerateRetryToken(token, &tokenlen, remote_addr, dcid, token_secret))
- return {};
-
- QuicCID cid;
- EntropySource(cid.data(), kScidLen);
- cid.set_length(kScidLen);
-
- size_t pktlen = tokenlen + (2 * NGTCP2_MAX_CIDLEN) + scid.length() + 8;
-
- auto packet = QuicPacket::Create("retry", pktlen);
- ssize_t nwrite =
- ngtcp2_crypto_write_retry(
- packet->data(),
- NGTCP2_MAX_PKTLEN_IPV4,
- scid.cid(),
- cid.cid(),
- dcid.cid(),
- token,
- tokenlen);
- if (nwrite <= 0)
- return {};
- packet->set_length(nwrite);
- return packet;
-}
-
-// Validates a retry token included in the given header. This will return
-// true if the token cannot be validated, false otherwise. A token is
-// valid if it can be successfully decrypted using the key derived from
-// random data embedded in the token, the structure of the token matches
-// that generated by the GenerateRetryToken function, and the token was
-// not generated earlier than now - verification_expiration. If validation
-// is successful, ocid will be updated to the original connection ID encoded
-// in the encrypted token.
-bool InvalidRetryToken(
- const ngtcp2_vec& token,
- const SocketAddress& addr,
- QuicCID* ocid,
- const uint8_t* token_secret,
- uint64_t verification_expiration) {
-
- if (token.len < kTokenRandLen)
- return true;
-
- ngtcp2_crypto_ctx ctx;
- ngtcp2_crypto_ctx_initial(&ctx);
-
- size_t ivlen = ngtcp2_crypto_packet_protection_ivlen(&ctx.aead);
-
- size_t ciphertextlen = token.len - kTokenRandLen;
- const uint8_t* ciphertext = token.base;
- const uint8_t* rand_data = token.base + ciphertextlen;
-
- uint8_t token_key[kCryptoTokenKeylen];
- uint8_t token_iv[kCryptoTokenIvlen];
-
- if (!DeriveTokenKey(
- token_key,
- token_iv,
- rand_data,
- kTokenRandLen,
- ctx,
- token_secret)) {
- return true;
- }
-
- uint8_t plaintext[4096];
-
- ngtcp2_crypto_aead_ctx aead_ctx;
- if (NGTCP2_ERR(ngtcp2_crypto_aead_ctx_decrypt_init(
- &aead_ctx,
- &ctx.aead,
- token_key,
- ivlen))) {
- return true;
- }
-
- if (NGTCP2_ERR(ngtcp2_crypto_decrypt(
- plaintext,
- &ctx.aead,
- &aead_ctx,
- ciphertext,
- ciphertextlen,
- token_iv,
- ivlen,
- addr.raw(),
- addr.length()))) {
- return true;
- }
-
- size_t plaintextlen = ciphertextlen - ngtcp2_crypto_aead_taglen(&ctx.aead);
- if (plaintextlen < addr.length() + sizeof(uint64_t))
- return true;
-
- ssize_t cil = plaintextlen - addr.length() - sizeof(uint64_t);
- if ((cil != 0 && (cil < NGTCP2_MIN_CIDLEN || cil > NGTCP2_MAX_CIDLEN)) ||
- memcmp(plaintext, addr.raw(), addr.length()) != 0) {
- return true;
- }
-
- uint64_t t;
- memcpy(&t, plaintext + addr.length(), sizeof(uint64_t));
-
- // 10-second window by default, but configurable for each
- // QuicSocket instance with a MIN_RETRYTOKEN_EXPIRATION second
- // minimum and a MAX_RETRYTOKEN_EXPIRATION second maximum.
- if (t + verification_expiration * NGTCP2_SECONDS < uv_hrtime())
- return true;
-
- ngtcp2_cid_init(
- ocid->cid(),
- plaintext + addr.length() + sizeof(uint64_t),
- cil);
-
- return false;
-}
-
-// Get the ALPN protocol identifier that was negotiated for the session
-Local<Value> GetALPNProtocol(const QuicSession& session) {
- QuicCryptoContext* ctx = session.crypto_context();
- Environment* env = session.env();
- std::string alpn = ctx->selected_alpn();
- // This supposed to be `NGHTTP3_ALPN_H3 + 1`
- // Details see https://github.com/nodejs/node/issues/33959
- if (alpn == &NGHTTP3_ALPN_H3[1]) {
- return env->http3_alpn_string();
- } else {
- return ToV8Value(
- env->context(),
- alpn,
- env->isolate()).FromMaybe(Local<Value>());
- }
-}
-
-namespace {
-int CertCB(SSL* ssl, void* arg) {
- QuicSession* session = static_cast<QuicSession*>(arg);
- int ret;
- switch (SSL_get_tlsext_status_type(ssl)) {
- case TLSEXT_STATUSTYPE_ocsp:
- ret = session->crypto_context()->OnOCSP();
- return UNLIKELY(session->is_destroyed()) ? 0 : ret;
- default:
- return 1;
- }
-}
-
-void Keylog_CB(const SSL* ssl, const char* line) {
- QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl));
- session->crypto_context()->Keylog(line);
-}
-
-int Client_Hello_CB(
- SSL* ssl,
- int* tls_alert,
- void* arg) {
- QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl));
- int ret = session->crypto_context()->OnClientHello();
- if (UNLIKELY(session->is_destroyed())) {
- *tls_alert = SSL_R_SSL_HANDSHAKE_FAILURE;
- return 0;
- }
- switch (ret) {
- case 0:
- return 1;
- case -1:
- return -1;
- default:
- *tls_alert = ret;
- return 0;
- }
-}
-
-int AlpnSelection(
- SSL* ssl,
- const unsigned char** out,
- unsigned char* outlen,
- const unsigned char* in,
- unsigned int inlen,
- void* arg) {
- QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl));
-
- unsigned char* tmp;
-
- // The QuicServerSession supports exactly one ALPN identifier. If that does
- // not match any of the ALPN identifiers provided in the client request,
- // then we fail here. Note that this will not fail the TLS handshake, so
- // we have to check later if the ALPN matches the expected identifier or not.
- if (SSL_select_next_proto(
- &tmp,
- outlen,
- reinterpret_cast<const unsigned char*>(session->alpn().c_str()),
- session->alpn().length(),
- in,
- inlen) == OPENSSL_NPN_NO_OVERLAP) {
- return SSL_TLSEXT_ERR_NOACK;
- }
- *out = tmp;
- return SSL_TLSEXT_ERR_OK;
-}
-
-int AllowEarlyDataCB(SSL* ssl, void* arg) {
- QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl));
- return session->allow_early_data() ? 1 : 0;
-}
-
-int TLS_Status_Callback(SSL* ssl, void* arg) {
- QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl));
- return session->crypto_context()->OnTLSStatus();
-}
-
-int New_Session_Callback(SSL* ssl, SSL_SESSION* session) {
- QuicSession* s = static_cast<QuicSession*>(SSL_get_app_data(ssl));
- return s->set_session(session);
-}
-
-int GenerateSessionTicket(SSL* ssl, void* arg) {
- QuicSession* s = static_cast<QuicSession*>(SSL_get_app_data(ssl));
- SessionTicketAppData app_data(SSL_get_session(ssl));
- s->SetSessionTicketAppData(app_data);
- return 1;
-}
-
-SSL_TICKET_RETURN DecryptSessionTicket(
- SSL* ssl,
- SSL_SESSION* session,
- const unsigned char* keyname,
- size_t keyname_len,
- SSL_TICKET_STATUS status,
- void* arg) {
- QuicSession* s = static_cast<QuicSession*>(SSL_get_app_data(ssl));
- SessionTicketAppData::Flag flag = SessionTicketAppData::Flag::STATUS_NONE;
- switch (status) {
- default:
- return SSL_TICKET_RETURN_IGNORE;
- case SSL_TICKET_EMPTY:
- // Fall through
- case SSL_TICKET_NO_DECRYPT:
- return SSL_TICKET_RETURN_IGNORE_RENEW;
- case SSL_TICKET_SUCCESS_RENEW:
- flag = SessionTicketAppData::Flag::STATUS_RENEW;
- // Fall through
- case SSL_TICKET_SUCCESS:
- SessionTicketAppData app_data(session);
- switch (s->GetSessionTicketAppData(app_data, flag)) {
- default:
- return SSL_TICKET_RETURN_IGNORE;
- case SessionTicketAppData::Status::TICKET_IGNORE:
- return SSL_TICKET_RETURN_IGNORE;
- case SessionTicketAppData::Status::TICKET_IGNORE_RENEW:
- return SSL_TICKET_RETURN_IGNORE_RENEW;
- case SessionTicketAppData::Status::TICKET_USE:
- return SSL_TICKET_RETURN_USE;
- case SessionTicketAppData::Status::TICKET_USE_RENEW:
- return SSL_TICKET_RETURN_USE_RENEW;
- }
- }
-}
-
-int SetEncryptionSecrets(
- SSL* ssl,
- OSSL_ENCRYPTION_LEVEL ossl_level,
- const uint8_t* read_secret,
- const uint8_t* write_secret,
- size_t secret_len) {
- QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl));
- return session->crypto_context()->OnSecrets(
- from_ossl_level(ossl_level),
- read_secret,
- write_secret,
- secret_len) ? 1 : 0;
-}
-
-int AddHandshakeData(
- SSL* ssl,
- OSSL_ENCRYPTION_LEVEL ossl_level,
- const uint8_t* data,
- size_t len) {
- QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl));
- session->crypto_context()->WriteHandshake(
- from_ossl_level(ossl_level),
- data,
- len);
- return 1;
-}
-
-int FlushFlight(SSL* ssl) { return 1; }
-
-int SendAlert(
- SSL* ssl,
- enum ssl_encryption_level_t level,
- uint8_t alert) {
- QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl));
- session->crypto_context()->set_tls_alert(alert);
- return 1;
-}
-
-bool SetTransportParams(QuicSession* session, const crypto::SSLPointer& ssl) {
- ngtcp2_transport_params params;
- ngtcp2_conn_get_local_transport_params(session->connection(), &params);
- uint8_t buf[512];
- ssize_t nwrite = ngtcp2_encode_transport_params(
- buf,
- arraysize(buf),
- NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS,
- &params);
- return nwrite >= 0 &&
- SSL_set_quic_transport_params(ssl.get(), buf, nwrite) == 1;
-}
-
-SSL_QUIC_METHOD quic_method = SSL_QUIC_METHOD{
- SetEncryptionSecrets,
- AddHandshakeData,
- FlushFlight,
- SendAlert
-};
-
-void SetHostname(const crypto::SSLPointer& ssl, const std::string& hostname) {
- // If the hostname is an IP address, use an empty string
- // as the hostname instead.
- X509_VERIFY_PARAM* param = SSL_get0_param(ssl.get());
- X509_VERIFY_PARAM_set_hostflags(param, 0);
-
- if (UNLIKELY(SocketAddress::is_numeric_host(hostname.c_str()))) {
- SSL_set_tlsext_host_name(ssl.get(), "");
- CHECK_EQ(X509_VERIFY_PARAM_set1_host(param, "", 0), 1);
- } else {
- SSL_set_tlsext_host_name(ssl.get(), hostname.c_str());
- CHECK_EQ(
- X509_VERIFY_PARAM_set1_host(param, hostname.c_str(), hostname.length()),
- 1);
- }
-}
-
-} // namespace
-
-void InitializeTLS(QuicSession* session, const crypto::SSLPointer& ssl) {
- QuicCryptoContext* ctx = session->crypto_context();
- Environment* env = session->env();
- QuicState* quic_state = session->quic_state();
-
- SSL_set_app_data(ssl.get(), session);
- SSL_set_cert_cb(ssl.get(), CertCB,
- const_cast<void*>(reinterpret_cast<const void*>(session)));
- SSL_set_verify(ssl.get(), SSL_VERIFY_NONE, crypto::VerifyCallback);
-
- // Enable tracing if the `--trace-tls` command line flag is used.
- if (env->options()->trace_tls) {
- ctx->EnableTrace();
- if (quic_state->warn_trace_tls) {
- quic_state->warn_trace_tls = false;
- ProcessEmitWarning(env,
- "Enabling --trace-tls can expose sensitive data "
- "in the resulting log");
- }
- }
-
- switch (ctx->side()) {
- case NGTCP2_CRYPTO_SIDE_CLIENT: {
- SSL_set_connect_state(ssl.get());
- crypto::SetALPN(ssl, session->alpn());
- SetHostname(ssl, session->hostname());
- if (ctx->is_option_set(QUICCLIENTSESSION_OPTION_REQUEST_OCSP))
- SSL_set_tlsext_status_type(ssl.get(), TLSEXT_STATUSTYPE_ocsp);
- break;
- }
- case NGTCP2_CRYPTO_SIDE_SERVER: {
- SSL_set_accept_state(ssl.get());
- if (ctx->is_option_set(QUICSERVERSESSION_OPTION_REQUEST_CERT)) {
- int verify_mode = SSL_VERIFY_PEER;
- if (ctx->is_option_set(QUICSERVERSESSION_OPTION_REJECT_UNAUTHORIZED))
- verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
- SSL_set_verify(ssl.get(), verify_mode, crypto::VerifyCallback);
- }
- break;
- }
- default:
- UNREACHABLE();
- }
-
- ngtcp2_conn_set_tls_native_handle(session->connection(), ssl.get());
- SetTransportParams(session, ssl);
-}
-
-void InitializeSecureContext(
- BaseObjectPtr<crypto::SecureContext> sc,
- bool early_data,
- ngtcp2_crypto_side side) {
- constexpr static unsigned char session_id_ctx[] = "node.js quic server";
- switch (side) {
- case NGTCP2_CRYPTO_SIDE_SERVER:
- SSL_CTX_set_options(
- **sc,
- (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
- SSL_OP_SINGLE_ECDH_USE |
- SSL_OP_CIPHER_SERVER_PREFERENCE |
- SSL_OP_NO_ANTI_REPLAY);
-
- SSL_CTX_set_mode(**sc, SSL_MODE_RELEASE_BUFFERS);
-
- SSL_CTX_set_alpn_select_cb(**sc, AlpnSelection, nullptr);
- SSL_CTX_set_client_hello_cb(**sc, Client_Hello_CB, nullptr);
-
- SSL_CTX_set_session_ticket_cb(
- **sc,
- GenerateSessionTicket,
- DecryptSessionTicket,
- nullptr);
-
- if (early_data) {
- SSL_CTX_set_max_early_data(**sc, 0xffffffff);
- SSL_CTX_set_allow_early_data_cb(**sc, AllowEarlyDataCB, nullptr);
- }
-
- SSL_CTX_set_session_id_context(
- **sc,
- session_id_ctx,
- sizeof(session_id_ctx) - 1);
- break;
- case NGTCP2_CRYPTO_SIDE_CLIENT:
- SSL_CTX_set_session_cache_mode(
- **sc,
- SSL_SESS_CACHE_CLIENT |
- SSL_SESS_CACHE_NO_INTERNAL_STORE);
- SSL_CTX_sess_set_new_cb(**sc, New_Session_Callback);
- break;
- default:
- UNREACHABLE();
- }
- SSL_CTX_set_min_proto_version(**sc, TLS1_3_VERSION);
- SSL_CTX_set_max_proto_version(**sc, TLS1_3_VERSION);
- SSL_CTX_set_default_verify_paths(**sc);
- SSL_CTX_set_tlsext_status_cb(**sc, TLS_Status_Callback);
- SSL_CTX_set_keylog_callback(**sc, Keylog_CB);
- SSL_CTX_set_tlsext_status_arg(**sc, nullptr);
- SSL_CTX_set_quic_method(**sc, &quic_method);
-}
-
-ngtcp2_crypto_level from_ossl_level(OSSL_ENCRYPTION_LEVEL ossl_level) {
- switch (ossl_level) {
- case ssl_encryption_initial:
- return NGTCP2_CRYPTO_LEVEL_INITIAL;
- case ssl_encryption_early_data:
- return NGTCP2_CRYPTO_LEVEL_EARLY;
- case ssl_encryption_handshake:
- return NGTCP2_CRYPTO_LEVEL_HANDSHAKE;
- case ssl_encryption_application:
- return NGTCP2_CRYPTO_LEVEL_APP;
- default:
- UNREACHABLE();
- }
-}
-
-const char* crypto_level_name(ngtcp2_crypto_level level) {
- switch (level) {
- case NGTCP2_CRYPTO_LEVEL_INITIAL:
- return "initial";
- case NGTCP2_CRYPTO_LEVEL_EARLY:
- return "early";
- case NGTCP2_CRYPTO_LEVEL_HANDSHAKE:
- return "handshake";
- case NGTCP2_CRYPTO_LEVEL_APP:
- return "app";
- default:
- UNREACHABLE();
- }
-}
-
-// When using IPv6, QUIC recommends the use of IPv6 Flow Labels
-// as specified in https://tools.ietf.org/html/rfc6437. These
-// are used as a means of reliably associating packets exchanged
-// as part of a single flow and protecting against certain kinds
-// of attacks.
-uint32_t GenerateFlowLabel(
- const SocketAddress& local,
- const SocketAddress& remote,
- const QuicCID& cid,
- const uint8_t* secret,
- size_t secretlen) {
- static constexpr size_t kInfoLen =
- (sizeof(sockaddr_in6) * 2) + NGTCP2_MAX_CIDLEN;
-
- uint32_t label = 0;
-
- std::array<uint8_t, kInfoLen> plaintext;
- size_t infolen = local.length() + remote.length() + cid.length();
- CHECK_LE(infolen, kInfoLen);
-
- ngtcp2_crypto_ctx ctx;
- ngtcp2_crypto_ctx_initial(&ctx);
-
- auto p = std::begin(plaintext);
- p = std::copy_n(local.raw(), local.length(), p);
- p = std::copy_n(remote.raw(), remote.length(), p);
- p = std::copy_n(cid->data, cid->datalen, p);
-
- ngtcp2_crypto_hkdf_expand(
- reinterpret_cast<uint8_t*>(&label),
- sizeof(label),
- &ctx.md,
- secret,
- secretlen,
- plaintext.data(),
- infolen);
-
- label &= kLabelMask;
- DCHECK_LE(label, kLabelMask);
- return label;
-}
-
-} // namespace quic
-} // namespace node
diff --git a/src/quic/node_quic_crypto.h b/src/quic/node_quic_crypto.h
deleted file mode 100644
index e37c5e059a5..00000000000
--- a/src/quic/node_quic_crypto.h
+++ /dev/null
@@ -1,144 +0,0 @@
-#ifndef SRC_QUIC_NODE_QUIC_CRYPTO_H_
-#define SRC_QUIC_NODE_QUIC_CRYPTO_H_
-
-#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#include "node_crypto.h"
-#include "node_quic_util.h"
-#include "v8.h"
-
-#include <ngtcp2/ngtcp2.h>
-#include <ngtcp2/ngtcp2_crypto.h>
-#include <openssl/ssl.h>
-
-namespace node {
-
-namespace quic {
-
-// Crypto and OpenSSL related utility functions used in
-// various places throughout the QUIC implementation.
-
-// Forward declaration
-class QuicSession;
-class QuicPacket;
-
-// many ngtcp2 functions return 0 to indicate success
-// and non-zero to indicate failure. Most of the time,
-// for such functions we don't care about the specific
-// return value so we simplify using a macro.
-
-#define NGTCP2_ERR(V) (V != 0)
-#define NGTCP2_OK(V) (V == 0)
-
-// Called by QuicInitSecureContext to initialize the
-// given SecureContext with the defaults for the given
-// QUIC side (client or server).
-void InitializeSecureContext(
- BaseObjectPtr<crypto::SecureContext> sc,
- bool early_data,
- ngtcp2_crypto_side side);
-
-// Called in the QuicSession::InitServer and
-// QuicSession::InitClient to configure the
-// appropriate settings for the SSL* associated
-// with the session.
-void InitializeTLS(QuicSession* session, const crypto::SSLPointer& ssl);
-
-// Generates a stateless reset token using HKDF with the
-// cid and token secret as input. The token secret is
-// either provided by user code when a QuicSocket is
-// created or is generated randomly.
-//
-// QUIC leaves the generation of stateless session tokens
-// up to the implementation to figure out. The idea, however,
-// is that it ought to be possible to generate a stateless
-// reset token reliably even when all state for a connection
-// has been lost. We use the cid as it's the only reliably
-// consistent bit of data we have when a session is destroyed.
-bool GenerateResetToken(
- uint8_t* token,
- const uint8_t* secret,
- const QuicCID& cid);
-
-// The Retry Token is an encrypted token that is sent to the client
-// by the server as part of the path validation flow. The plaintext
-// format within the token is opaque and only meaningful the server.
-// We can structure it any way we want. It needs to:
-// * be hard to guess
-// * be time limited
-// * be specific to the client address
-// * be specific to the original cid
-// * contain random data.
-std::unique_ptr<QuicPacket> GenerateRetryPacket(
- const uint8_t* token_secret,
- const QuicCID& dcid,
- const QuicCID& scid,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr);
-
-// The IPv6 Flow Label is generated and set whenever IPv6 is used.
-// The label is derived as a cryptographic function of the CID,
-// local and remote addresses, and the given secret, that is then
-// truncated to a 20-bit value (per IPv6 requirements). In QUIC,
-// the flow label *may* be used as a way of disambiguating IP
-// packets that belong to the same flow from a remote peer.
-uint32_t GenerateFlowLabel(
- const SocketAddress& local,
- const SocketAddress& remote,
- const QuicCID& cid,
- const uint8_t* secret,
- size_t secretlen);
-
-// Verifies the validity of a retry token. Returns true if the
-// token is *not valid*, false otherwise. If the token is valid,
-// the ocid will be updated to the original CID value encoded
-// within the successfully validated, encrypted token.
-bool InvalidRetryToken(
- const ngtcp2_vec& token,
- const SocketAddress& addr,
- QuicCID* ocid,
- const uint8_t* token_secret,
- uint64_t verification_expiration);
-
-// Get the ALPN protocol identifier that was negotiated for the session
-v8::Local<v8::Value> GetALPNProtocol(const QuicSession& session);
-
-ngtcp2_crypto_level from_ossl_level(OSSL_ENCRYPTION_LEVEL ossl_level);
-const char* crypto_level_name(ngtcp2_crypto_level level);
-
-// SessionTicketAppData is a utility class that is used only during
-// the generation or access of TLS stateless sesson tickets. It
-// exists solely to provide a easier way for QuicApplication instances
-// to set relevant metadata in the session ticket when it is created,
-// and the exract and subsequently verify that data when a ticket is
-// received and is being validated. The app data is completely opaque
-// to anything other than the server-side of the QuicApplication that
-// sets it.
-class SessionTicketAppData {
- public:
- enum class Status {
- TICKET_USE,
- TICKET_USE_RENEW,
- TICKET_IGNORE,
- TICKET_IGNORE_RENEW
- };
-
- enum class Flag {
- STATUS_NONE,
- STATUS_RENEW
- };
-
- explicit SessionTicketAppData(SSL_SESSION* session) : session_(session) {}
- bool Set(const uint8_t* data, size_t len);
- bool Get(uint8_t** data, size_t* len) const;
-
- private:
- bool set_ = false;
- SSL_SESSION* session_;
-};
-
-} // namespace quic
-} // namespace node
-
-#endif // NODE_WANT_INTERNALS
-#endif // SRC_QUIC_NODE_QUIC_CRYPTO_H_
diff --git a/src/quic/node_quic_default_application.cc b/src/quic/node_quic_default_application.cc
deleted file mode 100644
index 5005d3d6919..00000000000
--- a/src/quic/node_quic_default_application.cc
+++ /dev/null
@@ -1,177 +0,0 @@
-#include "debug_utils-inl.h"
-#include "node_quic_buffer-inl.h"
-#include "node_quic_default_application.h"
-#include "node_quic_session-inl.h"
-#include "node_quic_socket-inl.h"
-#include "node_quic_stream-inl.h"
-#include "node_quic_util-inl.h"
-#include "node_sockaddr-inl.h"
-#include <ngtcp2/ngtcp2.h>
-
-#include <vector>
-
-namespace node {
-namespace quic {
-
-namespace {
-void Consume(ngtcp2_vec** pvec, size_t* pcnt, size_t len) {
- ngtcp2_vec* v = *pvec;
- size_t cnt = *pcnt;
-
- for (; cnt > 0; --cnt, ++v) {
- if (v->len > len) {
- v->len -= len;
- v->base += len;
- break;
- }
- len -= v->len;
- }
-
- *pvec = v;
- *pcnt = cnt;
-}
-
-int IsEmpty(const ngtcp2_vec* vec, size_t cnt) {
- size_t i;
- for (i = 0; i < cnt && vec[i].len == 0; ++i) {}
- return i == cnt;
-}
-} // anonymous namespace
-
-DefaultApplication::DefaultApplication(
- QuicSession* session)
- : QuicApplication(session) {}
-
-bool DefaultApplication::Initialize() {
- if (needs_init()) {
- Debug(session(), "Default QUIC Application Initialized");
- set_init_done();
- }
- return true;
-}
-
-void DefaultApplication::ScheduleStream(int64_t stream_id) {
- BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id);
- if (LIKELY(stream && !stream->is_destroyed())) {
- Debug(session(), "Scheduling stream %" PRIu64, stream_id);
- stream->Schedule(&stream_queue_);
- }
-}
-
-void DefaultApplication::UnscheduleStream(int64_t stream_id) {
- BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id);
- if (LIKELY(stream)) {
- Debug(session(), "Unscheduling stream %" PRIu64, stream_id);
- stream->Unschedule();
- }
-}
-
-void DefaultApplication::ResumeStream(int64_t stream_id) {
- ScheduleStream(stream_id);
-}
-
-bool DefaultApplication::ReceiveStreamData(
- uint32_t flags,
- int64_t stream_id,
- const uint8_t* data,
- size_t datalen,
- uint64_t offset) {
- // Ensure that the QuicStream exists before deferring to
- // QuicApplication specific processing logic.
- Debug(session(), "Default QUIC Application receiving stream data");
- BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id);
- if (!stream) {
- // Shutdown the stream explicitly if the session is being closed.
- if (session()->is_graceful_closing()) {
- session()->ShutdownStream(stream_id, NGTCP2_ERR_CLOSING);
- return true;
- }
-
- // One potential DOS attack vector is to send a bunch of
- // empty stream frames to commit resources. Check that
- // here. Essentially, we only want to create a new stream
- // if the datalen is greater than 0, otherwise, we ignore
- // the packet. ngtcp2 should be handling this for us,
- // but we handle it just to be safe.
- if (UNLIKELY(datalen == 0))
- return true;
-
- stream = session()->CreateStream(stream_id);
- }
- CHECK(stream);
-
- // If the stream ended up being destroyed immediately after
- // creation, just skip the data processing and return.
- if (UNLIKELY(stream->is_destroyed()))
- return true;
-
- stream->ReceiveData(flags, data, datalen, offset);
- return true;
-}
-
-int DefaultApplication::GetStreamData(StreamData* stream_data) {
- QuicStream* stream = stream_queue_.PopFront();
- // If stream is nullptr, there are no streams with data pending.
- if (stream == nullptr)
- return 0;
-
- stream_data->stream.reset(stream);
- stream_data->id = stream->id();
-
- auto next = [&](
- int status,
- const ngtcp2_vec* data,
- size_t count,
- bob::Done done) {
- switch (status) {
- case bob::Status::STATUS_BLOCK:
- // Fall through
- case bob::Status::STATUS_WAIT:
- // Fall through
- case bob::Status::STATUS_EOS:
- return;
- case bob::Status::STATUS_END:
- stream_data->fin = 1;
- }
-
- stream_data->count = count;
-
- if (count > 0) {
- stream->Schedule(&stream_queue_);
- stream_data->remaining = get_length(data, count);
- } else {
- stream_data->remaining = 0;
- }
- };
-
- if (LIKELY(!stream->is_eos())) {
- CHECK_GE(stream->Pull(
- std::move(next),
- bob::Options::OPTIONS_SYNC,
- stream_data->data,
- arraysize(stream_data->data),
- kMaxVectorCount), 0);
- }
-
- return 0;
-}
-
-bool DefaultApplication::StreamCommit(
- StreamData* stream_data,
- size_t datalen) {
- CHECK(stream_data->stream);
- stream_data->remaining -= datalen;
- Consume(&stream_data->buf, &stream_data->count, datalen);
- stream_data->stream->Commit(datalen);
- return true;
-}
-
-bool DefaultApplication::ShouldSetFin(const StreamData& stream_data) {
- if (!stream_data.stream ||
- !IsEmpty(stream_data.buf, stream_data.count))
- return false;
- return !stream_data.stream->is_writable();
-}
-
-} // namespace quic
-} // namespace node
diff --git a/src/quic/node_quic_default_application.h b/src/quic/node_quic_default_application.h
deleted file mode 100644
index 52c9044c0e3..00000000000
--- a/src/quic/node_quic_default_application.h
+++ /dev/null
@@ -1,60 +0,0 @@
-#ifndef SRC_QUIC_NODE_QUIC_DEFAULT_APPLICATION_H_
-#define SRC_QUIC_NODE_QUIC_DEFAULT_APPLICATION_H_
-
-#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#include "node_quic_stream.h"
-#include "node_quic_session.h"
-#include "node_quic_util.h"
-#include "util.h"
-#include "v8.h"
-
-namespace node {
-
-namespace quic {
-
-// The DefaultApplication is used whenever an unknown/unrecognized
-// alpn identifier is used. It switches the QUIC implementation into
-// a minimal/generic mode that defers all application level processing
-// to the user-code level. Headers are not supported by QuicStream
-// instances created under the default application.
-class DefaultApplication final : public QuicApplication {
- public:
- explicit DefaultApplication(QuicSession* session);
-
- bool Initialize() override;
-
- void StopTrackingMemory(void* ptr) override {
- // Do nothing. Not used.
- }
-
- bool ReceiveStreamData(
- uint32_t flags,
- int64_t stream_id,
- const uint8_t* data,
- size_t datalen,
- uint64_t offset) override;
-
- int GetStreamData(StreamData* stream_data) override;
-
- void ResumeStream(int64_t stream_id) override;
- bool ShouldSetFin(const StreamData& stream_data) override;
- bool StreamCommit(StreamData* stream_data, size_t datalen) override;
-
- SET_SELF_SIZE(DefaultApplication)
- SET_MEMORY_INFO_NAME(DefaultApplication)
- SET_NO_MEMORY_INFO()
-
- private:
- void ScheduleStream(int64_t stream_id);
- void UnscheduleStream(int64_t stream_id);
-
- QuicStream::Queue stream_queue_;
-};
-
-} // namespace quic
-
-} // namespace node
-
-#endif // NODE_WANT_INTERNALS
-#endif // SRC_QUIC_NODE_QUIC_DEFAULT_APPLICATION_H_
diff --git a/src/quic/node_quic_http3_application.cc b/src/quic/node_quic_http3_application.cc
deleted file mode 100644
index 82cd677d1ce..00000000000
--- a/src/quic/node_quic_http3_application.cc
+++ /dev/null
@@ -1,941 +0,0 @@
-#include "node.h"
-#include "debug_utils-inl.h"
-#include "node_mem-inl.h"
-#include "node_quic_buffer-inl.h"
-#include "node_quic_http3_application.h"
-#include "node_quic_session-inl.h"
-#include "node_quic_socket-inl.h"
-#include "node_quic_stream-inl.h"
-#include "node_quic_util-inl.h"
-#include "node_sockaddr-inl.h"
-#include "node_http_common-inl.h"
-
-#include <ngtcp2/ngtcp2.h>
-#include <nghttp3/nghttp3.h>
-#include <algorithm>
-
-namespace node {
-
-using v8::Array;
-using v8::Local;
-
-namespace quic {
-
-// nghttp3 uses a numeric identifier for a large number
-// of known HTTP header names. These allow us to use
-// static strings for those rather than allocating new
-// strings all of the time. The list of strings supported
-// is included in node_http_common.h
-#define V1(name, value) case NGHTTP3_QPACK_TOKEN__##name: return value;
-#define V2(name, value) case NGHTTP3_QPACK_TOKEN_##name: return value;
-const char* Http3HeaderTraits::ToHttpHeaderName(int32_t token) {
- switch (token) {
- default:
- // Fall through
- case -1: return nullptr;
- HTTP_SPECIAL_HEADERS(V1)
- HTTP_REGULAR_HEADERS(V2)
- }
-}
-#undef V1
-#undef V2
-
-template <typename M, typename T>
-void Http3Application::SetConfig(
- int idx,
- M T::*member) {
- AliasedFloat64Array& buffer = session()->quic_state()->http3config_buffer;
- uint64_t flags = static_cast<uint64_t>(buffer[IDX_HTTP3_CONFIG_COUNT]);
- if (flags & (1ULL << idx))
- config_.*member = static_cast<uint64_t>(buffer[idx]);
-}
-
-Http3Application::Http3Application(
- QuicSession* session)
- : QuicApplication(session),
- alloc_info_(MakeAllocator()) {
- // Collect Configuration Details.
- SetConfig<size_t>(IDX_HTTP3_QPACK_MAX_TABLE_CAPACITY,
- &Http3ApplicationConfig::qpack_max_table_capacity);
- SetConfig<size_t>(IDX_HTTP3_QPACK_BLOCKED_STREAMS,
- &Http3ApplicationConfig::qpack_blocked_streams);
- SetConfig(IDX_HTTP3_MAX_HEADER_LIST_SIZE,
- &Http3ApplicationConfig::max_field_section_size);
- SetConfig(IDX_HTTP3_MAX_PUSHES,
- &Http3ApplicationConfig::max_pushes);
- SetConfig(IDX_HTTP3_MAX_HEADER_PAIRS,
- &Http3ApplicationConfig::max_header_pairs);
- SetConfig(IDX_HTTP3_MAX_HEADER_LENGTH,
- &Http3ApplicationConfig::max_header_length);
- set_max_header_pairs(
- session->is_server()
- ? GetServerMaxHeaderPairs(config_.max_header_pairs)
- : GetClientMaxHeaderPairs(config_.max_header_pairs));
- set_max_header_length(config_.max_header_length);
-
- session->quic_state()->http3config_buffer[IDX_HTTP3_CONFIG_COUNT] = 0;
-}
-
-// Push streams in HTTP/3 are a bit complicated.
-// First, it's important to know that only an HTTP/3 server can
-// create a push stream.
-// Second, a push stream is essentially an *assumed* request. For
-// instance, if a client requests a webpage that has links to css
-// and js files, and the server expects the client to send subsequent
-// requests for those css and js files, the server can shortcut the
-// process by opening a push stream for each additional resource
-// it assumes the client to make.
-// Third, a push stream can only be opened within the context
-// of an HTTP/3 request/response. Essentially, a server receives
-// a request and while processing the response, the server can
-// open one or more push streams.
-//
-// Now... a push stream consists of two components: a push promise
-// and a push fulfillment. The push promise is sent *as part of
-// the response on the original stream* and is assigned a push id
-// and a block of headers containing the *assumed request headers*.
-// The push promise is sent on the request/response bidirectional
-// stream.
-// The push fulfillment is a unidirectional stream opened by the
-// server that contains the push id, the response header block, and
-// the response payload.
-// Here's where it can get a bit complicated: the server sends the
-// push promise and the push fulfillment on two different, and
-// independent QUIC streams. The push id is used to correlate
-// those on the client side, but, it's entirely possible for the
-// client to receive the push fulfillment before it actually receives
-// the push promise. It's *unlikely*, but it's possible. Fortunately,
-// nghttp3 handles the complexity of that for us internally but
-// makes for some weird timing and could lead to some amount of
-// buffering to occur.
-//
-// The *logical* order of events from the client side *should*
-// be: (a) receive the push promise containing assumed request
-// headers, (b) receive the push fulfillment containing the
-// response headers followed immediately by the response payload.
-//
-// On the server side, the steps are: (a) first create the push
-// promise creating the push_id then (b) open the unidirectional
-// stream that will be used to fullfil the push promise. Once that
-// unidirectional stream is created, the push id and unidirectional
-// stream ID must be bound. The CreateAndBindPushStream handles (b)
-int64_t Http3Application::CreateAndBindPushStream(int64_t push_id) {
- CHECK(session()->is_server());
- int64_t stream_id;
- if (!session()->OpenUnidirectionalStream(&stream_id))
- return 0;
- return nghttp3_conn_bind_push_stream(
- connection(),
- push_id,
- stream_id) == 0 ? stream_id : 0;
-}
-
-bool Http3Application::SubmitPushPromise(
- int64_t id,
- int64_t* push_id,
- int64_t* stream_id,
- const Http3Headers& headers) {
- // Successfully creating the push promise and opening the
- // fulfillment stream will queue nghttp3 up to send data.
- // Creating the SendSessionScope here ensures that when
- // SubmitPush exits, SendPendingData will be called if
- // we are not within the context of an ngtcp2 callback.
- QuicSession::SendSessionScope send_scope(session());
-
- Debug(
- session(),
- "Submitting %d push promise headers",
- headers.length());
- if (nghttp3_conn_submit_push_promise(
- connection(),
- push_id,
- id,
- headers.data(),
- headers.length()) != 0) {
- return false;
- }
- // Once we've successfully submitting the push promise and have
- // a push id assigned, we create the push fulfillment stream.
- *stream_id = CreateAndBindPushStream(*push_id);
- return *stream_id != 0; // push stream can never use stream id 0
-}
-
-bool Http3Application::SubmitInformation(
- int64_t id,
- const Http3Headers& headers) {
- QuicSession::SendSessionScope send_scope(session());
- Debug(
- session(),
- "Submitting %d informational headers for stream %" PRId64,
- headers.length(),
- id);
- return nghttp3_conn_submit_info(
- connection(),
- id,
- headers.data(),
- headers.length()) == 0;
-}
-
-bool Http3Application::SubmitTrailers(
- int64_t id,
- const Http3Headers& headers) {
- QuicSession::SendSessionScope send_scope(session());
- Debug(
- session(),
- "Submitting %d trailing headers for stream %" PRId64,
- headers.length(),
- id);
- return nghttp3_conn_submit_trailers(
- connection(),
- id,
- headers.data(),
- headers.length()) == 0;
-}
-
-bool Http3Application::SubmitHeaders(
- int64_t id,
- const Http3Headers& headers,
- int32_t flags) {
- QuicSession::SendSessionScope send_scope(session());
- static constexpr nghttp3_data_reader reader = {
- Http3Application::OnReadData };
- const nghttp3_data_reader* reader_ptr = nullptr;
- if (!(flags & QUICSTREAM_HEADER_FLAGS_TERMINAL))
- reader_ptr = &reader;
-
- switch (session()->crypto_context()->side()) {
- case NGTCP2_CRYPTO_SIDE_CLIENT:
- return nghttp3_conn_submit_request(
- connection(),
- id,
- headers.data(),
- headers.length(),
- reader_ptr,
- nullptr) == 0;
- case NGTCP2_CRYPTO_SIDE_SERVER:
- return nghttp3_conn_submit_response(
- connection(),
- id,
- headers.data(),
- headers.length(),
- reader_ptr) == 0;
- default:
- UNREACHABLE();
- }
-}
-
-// SubmitPush initiates a push stream by first creating a push promise
-// with an associated push id, then opening the unidirectional stream
-// that is used to fullfill it. Assuming both operations are successful,
-// the QuicStream instance is created and added to the server QuicSession.
-//
-// The headers block passed to the submit push contains the assumed
-// *request* headers. The response headers are provided using the
-// SubmitHeaders() function on the created QuicStream.
-BaseObjectPtr<QuicStream> Http3Application::SubmitPush(
- int64_t id,
- Local<Array> headers) {
- // If the QuicSession is not a server session, return false
- // immediately. Push streams cannot be sent by an HTTP/3 client.
- if (!session()->is_server())
- return {};
-
- Http3Headers nva(env(), headers);
- int64_t push_id;
- int64_t stream_id;
-
- // There are several reasons why push may fail. We currently handle
- // them all the same. Later we might want to differentiate when the
- // return value is NGHTTP3_ERR_PUSH_ID_BLOCKED.
- return SubmitPushPromise(id, &push_id, &stream_id, nva) ?
- QuicStream::New(session(), stream_id, push_id) :
- BaseObjectPtr<QuicStream>();
-}
-
-// Submit informational headers (response headers that use a 1xx
-// status code). If the QuicSession is not a server session, return
-// false immediately because info headers cannot be sent by a
-// client
-bool Http3Application::SubmitInformation(
- int64_t stream_id,
- Local<Array> headers) {
- if (!session()->is_server())
- return false;
- Http3Headers nva(session()->env(), headers);
- return SubmitInformation(stream_id, nva);
-}
-
-// For client sessions, submits request headers. For server sessions,
-// submits response headers.
-bool Http3Application::SubmitHeaders(
- int64_t stream_id,
- Local<Array> headers,
- uint32_t flags) {
- Http3Headers nva(session()->env(), headers);
- return SubmitHeaders(stream_id, nva, flags);
-}
-
-// Submits trailing headers for the HTTP/3 request or response.
-bool Http3Application::SubmitTrailers(
- int64_t stream_id,
- Local<Array> headers) {
- Http3Headers nva(session()->env(), headers);
- return SubmitTrailers(stream_id, nva);
-}
-
-void Http3Application::CheckAllocatedSize(size_t previous_size) const {
- CHECK_GE(current_nghttp3_memory_, previous_size);
-}
-
-void Http3Application::IncreaseAllocatedSize(size_t size) {
- current_nghttp3_memory_ += size;
-}
-
-void Http3Application::DecreaseAllocatedSize(size_t size) {
- current_nghttp3_memory_ -= size;
-}
-
-void Http3Application::MemoryInfo(MemoryTracker* tracker) const {
- tracker->TrackFieldWithSize("current_nghttp3_memory",
- current_nghttp3_memory_);
-}
-
-// Creates the underlying nghttp3 connection state for the session.
-void Http3Application::CreateConnection() {
- // nghttp3_conn_server_new and nghttp3_conn_client_new share
- // identical definitions, so new_fn will work for both.
- using new_fn = decltype(&nghttp3_conn_server_new);
- static new_fn fns[] = {
- nghttp3_conn_client_new, // NGTCP2_CRYPTO_SIDE_CLIENT
- nghttp3_conn_server_new, // NGTCP2_CRYPTO_SIDE_SERVER
- };
-
- ngtcp2_crypto_side side = session()->crypto_context()->side();
- nghttp3_conn* conn;
-
- CHECK_EQ(fns[side](
- &conn,
- &callbacks_[side],
- &config_,
- &alloc_info_,
- this), 0);
- CHECK_NOT_NULL(conn);
- connection_.reset(conn);
-}
-
-// The HTTP/3 QUIC binding uses a single unidirectional control
-// stream in each direction to exchange frames impacting the entire
-// connection.
-bool Http3Application::CreateAndBindControlStream() {
- if (!session()->OpenUnidirectionalStream(&control_stream_id_))
- return false;
- Debug(
- session(),
- "Open stream %" PRId64 " and bind as control stream",
- control_stream_id_);
- return nghttp3_conn_bind_control_stream(
- connection(),
- control_stream_id_) == 0;
-}
-
-// The HTTP/3 QUIC binding creates two unidirectional streams in
-// each direction to exchange header compression details.
-bool Http3Application::CreateAndBindQPackStreams() {
- if (!session()->OpenUnidirectionalStream(&qpack_enc_stream_id_) ||
- !session()->OpenUnidirectionalStream(&qpack_dec_stream_id_)) {
- return false;
- }
- Debug(
- session(),
- "Open streams %" PRId64 " and %" PRId64 " and bind as qpack streams",
- qpack_enc_stream_id_,
- qpack_dec_stream_id_);
- return nghttp3_conn_bind_qpack_streams(
- connection(),
- qpack_enc_stream_id_,
- qpack_dec_stream_id_) == 0;
-}
-
-bool Http3Application::Initialize() {
- if (!needs_init())
- return false;
-
- // The QuicSession must allow for at least three local unidirectional streams.
- // This number is fixed by the http3 specification and represent the
- // control stream and two qpack management streams.
- if (session()->max_local_streams_uni() < 3)
- return false;
-
- Debug(session(), "QPack Max Table Capacity: %" PRIu64,
- config_.qpack_max_table_capacity);
- Debug(session(), "QPack Blocked Streams: %" PRIu64,
- config_.qpack_blocked_streams);
- Debug(session(), "Max Header List Size: %" PRIu64,
- config_.max_field_section_size);
- Debug(session(), "Max Pushes: %" PRIu64,
- config_.max_pushes);
-
- CreateConnection();
- Debug(session(), "HTTP/3 connection created");
-
- ngtcp2_transport_params params;
- session()->GetLocalTransportParams(&params);
-
- if (session()->is_server()) {
- nghttp3_conn_set_max_client_streams_bidi(
- connection(),
- params.initial_max_streams_bidi);
- }
-
- if (!CreateAndBindControlStream() ||
- !CreateAndBindQPackStreams()) {
- return false;
- }
-
- set_init_done();
- return true;
-}
-
-// All HTTP/3 control, header, and stream data arrives as QUIC stream data.
-// Here we pass the received data off to nghttp3 for processing. This will
-// trigger the invocation of the various nghttp3 callbacks.
-bool Http3Application::ReceiveStreamData(
- uint32_t flags,
- int64_t stream_id,
- const uint8_t* data,
- size_t datalen,
- uint64_t offset) {
- Debug(session(), "Receiving %" PRIu64 " bytes for stream %" PRIu64 "%s",
- datalen,
- stream_id,
- flags & NGTCP2_STREAM_DATA_FLAG_FIN ? " (fin)" : "");
- ssize_t nread =
- nghttp3_conn_read_stream(
- connection(),
- stream_id,
- data,
- datalen,
- flags & NGTCP2_STREAM_DATA_FLAG_FIN);
- if (nread < 0) {
- Debug(session(), "Failure to read HTTP/3 Stream Data [%" PRId64 "]", nread);
- return false;
- }
-
- return true;
-}
-
-// This is the QUIC-level stream data acknowledgement. It is called for
-// all streams, including unidirectional streams. This has to forward on
-// to nghttp3 for processing. The Http3Application::AckedStreamData might
-// be called as a result to acknowledge (and free) QuicStream data.
-void Http3Application::AcknowledgeStreamData(
- int64_t stream_id,
- uint64_t offset,
- size_t datalen) {
- if (nghttp3_conn_add_ack_offset(connection(), stream_id, datalen) != 0)
- Debug(session(), "Failure to acknowledge HTTP/3 Stream Data");
-}
-
-void Http3Application::StreamClose(
- int64_t stream_id,
- uint64_t app_error_code) {
- if (app_error_code == 0)
- app_error_code = NGTCP2_APP_NOERROR;
- nghttp3_conn_close_stream(connection(), stream_id, app_error_code);
- QuicApplication::StreamClose(stream_id, app_error_code);
-}
-
-void Http3Application::StreamReset(
- int64_t stream_id,
- uint64_t app_error_code) {
- nghttp3_conn_reset_stream(connection(), stream_id);
- QuicApplication::StreamReset(stream_id, app_error_code);
-}
-
-// When SendPendingData tries to send data for a given stream and there
-// is no data to send but the QuicStream is still writable, it will
-// be paused. When there's data available, the stream is resumed.
-void Http3Application::ResumeStream(int64_t stream_id) {
- nghttp3_conn_resume_stream(connection(), stream_id);
-}
-
-// When stream data cannot be sent because of flow control, it is marked
-// as being blocked. When the flow control windows expands, nghttp3 has
-// to be told to unblock the stream so it knows to try sending data again.
-void Http3Application::ExtendMaxStreamData(
- int64_t stream_id,
- uint64_t max_data) {
- nghttp3_conn_unblock_stream(connection(), stream_id);
-}
-
-// When stream data cannot be sent because of flow control, it is marked
-// as being blocked.
-bool Http3Application::BlockStream(int64_t stream_id) {
- int err = nghttp3_conn_block_stream(connection(), stream_id);
- if (err != 0) {
- session()->set_last_error(QUIC_ERROR_APPLICATION, err);
- return false;
- }
- return true;
-}
-
-// nghttp3 keeps track of how much QuicStream data it has available and
-// has sent. StreamCommit is called when a QuicPacket is serialized
-// and updates nghttp3's internal state.
-bool Http3Application::StreamCommit(StreamData* stream_data, size_t datalen) {
- int err = nghttp3_conn_add_write_offset(
- connection(),
- stream_data->id,
- datalen);
- if (err != 0) {
- session()->set_last_error(QUIC_ERROR_APPLICATION, err);
- return false;
- }
- return true;
-}
-
-// GetStreamData is called by SendPendingData to collect the QuicStream data
-// that is to be packaged into a serialized QuicPacket. There may or may not
-// be any stream data to send. The call to nghttp3_conn_writev_stream will
-// provide any available stream data (if any). If nghttp3 is not sure if
-// there is data to send, it will subsequently call Http3Application::ReadData
-// to collect available data from the QuicStream.
-int Http3Application::GetStreamData(StreamData* stream_data) {
- ssize_t ret = 0;
- if (connection() && session()->max_data_left()) {
- ret = nghttp3_conn_writev_stream(
- connection(),
- &stream_data->id,
- &stream_data->fin,
- reinterpret_cast<nghttp3_vec*>(stream_data->data),
- sizeof(stream_data->data));
- if (ret < 0)
- return static_cast<int>(ret);
- else
- stream_data->remaining = stream_data->count = static_cast<size_t>(ret);
- }
- if (stream_data->id > -1) {
- Debug(session(), "Selected %" PRId64 " buffers for stream %" PRId64 "%s",
- stream_data->count,
- stream_data->id,
- stream_data->fin == 1 ? " (fin)" : "");
- }
- return 0;
-}
-
-// Determines whether SendPendingData should set fin on the QuicStream
-bool Http3Application::ShouldSetFin(const StreamData& stream_data) {
- return stream_data.id > -1 &&
- !is_control_stream(stream_data.id) &&
- stream_data.fin == 1;
-}
-
-// This is where nghttp3 pulls the data from the outgoing
-// buffer to prepare it to be sent on the QUIC stream.
-ssize_t Http3Application::ReadData(
- int64_t stream_id,
- nghttp3_vec* vec,
- size_t veccnt,
- uint32_t* pflags) {
- BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id);
- CHECK(stream);
-
- ssize_t ret = NGHTTP3_ERR_WOULDBLOCK;
-
- auto next = [&](
- int status,
- const ngtcp2_vec* data,
- size_t count,
- bob::Done done) {
- CHECK_LE(count, veccnt);
-
- switch (status) {
- case bob::Status::STATUS_BLOCK:
- // Fall through
- case bob::Status::STATUS_WAIT:
- // Fall through
- case bob::Status::STATUS_EOS:
- return;
- case bob::Status::STATUS_END:
- *pflags |= NGHTTP3_DATA_FLAG_EOF;
- break;
- }
-
- ret = count;
- size_t numbytes =
- nghttp3_vec_len(
- reinterpret_cast<const nghttp3_vec*>(data),
- count);
- std::move(done)(numbytes);
-
- Debug(session(), "Sending %" PRIu64 " bytes for stream %" PRId64,
- numbytes, stream_id);
- };
-
- CHECK_GE(stream->Pull(
- std::move(next),
- // Set OPTIONS_END here because nghttp3 takes over responsibility
- // for ensuring the data all gets written out.
- bob::Options::OPTIONS_END | bob::Options::OPTIONS_SYNC,
- reinterpret_cast<ngtcp2_vec*>(vec),
- veccnt,
- kMaxVectorCount), 0);
-
- return ret;
-}
-
-// Outgoing data is retained in memory until it is acknowledged.
-void Http3Application::AckedStreamData(int64_t stream_id, size_t datalen) {
- Acknowledge(stream_id, 0, datalen);
-}
-
-void Http3Application::StreamClosed(
- int64_t stream_id,
- uint64_t app_error_code) {
- BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id);
- if (stream)
- stream->ReceiveData(1, nullptr, 0, 0);
-}
-
-BaseObjectPtr<QuicStream> Http3Application::FindOrCreateStream(
- int64_t stream_id) {
- BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id);
- if (!stream) {
- if (session()->is_graceful_closing()) {
- nghttp3_conn_close_stream(connection(), stream_id, NGTCP2_ERR_CLOSING);
- return {};
- }
- stream = session()->CreateStream(stream_id);
- nghttp3_conn_set_stream_user_data(connection(), stream_id, stream.get());
- }
- CHECK(stream);
- return stream;
-}
-
-void Http3Application::ReceiveData(
- int64_t stream_id,
- const uint8_t* data,
- size_t datalen) {
- FindOrCreateStream(stream_id)->ReceiveData(0, data, datalen, 0);
-}
-
-void Http3Application::DeferredConsume(
- int64_t stream_id,
- size_t consumed) {
- // Do nothing here for now. nghttp3 uses the on_deferred_consume
- // callback to notify when stream data that had previously been
- // deferred has been delivered to the application so that the
- // stream data offset can be extended. However, we extend the
- // data offset from within QuicStream when the data is delivered
- // so we don't have to do it here.
-}
-
-// Called when a nghttp3 detects that a new block of headers
-// has been received. Http3Application::ReceiveHeader will
-// be called for each name+value pair received, then
-// Http3Application::EndHeaders will be called to finalize
-// the header block.
-void Http3Application::BeginHeaders(
- int64_t stream_id,
- QuicStreamHeadersKind kind) {
- Debug(session(), "Starting header block for stream %" PRId64, stream_id);
- FindOrCreateStream(stream_id)->BeginHeaders(kind);
-}
-
-// As each header name+value pair is received, it is stored internally
-// by the QuicStream until stream->EndHeaders() is called, during which
-// the collected headers are converted to an array and passed off to
-// the javascript side.
-bool Http3Application::ReceiveHeader(
- int64_t stream_id,
- int32_t token,
- nghttp3_rcbuf* name,
- nghttp3_rcbuf* value,
- uint8_t flags) {
- // Protect against zero-length headers (zero-length if either the
- // name or value are zero-length). Such headers are simply ignored.
- if (!Http3Header::IsZeroLength(name, value)) {
- Debug(session(), "Receiving header for stream %" PRId64, stream_id);
- BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id);
- CHECK(stream);
- if (token == NGHTTP3_QPACK_TOKEN__STATUS) {
- nghttp3_vec vec = nghttp3_rcbuf_get_buf(value);
- if (vec.base[0] == '1')
- stream->set_headers_kind(QUICSTREAM_HEADERS_KIND_INFORMATIONAL);
- else
- stream->set_headers_kind(QUICSTREAM_HEADERS_KIND_INITIAL);
- }
- auto header = std::make_unique<Http3Header>(
- session()->env(),
- token,
- name,
- value,
- flags);
- return stream->AddHeader(std::move(header));
- }
- return true;
-}
-
-// Marks the completion of a headers block.
-void Http3Application::EndHeaders(int64_t stream_id, int64_t push_id) {
- Debug(session(), "Ending header block for stream %" PRId64, stream_id);
- BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id);
- CHECK(stream);
- stream->EndHeaders();
-}
-
-void Http3Application::CancelPush(
- int64_t push_id,
- int64_t stream_id) {
- Debug(session(), "push stream canceled");
-}
-
-void Http3Application::PushStream(
- int64_t push_id,
- int64_t stream_id) {
- Debug(session(), "Received push stream %" PRIu64 " (%" PRIu64 ")",
- stream_id, push_id);
-}
-
-void Http3Application::SendStopSending(
- int64_t stream_id,
- uint64_t app_error_code) {
- ngtcp2_conn_shutdown_stream_read(
- session()->connection(),
- stream_id,
- app_error_code);
-}
-
-void Http3Application::EndStream(int64_t stream_id) {
- BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id);
- CHECK(stream);
- stream->ReceiveData(1, nullptr, 0, 0);
-}
-
-const nghttp3_conn_callbacks Http3Application::callbacks_[2] = {
- // NGTCP2_CRYPTO_SIDE_CLIENT
- {
- OnAckedStreamData,
- OnStreamClose,
- OnReceiveData,
- OnDeferredConsume,
- OnBeginHeaders,
- OnReceiveHeader,
- OnEndHeaders,
- OnBeginTrailers, // Begin Trailers
- OnReceiveHeader, // Receive Trailer
- OnEndHeaders, // End Trailers
- OnBeginPushPromise,
- OnReceivePushPromise,
- OnEndPushPromise,
- OnCancelPush,
- OnSendStopSending,
- OnPushStream,
- OnEndStream
- },
- // NGTCP2_CRYPTO_SIDE_SERVER
- {
- OnAckedStreamData,
- OnStreamClose,
- OnReceiveData,
- OnDeferredConsume,
- OnBeginHeaders,
- OnReceiveHeader,
- OnEndHeaders,
- OnBeginTrailers, // Begin Trailers
- OnReceiveHeader, // Receive Trailer
- OnEndHeaders, // End Trailers
- OnBeginPushPromise,
- OnReceivePushPromise,
- OnEndPushPromise,
- OnCancelPush,
- OnSendStopSending,
- OnPushStream,
- OnEndStream
- }
-};
-
-int Http3Application::OnAckedStreamData(
- nghttp3_conn* conn,
- int64_t stream_id,
- size_t datalen,
- void* conn_user_data,
- void* stream_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- app->AckedStreamData(stream_id, datalen);
- return 0;
-}
-
-int Http3Application::OnStreamClose(
- nghttp3_conn* conn,
- int64_t stream_id,
- uint64_t app_error_code,
- void* conn_user_data,
- void* stream_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- app->StreamClosed(stream_id, app_error_code);
- return 0;
-}
-
-int Http3Application::OnReceiveData(
- nghttp3_conn* conn,
- int64_t stream_id,
- const uint8_t* data,
- size_t datalen,
- void* conn_user_data,
- void* stream_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- app->ReceiveData(stream_id, data, datalen);
- return 0;
-}
-
-int Http3Application::OnDeferredConsume(
- nghttp3_conn* conn,
- int64_t stream_id,
- size_t consumed,
- void* conn_user_data,
- void* stream_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- app->DeferredConsume(stream_id, consumed);
- return 0;
-}
-
-int Http3Application::OnBeginHeaders(
- nghttp3_conn* conn,
- int64_t stream_id,
- void* conn_user_data,
- void* stream_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- app->BeginHeaders(stream_id);
- return 0;
-}
-
-int Http3Application::OnBeginTrailers(
- nghttp3_conn* conn,
- int64_t stream_id,
- void* conn_user_data,
- void* stream_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- app->BeginHeaders(stream_id, QUICSTREAM_HEADERS_KIND_TRAILING);
- return 0;
-}
-
-int Http3Application::OnReceiveHeader(
- nghttp3_conn* conn,
- int64_t stream_id,
- int32_t token,
- nghttp3_rcbuf* name,
- nghttp3_rcbuf* value,
- uint8_t flags,
- void* conn_user_data,
- void* stream_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- // TODO(@jasnell): Need to determine the appropriate response code here
- // for when the header is not going to be accepted.
- return app->ReceiveHeader(stream_id, token, name, value, flags) ?
- 0 : NGHTTP3_ERR_CALLBACK_FAILURE;
-}
-
-int Http3Application::OnEndHeaders(
- nghttp3_conn* conn,
- int64_t stream_id,
- void* conn_user_data,
- void* stream_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- app->EndHeaders(stream_id);
- return 0;
-}
-
-int Http3Application::OnBeginPushPromise(
- nghttp3_conn* conn,
- int64_t stream_id,
- int64_t push_id,
- void* conn_user_data,
- void* stream_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- app->BeginHeaders(stream_id, QUICSTREAM_HEADERS_KIND_PUSH);
- return 0;
-}
-
-int Http3Application::OnReceivePushPromise(
- nghttp3_conn* conn,
- int64_t stream_id,
- int64_t push_id,
- int32_t token,
- nghttp3_rcbuf* name,
- nghttp3_rcbuf* value,
- uint8_t flags,
- void* conn_user_data,
- void* stream_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- if (!app->ReceiveHeader(stream_id, token, name, value, flags))
- return NGHTTP3_ERR_CALLBACK_FAILURE;
- return 0;
-}
-
-int Http3Application::OnEndPushPromise(
- nghttp3_conn* conn,
- int64_t stream_id,
- int64_t push_id,
- void* conn_user_data,
- void* stream_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- app->EndHeaders(stream_id, push_id);
- return 0;
-}
-
-int Http3Application::OnCancelPush(
- nghttp3_conn* conn,
- int64_t push_id,
- int64_t stream_id,
- void* conn_user_data,
- void* stream_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- app->CancelPush(push_id, stream_id);
- return 0;
-}
-
-int Http3Application::OnSendStopSending(
- nghttp3_conn* conn,
- int64_t stream_id,
- uint64_t app_error_code,
- void* conn_user_data,
- void* stream_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- app->SendStopSending(stream_id, app_error_code);
- return 0;
-}
-
-int Http3Application::OnPushStream(
- nghttp3_conn* conn,
- int64_t push_id,
- int64_t stream_id,
- void* conn_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- app->PushStream(push_id, stream_id);
- return 0;
-}
-
-int Http3Application::OnEndStream(
- nghttp3_conn* conn,
- int64_t stream_id,
- void* conn_user_data,
- void* stream_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- app->EndStream(stream_id);
- return 0;
-}
-
-ssize_t Http3Application::OnReadData(
- nghttp3_conn* conn,
- int64_t stream_id,
- nghttp3_vec* vec,
- size_t veccnt,
- uint32_t* pflags,
- void* conn_user_data,
- void* stream_user_data) {
- Http3Application* app = static_cast<Http3Application*>(conn_user_data);
- return app->ReadData(stream_id, vec, veccnt, pflags);
-}
-} // namespace quic
-} // namespace node
diff --git a/src/quic/node_quic_http3_application.h b/src/quic/node_quic_http3_application.h
deleted file mode 100644
index 35387414625..00000000000
--- a/src/quic/node_quic_http3_application.h
+++ /dev/null
@@ -1,331 +0,0 @@
-#ifndef SRC_QUIC_NODE_QUIC_HTTP3_APPLICATION_H_
-#define SRC_QUIC_NODE_QUIC_HTTP3_APPLICATION_H_
-
-#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#include "node.h"
-#include "node_http_common.h"
-#include "node_mem.h"
-#include "node_quic_session.h"
-#include "node_quic_stream-inl.h"
-#include "node_quic_util.h"
-#include "v8.h"
-#include <ngtcp2/ngtcp2.h>
-#include <nghttp3/nghttp3.h>
-
-namespace node {
-
-namespace quic {
-
-constexpr uint64_t DEFAULT_QPACK_MAX_TABLE_CAPACITY = 4096;
-constexpr uint64_t DEFAULT_QPACK_BLOCKED_STREAMS = 100;
-constexpr size_t DEFAULT_MAX_HEADER_LIST_SIZE = 65535;
-constexpr size_t DEFAULT_MAX_PUSHES = 65535;
-
-struct Http3RcBufferPointerTraits {
- typedef nghttp3_rcbuf rcbuf_t;
- typedef nghttp3_vec vector_t;
-
- static void inc(rcbuf_t* buf) {
- nghttp3_rcbuf_incref(buf);
- }
- static void dec(rcbuf_t* buf) {
- nghttp3_rcbuf_decref(buf);
- }
- static vector_t get_vec(const rcbuf_t* buf) {
- return nghttp3_rcbuf_get_buf(buf);
- }
- static bool is_static(const rcbuf_t* buf) {
- return nghttp3_rcbuf_is_static(buf);
- }
-};
-
-struct Http3HeadersTraits {
- typedef nghttp3_nv nv_t;
-};
-
-using Http3ConnectionPointer = DeleteFnPtr<nghttp3_conn, nghttp3_conn_del>;
-using Http3RcBufferPointer = NgRcBufPointer<Http3RcBufferPointerTraits>;
-using Http3Headers = NgHeaders<Http3HeadersTraits>;
-
-struct Http3HeaderTraits {
- typedef Http3RcBufferPointer rcbufferpointer_t;
- typedef QuicApplication allocator_t;
-
- static const char* ToHttpHeaderName(int32_t token);
-};
-
-using Http3Header = NgHeader<Http3HeaderTraits>;
-
-struct Http3ApplicationConfig : public nghttp3_conn_settings {
- Http3ApplicationConfig() {
- nghttp3_conn_settings_default(this);
- qpack_max_table_capacity = DEFAULT_QPACK_MAX_TABLE_CAPACITY;
- qpack_blocked_streams = DEFAULT_QPACK_BLOCKED_STREAMS;
- max_field_section_size = DEFAULT_MAX_HEADER_LIST_SIZE;
- max_pushes = DEFAULT_MAX_PUSHES;
- }
- uint64_t max_header_pairs = DEFAULT_MAX_HEADER_LIST_PAIRS;
- uint64_t max_header_length = DEFAULT_MAX_HEADER_LENGTH;
-};
-
-class Http3Application;
-using Http3MemoryManager =
- mem::NgLibMemoryManager<Http3Application, nghttp3_mem>;
-
-// Http3Application is used whenever the h3 alpn identifier is used.
-// It causes the QuicSession to apply HTTP/3 semantics to the connection,
-// including handling of headers and other HTTP/3 specific processing.
-class Http3Application final :
- public QuicApplication,
- public Http3MemoryManager {
- public:
- explicit Http3Application(QuicSession* session);
-
- bool Initialize() override;
-
- void StopTrackingMemory(void* ptr) override {
- Http3MemoryManager::StopTrackingMemory(ptr);
- }
-
- bool ReceiveStreamData(
- uint32_t flags,
- int64_t stream_id,
- const uint8_t* data,
- size_t datalen,
- uint64_t offset) override;
-
- void AcknowledgeStreamData(
- int64_t stream_id,
- uint64_t offset,
- size_t datalen) override;
-
- void StreamClose(int64_t stream_id, uint64_t app_error_code) override;
-
- void StreamReset(
- int64_t stream_id,
- uint64_t app_error_code) override;
-
- void ResumeStream(int64_t stream_id) override;
-
- void ExtendMaxStreamData(int64_t stream_id, uint64_t max_data) override;
-
- bool SubmitInformation(
- int64_t stream_id,
- v8::Local<v8::Array> headers) override;
-
- bool SubmitHeaders(
- int64_t stream_id,
- v8::Local<v8::Array> headers,
- uint32_t flags) override;
-
- bool SubmitTrailers(
- int64_t stream_id,
- v8::Local<v8::Array> headers) override;
-
- BaseObjectPtr<QuicStream> SubmitPush(
- int64_t id,
- v8::Local<v8::Array> headers) override;
-
- // Implementation for mem::NgLibMemoryManager
- void CheckAllocatedSize(size_t previous_size) const;
- void IncreaseAllocatedSize(size_t size);
- void DecreaseAllocatedSize(size_t size);
-
- SET_SELF_SIZE(Http3Application)
- SET_MEMORY_INFO_NAME(Http3Application)
- void MemoryInfo(MemoryTracker* tracker) const override;
-
- private:
- template <typename M = uint64_t, typename T>
- void SetConfig(int idx, M T::*member);
-
- nghttp3_conn* connection() const { return connection_.get(); }
- BaseObjectPtr<QuicStream> FindOrCreateStream(int64_t stream_id);
-
- bool CreateAndBindControlStream();
- bool CreateAndBindQPackStreams();
- int64_t CreateAndBindPushStream(int64_t push_id);
-
- int GetStreamData(StreamData* stream_data) override;
-
- bool BlockStream(int64_t stream_id) override;
- bool StreamCommit(StreamData* stream_data, size_t datalen) override;
- bool ShouldSetFin(const StreamData& data) override;
- bool SubmitPushPromise(
- int64_t id,
- int64_t* push_id,
- int64_t* stream_id,
- const Http3Headers& headers);
- bool SubmitInformation(int64_t id, const Http3Headers& headers);
- bool SubmitTrailers(int64_t id, const Http3Headers& headers);
- bool SubmitHeaders(int64_t id, const Http3Headers& headers, int32_t flags);
-
- ssize_t ReadData(
- int64_t stream_id,
- nghttp3_vec* vec,
- size_t veccnt,
- uint32_t* pflags);
-
- void AckedStreamData(int64_t stream_id, size_t datalen);
- void StreamClosed(int64_t stream_id, uint64_t app_error_code);
- void ReceiveData(int64_t stream_id, const uint8_t* data, size_t datalen);
- void DeferredConsume(int64_t stream_id, size_t consumed);
- void BeginHeaders(
- int64_t stream_id,
- QuicStreamHeadersKind kind = QUICSTREAM_HEADERS_KIND_NONE);
- bool ReceiveHeader(
- int64_t stream_id,
- int32_t token,
- nghttp3_rcbuf* name,
- nghttp3_rcbuf* value,
- uint8_t flags);
- void EndHeaders(int64_t stream_id, int64_t push_id = 0);
- void CancelPush(int64_t push_id, int64_t stream_id);
- void SendStopSending(int64_t stream_id, uint64_t app_error_code);
- void PushStream(int64_t push_id, int64_t stream_id);
- void EndStream(int64_t stream_id);
-
- bool is_control_stream(int64_t stream_id) const {
- return stream_id == control_stream_id_ ||
- stream_id == qpack_dec_stream_id_ ||
- stream_id == qpack_enc_stream_id_;
- }
-
- nghttp3_mem alloc_info_;
- Http3ConnectionPointer connection_;
- int64_t control_stream_id_;
- int64_t qpack_enc_stream_id_;
- int64_t qpack_dec_stream_id_;
- size_t current_nghttp3_memory_ = 0;
-
- Http3ApplicationConfig config_;
-
- void CreateConnection();
-
- static const nghttp3_conn_callbacks callbacks_[2];
-
- static int OnAckedStreamData(
- nghttp3_conn* conn,
- int64_t stream_id,
- size_t datalen,
- void* conn_user_data,
- void* stream_user_data);
-
- static int OnStreamClose(
- nghttp3_conn* conn,
- int64_t stream_id,
- uint64_t app_error_code,
- void* conn_user_data,
- void* stream_user_data);
-
- static int OnReceiveData(
- nghttp3_conn* conn,
- int64_t stream_id,
- const uint8_t* data,
- size_t datalen,
- void* conn_user_data,
- void* stream_user_data);
-
- static int OnDeferredConsume(
- nghttp3_conn* conn,
- int64_t stream_id,
- size_t consumed,
- void* conn_user_data,
- void* stream_user_data);
-
- static int OnBeginHeaders(
- nghttp3_conn* conn,
- int64_t stream_id,
- void* conn_user_data,
- void* stream_user_data);
-
- static int OnBeginTrailers(
- nghttp3_conn* conn,
- int64_t stream_id,
- void* conn_user_data,
- void* stream_user_data);
-
- static int OnReceiveHeader(
- nghttp3_conn* conn,
- int64_t stream_id,
- int32_t token,
- nghttp3_rcbuf* name,
- nghttp3_rcbuf* value,
- uint8_t flags,
- void* conn_user_data,
- void* stream_user_data);
-
- static int OnEndHeaders(
- nghttp3_conn* conn,
- int64_t stream_id,
- void* conn_user_data,
- void* stream_user_data);
-
- static int OnBeginPushPromise(
- nghttp3_conn* conn,
- int64_t stream_id,
- int64_t push_id,
- void* conn_user_data,
- void* stream_user_data);
-
- static int OnReceivePushPromise(
- nghttp3_conn* conn,
- int64_t stream_id,
- int64_t push_id,
- int32_t token,
- nghttp3_rcbuf* name,
- nghttp3_rcbuf* value,
- uint8_t flags,
- void* conn_user_data,
- void* stream_user_data);
-
- static int OnEndPushPromise(
- nghttp3_conn* conn,
- int64_t stream_id,
- int64_t push_id,
- void* conn_user_data,
- void* stream_user_data);
-
- static int OnCancelPush(
- nghttp3_conn* conn,
- int64_t push_id,
- int64_t stream_id,
- void* conn_user_data,
- void* stream_user_data);
-
- static int OnSendStopSending(
- nghttp3_conn* conn,
- int64_t stream_id,
- uint64_t app_error_code,
- void* conn_user_data,
- void* stream_user_data);
-
- static int OnPushStream(
- nghttp3_conn* conn,
- int64_t push_id,
- int64_t stream_id,
- void* conn_user_data);
-
- static int OnEndStream(
- nghttp3_conn* conn,
- int64_t stream_id,
- void* conn_user_data,
- void* stream_user_data);
-
- static ssize_t OnReadData(
- nghttp3_conn* conn,
- int64_t stream_id,
- nghttp3_vec* vec,
- size_t veccnt,
- uint32_t* pflags,
- void* conn_user_data,
- void* stream_user_data);
-};
-
-} // namespace quic
-
-} // namespace node
-
-#endif // NODE_WANT_INTERNALS
-#endif // SRC_QUIC_NODE_QUIC_HTTP3_APPLICATION_H_
diff --git a/src/quic/node_quic_session-inl.h b/src/quic/node_quic_session-inl.h
deleted file mode 100644
index c506812b044..00000000000
--- a/src/quic/node_quic_session-inl.h
+++ /dev/null
@@ -1,447 +0,0 @@
-#ifndef SRC_QUIC_NODE_QUIC_SESSION_INL_H_
-#define SRC_QUIC_NODE_QUIC_SESSION_INL_H_
-
-#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#include "debug_utils-inl.h"
-#include "node_crypto.h"
-#include "crypto/crypto_common.h"
-#include "node_quic_crypto.h"
-#include "node_quic_session.h"
-#include "node_quic_socket-inl.h"
-#include "node_quic_stream-inl.h"
-
-#include <openssl/ssl.h>
-#include <memory>
-#include <string>
-
-namespace node {
-
-namespace quic {
-
-void QuicSessionConfig::GenerateStatelessResetToken(
- QuicSession* session,
- const QuicCID& cid) {
- transport_params.stateless_reset_token_present = 1;
- StatelessResetToken token(
- transport_params.stateless_reset_token,
- session->socket()->session_reset_secret(),
- cid);
- Debug(session, "Generated stateless reset token %s for CID %s", token, cid);
-}
-
-void QuicSessionConfig::GeneratePreferredAddressToken(
- ConnectionIDStrategy connection_id_strategy,
- QuicSession* session,
- QuicCID* pscid) {
- connection_id_strategy(session, pscid->cid(), kScidLen);
- transport_params.preferred_address.cid = **pscid;
-
- StatelessResetToken(
- transport_params.preferred_address.stateless_reset_token,
- session->socket()->session_reset_secret(),
- *pscid);
-}
-
-void QuicSessionConfig::set_original_connection_id(
- const QuicCID& ocid,
- const QuicCID& scid) {
- if (ocid) {
- transport_params.original_dcid = *ocid;
- transport_params.retry_scid = *scid;
- transport_params.retry_scid_present = 1;
- } else {
- transport_params.original_dcid = *scid;
- }
-}
-
-void QuicSessionConfig::set_qlog(const ngtcp2_qlog_settings& qlog_) {
- qlog = qlog_;
-}
-
-QuicCryptoContext::QuicCryptoContext(
- QuicSession* session,
- BaseObjectPtr<crypto::SecureContext> secure_context,
- ngtcp2_crypto_side side,
- uint32_t options) :
- session_(session),
- secure_context_(secure_context),
- side_(side),
- options_(options) {
- ssl_.reset(SSL_new(secure_context_->ctx_.get()));
- CHECK(ssl_);
-}
-
-void QuicCryptoContext::Initialize() {
- InitializeTLS(session(), ssl_);
-}
-
-// Cancels and frees any remaining outbound handshake data
-// at each crypto level.
-uint64_t QuicCryptoContext::Cancel() {
- uint64_t len = handshake_[0].Cancel();
- len += handshake_[1].Cancel();
- len += handshake_[2].Cancel();
- return len;
-}
-
-v8::MaybeLocal<v8::Value> QuicCryptoContext::ocsp_response() const {
- Environment* env = session()->env();
- return crypto::GetSSLOCSPResponse(
- env,
- ssl_.get(),
- v8::Undefined(env->isolate()));
-}
-
-ngtcp2_crypto_level QuicCryptoContext::read_crypto_level() const {
- return from_ossl_level(SSL_quic_read_level(ssl_.get()));
-}
-
-ngtcp2_crypto_level QuicCryptoContext::write_crypto_level() const {
- return from_ossl_level(SSL_quic_write_level(ssl_.get()));
-}
-
-// TLS Keylogging is enabled per-QuicSession by attaching an handler to the
-// "keylog" event. Each keylog line is emitted to JavaScript where it can
-// be routed to whatever destination makes sense. Typically, this will be
-// to a keylog file that can be consumed by tools like Wireshark to intercept
-// and decrypt QUIC network traffic.
-void QuicCryptoContext::Keylog(const char* line) {
- if (UNLIKELY(session_->state_->keylog_enabled))
- session_->listener()->OnKeylog(line, strlen(line));
-}
-
-// Following a pause in the handshake for OCSP or client hello, we kickstart
-// the handshake again here by triggering ngtcp2 to serialize data.
-void QuicCryptoContext::ResumeHandshake() {
- // We haven't received any actual new handshake data but calling
- // this will trigger the handshake to continue.
- Receive(read_crypto_level(), 0, nullptr, 0);
- session_->SendPendingData();
-}
-
-// For 0RTT, this sets the TLS session data from the given buffer.
-bool QuicCryptoContext::set_session(crypto::SSLSessionPointer session) {
- if (side_ == NGTCP2_CRYPTO_SIDE_CLIENT && session != nullptr) {
- set_early_data(
- SSL_SESSION_get_max_early_data(session.get()) == 0xffffffffUL);
- }
- return crypto::SetTLSSession(ssl_, std::move(session));
-}
-
-v8::MaybeLocal<v8::Array> QuicCryptoContext::hello_ciphers() const {
- return crypto::GetClientHelloCiphers(session()->env(), ssl_);
-}
-
-v8::MaybeLocal<v8::Value> QuicCryptoContext::cipher_name() const {
- return crypto::GetCipherName(session()->env(), ssl_);
-}
-
-v8::MaybeLocal<v8::Value> QuicCryptoContext::cipher_version() const {
- return crypto::GetCipherVersion(session()->env(), ssl_);
-}
-
-const char* QuicCryptoContext::servername() const {
- return crypto::GetServerName(ssl_.get());
-}
-
-const char* QuicCryptoContext::hello_alpn() const {
- return crypto::GetClientHelloALPN(ssl_);
-}
-
-const char* QuicCryptoContext::hello_servername() const {
- return crypto::GetClientHelloServerName(ssl_);
-}
-
-v8::MaybeLocal<v8::Object> QuicCryptoContext::ephemeral_key() const {
- return crypto::GetEphemeralKey(session()->env(), ssl_);
-}
-
-v8::MaybeLocal<v8::Value> QuicCryptoContext::peer_cert(bool abbreviated) const {
- return crypto::GetPeerCert(
- session()->env(),
- ssl_,
- abbreviated,
- session()->is_server());
-}
-
-v8::MaybeLocal<v8::Value> QuicCryptoContext::cert() const {
- return crypto::GetCert(session()->env(), ssl_);
-}
-
-std::string QuicCryptoContext::selected_alpn() const {
- const unsigned char* alpn_buf = nullptr;
- unsigned int alpnlen;
- SSL_get0_alpn_selected(ssl_.get(), &alpn_buf, &alpnlen);
- return alpnlen ?
- std::string(reinterpret_cast<const char*>(alpn_buf), alpnlen) :
- std::string();
-}
-
-bool QuicCryptoContext::early_data() const {
- return
- (is_early_data() &&
- SSL_get_early_data_status(ssl_.get()) == SSL_EARLY_DATA_ACCEPTED) ||
- SSL_get_max_early_data(ssl_.get()) == 0xffffffffUL;
-}
-
-void QuicCryptoContext::set_tls_alert(int err) {
- Debug(session(), "TLS Alert [%d]: %s", err, SSL_alert_type_string_long(err));
- session_->set_last_error(QuicError(QUIC_ERROR_CRYPTO, err));
-}
-
-QuicApplication::QuicApplication(QuicSession* session) : session_(session) {}
-
-void QuicApplication::set_stream_fin(int64_t stream_id) {
- BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id);
- CHECK(stream);
- stream->set_fin_sent();
-}
-
-std::unique_ptr<QuicPacket> QuicApplication::CreateStreamDataPacket() {
- return QuicPacket::Create(
- "stream data",
- session()->max_packet_length());
-}
-
-Environment* QuicApplication::env() const {
- return session()->env();
-}
-
-// Extends the stream-level flow control by the given number of bytes.
-void QuicSession::ExtendStreamOffset(int64_t stream_id, size_t amount) {
- Debug(this, "Extending max stream %" PRId64 " offset by %" PRId64 " bytes",
- stream_id, amount);
- ngtcp2_conn_extend_max_stream_offset(
- connection(),
- stream_id,
- amount);
-}
-
-// Extends the connection-level flow control for the entire session by
-// the given number of bytes.
-void QuicSession::ExtendOffset(size_t amount) {
- Debug(this, "Extending session offset by %" PRId64 " bytes", amount);
- ngtcp2_conn_extend_max_offset(connection(), amount);
-}
-
-// Copies the local transport params into the given struct for serialization.
-void QuicSession::GetLocalTransportParams(ngtcp2_transport_params* params) {
- CHECK(!is_destroyed());
- ngtcp2_conn_get_local_transport_params(connection(), params);
-}
-
-// Gets the QUIC version negotiated for this QuicSession
-uint32_t QuicSession::negotiated_version() const {
- CHECK(!is_destroyed());
- return ngtcp2_conn_get_negotiated_version(connection());
-}
-
-bool QuicSession::is_handshake_completed() const {
- DCHECK(!is_destroyed());
- return ngtcp2_conn_get_handshake_completed(connection());
-}
-
-void QuicSession::ShutdownStream(int64_t stream_id, uint64_t code) {
- if (is_in_closing_period() ||
- is_in_draining_period() ||
- is_silent_closing()) {
- return; // Nothing to do because we can't send any frames.
- }
- SendSessionScope send_scope(this);
- ngtcp2_conn_shutdown_stream(connection(), stream_id, 0);
-}
-
-// When a QuicSession hits the idle timeout, it is to be silently and
-// immediately closed without attempting to send any additional data to
-// the peer. All existing streams are abandoned and closed.
-void QuicSession::OnIdleTimeout() {
- if (!is_destroyed()) {
- if (state_->idle_timeout == 1) {
- Debug(this, "Idle timeout");
- Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
- return;
- }
- state_->idle_timeout = 1;
- UpdateClosingTimer();
- }
-}
-
-QuicCID QuicSession::dcid() const {
- return QuicCID(ngtcp2_conn_get_dcid(connection()));
-}
-
-void QuicSession::CheckAllocatedSize(size_t previous_size) const {
- CHECK_GE(current_ngtcp2_memory_, previous_size);
-}
-
-void QuicSession::IncreaseAllocatedSize(size_t size) {
- current_ngtcp2_memory_ += size;
-}
-
-void QuicSession::DecreaseAllocatedSize(size_t size) {
- current_ngtcp2_memory_ -= size;
-}
-
-uint64_t QuicSession::max_data_left() const {
- return ngtcp2_conn_get_max_data_left(connection());
-}
-
-uint64_t QuicSession::max_local_streams_uni() const {
- return ngtcp2_conn_get_max_local_streams_uni(connection());
-}
-
-void QuicSession::set_last_error(QuicError error) {
- last_error_ = error;
-}
-
-void QuicSession::set_last_error(int32_t family, uint64_t code) {
- set_last_error({ family, code });
-}
-
-void QuicSession::set_last_error(int32_t family, int code) {
- set_last_error({ family, code });
-}
-
-bool QuicSession::is_in_closing_period() const {
- return ngtcp2_conn_is_in_closing_period(connection());
-}
-
-bool QuicSession::is_in_draining_period() const {
- return ngtcp2_conn_is_in_draining_period(connection());
-}
-
-bool QuicSession::HasStream(int64_t id) const {
- return streams_.find(id) != std::end(streams_);
-}
-
-bool QuicSession::allow_early_data() const {
- // TODO(@jasnell): For now, we always allow early data.
- // Later there will be reasons we do not want to allow
- // it, such as lack of available system resources.
- return true;
-}
-
-void QuicSession::SetSessionTicketAppData(
- const SessionTicketAppData& app_data) {
- application_->SetSessionTicketAppData(app_data);
-}
-
-SessionTicketAppData::Status QuicSession::GetSessionTicketAppData(
- const SessionTicketAppData& app_data,
- SessionTicketAppData::Flag flag) {
- return application_->GetSessionTicketAppData(app_data, flag);
-}
-
-bool QuicSession::is_server() const {
- return crypto_context_->side() == NGTCP2_CRYPTO_SIDE_SERVER;
-}
-
-void QuicSession::StartGracefulClose() {
- set_graceful_closing();
- RecordTimestamp(&QuicSessionStats::closing_at);
-}
-
-// The connection ID Strategy is a function that generates
-// connection ID values. By default these are generated randomly.
-void QuicSession::set_connection_id_strategy(ConnectionIDStrategy strategy) {
- CHECK_NOT_NULL(strategy);
- connection_id_strategy_ = strategy;
-}
-
-bool QuicSession::is_unable_to_send_packets() {
- return NgCallbackScope::InNgCallbackScope(this) ||
- is_destroyed() ||
- is_in_draining_period() ||
- (is_server() && is_in_closing_period()) ||
- socket() == nullptr;
-}
-
-void QuicSession::set_preferred_address_strategy(
- PreferredAddressStrategy strategy) {
- preferred_address_strategy_ = strategy;
-}
-
-QuicSocket* QuicSession::socket() const {
- return socket_.get();
-}
-
-// Indicates that the stream is blocked from transmitting any
-// data. The specific handling of this is application specific.
-// By default, we keep track of statistics but leave it up to
-// the application to perform specific handling.
-void QuicSession::StreamDataBlocked(int64_t stream_id) {
- IncrementStat(&QuicSessionStats::block_count);
- listener_->OnStreamBlocked(stream_id);
-}
-
-// When a server advertises a preferred address in its initial
-// transport parameters, ngtcp2 on the client side will trigger
-// the OnSelectPreferredAdddress callback which will call this.
-// The paddr argument contains the advertised preferred address.
-// If the new address is going to be used, it needs to be copied
-// over to dest, otherwise dest is left alone. There are two
-// possible strategies that we currently support via user
-// configuration: use the preferred address or ignore it.
-void QuicSession::SelectPreferredAddress(
- const PreferredAddress& preferred_address) {
- CHECK(!is_server());
- preferred_address_strategy_(this, preferred_address);
-}
-
-// This variant of SendPacket is used by QuicApplication
-// instances to transmit a packet and update the network
-// path used at the same time.
-bool QuicSession::SendPacket(
- std::unique_ptr<QuicPacket> packet,
- const ngtcp2_path_storage& path) {
- UpdateEndpoint(path.path);
- return SendPacket(std::move(packet));
-}
-
-// Set the transport parameters received from the remote peer
-void QuicSession::set_remote_transport_params() {
- DCHECK(!is_destroyed());
- ngtcp2_conn_get_remote_transport_params(connection(), &transport_params_);
- set_transport_params_set();
-}
-
-// Submits information headers only if the selected application
-// supports headers.
-bool QuicSession::SubmitInformation(
- int64_t stream_id,
- v8::Local<v8::Array> headers) {
- return application_->SubmitInformation(stream_id, headers);
-}
-
-// Submits initial headers only if the selected application
-// supports headers. For http3, for instance, this is the
-// method used to submit both request and response headers.
-bool QuicSession::SubmitHeaders(
- int64_t stream_id,
- v8::Local<v8::Array> headers,
- uint32_t flags) {
- return application_->SubmitHeaders(stream_id, headers, flags);
-}
-
-// Submits trailing headers only if the selected application
-// supports headers.
-bool QuicSession::SubmitTrailers(
- int64_t stream_id,
- v8::Local<v8::Array> headers) {
- return application_->SubmitTrailers(stream_id, headers);
-}
-
-// Submits a new push stream
-BaseObjectPtr<QuicStream> QuicSession::SubmitPush(
- int64_t stream_id,
- v8::Local<v8::Array> headers) {
- return application_->SubmitPush(stream_id, headers);
-}
-
-} // namespace quic
-} // namespace node
-
-#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#endif // SRC_QUIC_NODE_QUIC_SESSION_INL_H_
diff --git a/src/quic/node_quic_session.cc b/src/quic/node_quic_session.cc
deleted file mode 100644
index fce6b658a43..00000000000
--- a/src/quic/node_quic_session.cc
+++ /dev/null
@@ -1,3963 +0,0 @@
-#include "node_quic_session-inl.h" // NOLINT(build/include)
-#include "aliased_buffer.h"
-#include "aliased_struct-inl.h"
-#include "allocated_buffer-inl.h"
-#include "async_wrap-inl.h"
-#include "debug_utils-inl.h"
-#include "env-inl.h"
-#include "crypto/crypto_common.h"
-#include "ngtcp2/ngtcp2.h"
-#include "nghttp3/nghttp3.h" // NGHTTP3_ALPN_H3
-#include "ngtcp2/ngtcp2_crypto.h"
-#include "ngtcp2/ngtcp2_crypto_openssl.h"
-#include "node.h"
-#include "node_buffer.h"
-#include "node_crypto.h"
-#include "node_errors.h"
-#include "node_internals.h"
-#include "node_http_common-inl.h"
-#include "node_mem-inl.h"
-#include "node_process.h"
-#include "node_quic_buffer-inl.h"
-#include "node_quic_crypto.h"
-#include "node_quic_socket-inl.h"
-#include "node_quic_stream-inl.h"
-#include "node_quic_util-inl.h"
-#include "node_quic_default_application.h"
-#include "node_quic_http3_application.h"
-#include "node_sockaddr-inl.h"
-#include "stream_base-inl.h"
-#include "v8.h"
-#include "uv.h"
-
-#include <algorithm>
-#include <memory>
-#include <vector>
-#include <string>
-#include <utility>
-
-namespace node {
-
-using crypto::EntropySource;
-using crypto::SecureContext;
-
-using v8::Array;
-using v8::ArrayBufferView;
-using v8::Context;
-using v8::FunctionCallbackInfo;
-using v8::FunctionTemplate;
-using v8::HandleScope;
-using v8::Integer;
-using v8::Local;
-using v8::Number;
-using v8::Object;
-using v8::ObjectTemplate;
-using v8::PropertyAttribute;
-using v8::String;
-using v8::Undefined;
-using v8::Value;
-
-using TryCatchScope = node::errors::TryCatchScope;
-
-namespace quic {
-
-QuicCallbackScope::QuicCallbackScope(QuicSession* session)
- : session_(session),
- private_(new InternalCallbackScope(
- session->env(),
- session->object(),
- {
- session->get_async_id(),
- session->get_trigger_async_id()
- })),
- try_catch_(session->env()->isolate()) {
- try_catch_.SetVerbose(true);
-}
-
-QuicCallbackScope::~QuicCallbackScope() {
- Environment* env = session_->env();
- if (UNLIKELY(try_catch_.HasCaught())) {
- session_->crypto_context()->set_in_client_hello(false);
- session_->crypto_context()->set_in_ocsp_request(false);
- if (!try_catch_.HasTerminated() && env->can_call_into_js()) {
- session_->set_last_error({
- QUIC_ERROR_SESSION,
- uint64_t{NGTCP2_INTERNAL_ERROR}
- });
- session_->Close();
- CHECK(session_->is_destroyed());
- }
- private_->MarkAsFailed();
- }
-}
-
-typedef ssize_t(*ngtcp2_close_fn)(
- ngtcp2_conn* conn,
- ngtcp2_path* path,
- uint8_t* dest,
- size_t destlen,
- uint64_t error_code,
- ngtcp2_tstamp ts);
-
-namespace {
-void SetConfig(QuicState* quic_state, int idx, uint64_t* val) {
- AliasedFloat64Array& buffer = quic_state->quicsessionconfig_buffer;
- uint64_t flags = static_cast<uint64_t>(buffer[IDX_QUIC_SESSION_CONFIG_COUNT]);
- if (flags & (1ULL << idx))
- *val = static_cast<uint64_t>(buffer[idx]);
-}
-
-// Forwards detailed(verbose) debugging information from ngtcp2. Enabled using
-// the NODE_DEBUG_NATIVE=NGTCP2_DEBUG category.
-void Ngtcp2DebugLog(void* user_data, const char* fmt, ...) {
- va_list ap;
- va_start(ap, fmt);
- std::string format(fmt, strlen(fmt) + 1);
- format[strlen(fmt)] = '\n';
- // Debug() does not work with the va_list here. So we use vfprintf
- // directly instead. Ngtcp2DebugLog is only enabled when the debug
- // category is enabled.
- vfprintf(stderr, format.c_str(), ap);
- va_end(ap);
-}
-
-void CopyPreferredAddress(
- uint8_t* dest,
- size_t destlen,
- uint16_t* port,
- const sockaddr* addr) {
- const sockaddr_in* src = reinterpret_cast<const sockaddr_in*>(addr);
- memcpy(dest, &src->sin_addr, destlen);
- *port = SocketAddress::GetPort(addr);
-}
-
-ngtcp2_close_fn SelectCloseFn(uint32_t family) {
- return family == QUIC_ERROR_APPLICATION ?
- ngtcp2_conn_write_application_close :
- ngtcp2_conn_write_connection_close;
-}
-
-} // namespace
-
-std::string QuicSession::RemoteTransportParamsDebug::ToString() const {
- ngtcp2_transport_params params;
- ngtcp2_conn_get_remote_transport_params(session->connection(), &params);
- std::string out = "Remote Transport Params:\n";
- out += " Ack Delay Exponent: " +
- std::to_string(params.ack_delay_exponent) + "\n";
- out += " Active Connection ID Limit: " +
- std::to_string(params.active_connection_id_limit) + "\n";
- out += " Disable Active Migration: " +
- std::string(params.disable_active_migration ? "Yes" : "No") + "\n";
- out += " Initial Max Data: " +
- std::to_string(params.initial_max_data) + "\n";
- out += " Initial Max Stream Data Bidi Local: " +
- std::to_string(params.initial_max_stream_data_bidi_local) + "\n";
- out += " Initial Max Stream Data Bidi Remote: " +
- std::to_string(params.initial_max_stream_data_bidi_remote) + "\n";
- out += " Initial Max Stream Data Uni: " +
- std::to_string(params.initial_max_stream_data_uni) + "\n";
- out += " Initial Max Streams Bidi: " +
- std::to_string(params.initial_max_streams_bidi) + "\n";
- out += " Initial Max Streams Uni: " +
- std::to_string(params.initial_max_streams_uni) + "\n";
- out += " Max Ack Delay: " +
- std::to_string(params.max_ack_delay) + "\n";
- out += " Max Idle Timeout: " +
- std::to_string(params.max_idle_timeout) + "\n";
- out += " Max Packet Size: " +
- std::to_string(params.max_udp_payload_size) + "\n";
-
- if (!session->is_server()) {
- if (params.retry_scid_present) {
- QuicCID cid(params.original_dcid);
- QuicCID retry(params.retry_scid);
- out += " Original Connection ID: " + cid.ToString() + "\n";
- out += " Retry SCID: " + retry.ToString() + "\n";
- } else {
- out += " Original Connection ID: N/A \n";
- }
-
- if (params.preferred_address_present) {
- out += " Preferred Address Present: Yes\n";
- // TODO(@jasnell): Serialize the IPv4 and IPv6 address options
- } else {
- out += " Preferred Address Present: No\n";
- }
-
- if (params.stateless_reset_token_present) {
- StatelessResetToken token(params.stateless_reset_token);
- out += " Stateless Reset Token: " + token.ToString() + "\n";
- } else {
- out += " Stateless Reset Token: N/A";
- }
- }
- return out;
-}
-
-void QuicSessionConfig::ResetToDefaults(QuicState* quic_state) {
- ngtcp2_settings_default(this);
- initial_ts = uv_hrtime();
- // Detailed(verbose) logging provided by ngtcp2 is only enabled
- // when the NODE_DEBUG_NATIVE=NGTCP2_DEBUG category is used.
- if (UNLIKELY(quic_state->env()->enabled_debug_list()->enabled(
- DebugCategory::NGTCP2_DEBUG))) {
- log_printf = Ngtcp2DebugLog;
- }
- transport_params.active_connection_id_limit =
- DEFAULT_ACTIVE_CONNECTION_ID_LIMIT;
- transport_params.initial_max_stream_data_bidi_local =
- DEFAULT_MAX_STREAM_DATA_BIDI_LOCAL;
- transport_params.initial_max_stream_data_bidi_remote =
- DEFAULT_MAX_STREAM_DATA_BIDI_REMOTE;
- transport_params.initial_max_stream_data_uni =
- DEFAULT_MAX_STREAM_DATA_UNI;
- transport_params.initial_max_streams_bidi =
- DEFAULT_MAX_STREAMS_BIDI;
- transport_params.initial_max_streams_uni =
- DEFAULT_MAX_STREAMS_UNI;
- transport_params.initial_max_data = DEFAULT_MAX_DATA;
- transport_params.max_idle_timeout = DEFAULT_MAX_IDLE_TIMEOUT;
- transport_params.max_udp_payload_size =
- NGTCP2_DEFAULT_MAX_UDP_PAYLOAD_SIZE;
- transport_params.max_ack_delay =
- NGTCP2_DEFAULT_MAX_ACK_DELAY;
- transport_params.disable_active_migration = 0;
- transport_params.preferred_address_present = 0;
- transport_params.stateless_reset_token_present = 0;
- cc_algo = NGTCP2_CC_ALGO_RENO;
-}
-
-// Sets the QuicSessionConfig using an AliasedBuffer for efficiency.
-void QuicSessionConfig::Set(
- QuicState* quic_state,
- const sockaddr* preferred_addr) {
- ResetToDefaults(quic_state);
- SetConfig(quic_state, IDX_QUIC_SESSION_ACTIVE_CONNECTION_ID_LIMIT,
- &transport_params.active_connection_id_limit);
- SetConfig(quic_state, IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_LOCAL,
- &transport_params.initial_max_stream_data_bidi_local);
- SetConfig(quic_state, IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_REMOTE,
- &transport_params.initial_max_stream_data_bidi_remote);
- SetConfig(quic_state, IDX_QUIC_SESSION_MAX_STREAM_DATA_UNI,
- &transport_params.initial_max_stream_data_uni);
- SetConfig(quic_state, IDX_QUIC_SESSION_MAX_DATA,
- &transport_params.initial_max_data);
- SetConfig(quic_state, IDX_QUIC_SESSION_MAX_STREAMS_BIDI,
- &transport_params.initial_max_streams_bidi);
- SetConfig(quic_state, IDX_QUIC_SESSION_MAX_STREAMS_UNI,
- &transport_params.initial_max_streams_uni);
- SetConfig(quic_state, IDX_QUIC_SESSION_MAX_IDLE_TIMEOUT,
- &transport_params.max_idle_timeout);
- SetConfig(quic_state, IDX_QUIC_SESSION_MAX_UDP_PAYLOAD_SIZE,
- &transport_params.max_udp_payload_size);
- SetConfig(quic_state, IDX_QUIC_SESSION_MAX_ACK_DELAY,
- &transport_params.max_ack_delay);
- SetConfig(quic_state,
- IDX_QUIC_SESSION_CC_ALGO,
- reinterpret_cast<uint64_t *>(&cc_algo));
-
- transport_params.max_idle_timeout =
- transport_params.max_idle_timeout * 1000000000;
-
- // TODO(@jasnell): QUIC allows both IPv4 and IPv6 addresses to be
- // specified. Here we're specifying one or the other. Need to
- // determine if that's what we want or should we support both.
- //
- // TODO(@jasnell): Currently, this is specified as a single value
- // that is used for all connections. In the future, it may be
- // necessary to determine the preferred address based on the
- // remote address. The trick, however, is that the preferred
- // address must be selected before the QuicSession is created,
- // before the handshake can be started. That is, it may need
- // to be an optional callback on QuicSocket. That would incur
- // a performance penalty so we'd really have to be sure of the
- // utility.
- if (preferred_addr != nullptr) {
- transport_params.preferred_address_present = 1;
- switch (preferred_addr->sa_family) {
- case AF_INET: {
- CopyPreferredAddress(
- transport_params.preferred_address.ipv4_addr,
- sizeof(transport_params.preferred_address.ipv4_addr),
- &transport_params.preferred_address.ipv4_port,
- preferred_addr);
- break;
- }
- case AF_INET6: {
- CopyPreferredAddress(
- transport_params.preferred_address.ipv6_addr,
- sizeof(transport_params.preferred_address.ipv6_addr),
- &transport_params.preferred_address.ipv6_port,
- preferred_addr);
- break;
- }
- default:
- UNREACHABLE();
- }
- }
-}
-
-void QuicSessionListener::OnKeylog(const char* line, size_t len) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnKeylog(line, len);
-}
-
-void QuicSessionListener::OnClientHello(
- const char* alpn,
- const char* server_name) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnClientHello(alpn, server_name);
-}
-
-QuicSessionListener::~QuicSessionListener() {
- if (session_)
- session_->RemoveListener(this);
-}
-
-void QuicSessionListener::OnCert(const char* server_name) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnCert(server_name);
-}
-
-void QuicSessionListener::OnOCSP(Local<Value> ocsp) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnOCSP(ocsp);
-}
-
-void QuicSessionListener::OnStreamHeaders(
- int64_t stream_id,
- int kind,
- const std::vector<std::unique_ptr<QuicHeader>>& headers,
- int64_t push_id) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnStreamHeaders(stream_id, kind, headers, push_id);
-}
-
-void QuicSessionListener::OnStreamClose(
- int64_t stream_id,
- uint64_t app_error_code) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnStreamClose(stream_id, app_error_code);
-}
-
-void QuicSessionListener::OnStreamReset(
- int64_t stream_id,
- uint64_t app_error_code) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnStreamReset(stream_id, app_error_code);
-}
-
-void QuicSessionListener::OnSessionClose(QuicError error, int flags) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnSessionClose(error, flags);
-}
-
-void QuicSessionListener::OnStreamReady(BaseObjectPtr<QuicStream> stream) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnStreamReady(stream);
-}
-
-void QuicSessionListener::OnHandshakeCompleted() {
- if (previous_listener_ != nullptr)
- previous_listener_->OnHandshakeCompleted();
-}
-
-void QuicSessionListener::OnPathValidation(
- ngtcp2_path_validation_result res,
- const sockaddr* local,
- const sockaddr* remote) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnPathValidation(res, local, remote);
-}
-
-void QuicSessionListener::OnSessionTicket(int size, SSL_SESSION* session) {
- if (previous_listener_ != nullptr) {
- previous_listener_->OnSessionTicket(size, session);
- }
-}
-
-void QuicSessionListener::OnStreamBlocked(int64_t stream_id) {
- if (previous_listener_ != nullptr) {
- previous_listener_->OnStreamBlocked(stream_id);
- }
-}
-
-void QuicSessionListener::OnUsePreferredAddress(
- int family,
- const PreferredAddress& preferred_address) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnUsePreferredAddress(family, preferred_address);
-}
-
-void QuicSessionListener::OnVersionNegotiation(
- uint32_t supported_version,
- const uint32_t* versions,
- size_t vcnt) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnVersionNegotiation(supported_version, versions, vcnt);
-}
-
-void QuicSessionListener::OnQLog(QLogStream* qlog_stream) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnQLog(qlog_stream);
-}
-
-void JSQuicSessionListener::OnKeylog(const char* line, size_t len) {
- Environment* env = session()->env();
-
- HandleScope handle_scope(env->isolate());
- Context::Scope context_scope(env->context());
-
- QuicCallbackScope cb_scope(session());
-
- Local<Value> line_buf;
- if (!Buffer::Copy(env, line, 1 + len).ToLocal(&line_buf))
- return;
-
- char* data = Buffer::Data(line_buf);
- data[len] = '\n';
-
- // Grab a shared pointer to this to prevent the QuicSession
- // from being freed while the MakeCallback is running.
- BaseObjectPtr<QuicSession> ptr(session());
- USE(env->quic_on_session_keylog_function()->Call(
- env->context(),
- session()->object(),
- 1,
- &line_buf));
-}
-
-void JSQuicSessionListener::OnStreamBlocked(int64_t stream_id) {
- Environment* env = session()->env();
-
- HandleScope handle_scope(env->isolate());
- Context::Scope context_scope(env->context());
-
- QuicCallbackScope cb_scope(session());
-
- BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id);
- USE(env->quic_on_stream_blocked_function()->Call(
- env->context(),
- stream->object(),
- 0, nullptr));
-}
-
-void JSQuicSessionListener::OnClientHello(
- const char* alpn,
- const char* server_name) {
-
- Environment* env = session()->env();
- HandleScope scope(env->isolate());
- Context::Scope context_scope(env->context());
-
- // Why this instead of using MakeCallback? We need to catch any
- // errors that happen both when preparing the arguments and
- // invoking the callback so that we can properly signal a failure
- // to the peer.
- QuicCallbackScope cb_scope(session());
-
- Local<Array> ciphers;
- Local<Value> alpn_string = Undefined(env->isolate());
- Local<Value> servername = Undefined(env->isolate());
-
- if (!session()->crypto_context()->hello_ciphers().ToLocal(&ciphers) ||
- (alpn != nullptr &&
- !String::NewFromUtf8(
- env->isolate(),
- alpn).ToLocal(&alpn_string)) ||
- (server_name != nullptr &&
- !String::NewFromUtf8(
- env->isolate(),
- server_name).ToLocal(&servername))) {
- return;
- }
-
- Local<Value> argv[] = {
- alpn_string,
- servername,
- ciphers
- };
-
- // Grab a shared pointer to this to prevent the QuicSession
- // from being freed while the MakeCallback is running.
- BaseObjectPtr<QuicSession> ptr(session());
- USE(env->quic_on_session_client_hello_function()->Call(
- env->context(),
- session()->object(),
- arraysize(argv),
- argv));
-}
-
-void JSQuicSessionListener::OnCert(const char* server_name) {
- Environment* env = session()->env();
- HandleScope handle_scope(env->isolate());
- Context::Scope context_scope(env->context());
-
- QuicCallbackScope cb_scope(session());
-
- Local<Value> servername = Undefined(env->isolate());
- if (UNLIKELY(server_name != nullptr &&
- !String::NewFromUtf8(
- env->isolate(),
- server_name,
- v8::NewStringType::kNormal,
- strlen(server_name)).ToLocal(&servername))) {
- return;
- }
-
- BaseObjectPtr<QuicSession> ptr(session());
-
- USE(env->quic_on_session_cert_function()->Call(
- env->context(),
- session()->object(),
- 1, &servername));
-}
-
-void JSQuicSessionListener::OnStreamHeaders(
- int64_t stream_id,
- int kind,
- const std::vector<std::unique_ptr<QuicHeader>>& headers,
- int64_t push_id) {
- Environment* env = session()->env();
- HandleScope scope(env->isolate());
- Context::Scope context_scope(env->context());
- MaybeStackBuffer<Local<Value>, 16> head(headers.size());
- size_t n = 0;
-
- QuicCallbackScope cb_scope(session());
-
- for (const auto& header : headers) {
- Local<Value> pair[2];
-
- if (UNLIKELY(!header->GetName(session()->application()).ToLocal(&pair[0])))
- return;
-
- if (UNLIKELY(!header->GetValue(session()->application()).ToLocal(&pair[1])))
- return;
-
- head[n++] = Array::New(env->isolate(), pair, arraysize(pair));
- }
-
- Local<Value> argv[] = {
- Number::New(env->isolate(), static_cast<double>(stream_id)),
- Array::New(env->isolate(), head.out(), n),
- Integer::New(env->isolate(), kind),
- Undefined(env->isolate())
- };
-
- if (kind == QUICSTREAM_HEADERS_KIND_PUSH)
- argv[3] = Number::New(env->isolate(), static_cast<double>(push_id));
-
- BaseObjectPtr<QuicSession> ptr(session());
-
- USE(env->quic_on_stream_headers_function()->Call(
- env->context(),
- session()->object(),
- arraysize(argv),
- argv));
-}
-
-void JSQuicSessionListener::OnOCSP(Local<Value> ocsp) {
- Environment* env = session()->env();
- HandleScope scope(env->isolate());
- Context::Scope context_scope(env->context());
- QuicCallbackScope cb_scope(session());
- BaseObjectPtr<QuicSession> ptr(session());
- USE(env->quic_on_session_status_function()->Call(
- env->context(),
- session()->object(),
- 1, &ocsp));
-}
-
-void JSQuicSessionListener::OnStreamClose(
- int64_t stream_id,
- uint64_t app_error_code) {
- Environment* env = session()->env();
- HandleScope scope(env->isolate());
- Context::Scope context_scope(env->context());
-
- QuicCallbackScope cb_scope(session());
-
- Local<Value> argv[] = {
- Number::New(env->isolate(), static_cast<double>(stream_id)),
- Number::New(env->isolate(), static_cast<double>(app_error_code))
- };
-
- // Grab a shared pointer to this to prevent the QuicSession
- // from being freed while the MakeCallback is running.
- BaseObjectPtr<QuicSession> ptr(session());
-
- USE(env->quic_on_stream_close_function()->Call(
- env->context(),
- session()->object(),
- arraysize(argv),
- argv));
-}
-
-void JSQuicSessionListener::OnStreamReset(
- int64_t stream_id,
- uint64_t app_error_code) {
- Environment* env = session()->env();
- HandleScope scope(env->isolate());
- Context::Scope context_scope(env->context());
-
- QuicCallbackScope cb_scope(session());
-
- Local<Value> argv[] = {
- Number::New(env->isolate(), static_cast<double>(stream_id)),
- Number::New(env->isolate(), static_cast<double>(app_error_code))
- };
- // Grab a shared pointer to this to prevent the QuicSession
- // from being freed while the MakeCallback is running.
- BaseObjectPtr<QuicSession> ptr(session());
-
- USE(env->quic_on_stream_reset_function()->Call(
- env->context(),
- session()->object(),
- arraysize(argv),
- argv));
-}
-
-void JSQuicSessionListener::OnSessionClose(QuicError error, int flags) {
- Environment* env = session()->env();
- HandleScope scope(env->isolate());
- Context::Scope context_scope(env->context());
-
- QuicCallbackScope cb_scope(session());
-
- Local<Value> argv[] = {
- Number::New(env->isolate(), static_cast<double>(error.code)),
- Integer::New(env->isolate(), error.family),
- flags & SESSION_CLOSE_FLAG_SILENT
- ? v8::True(env->isolate())
- : v8::False(env->isolate()),
- flags & SESSION_CLOSE_FLAG_STATELESS_RESET
- ? v8::True(env->isolate())
- : v8::False(env->isolate())
- };
-
- // Grab a shared pointer to this to prevent the QuicSession
- // from being freed while the MakeCallback is running.
- BaseObjectPtr<QuicSession> ptr(session());
- USE(env->quic_on_session_close_function()->Call(
- env->context(),
- session()->object(),
- arraysize(argv),
- argv));
-}
-
-void JSQuicSessionListener::OnStreamReady(BaseObjectPtr<QuicStream> stream) {
- Environment* env = session()->env();
- HandleScope scope(env->isolate());
- Context::Scope context_scope(env->context());
-
- QuicCallbackScope cb_scope(session());
-
- Local<Value> argv[] = {
- stream->object(),
- Number::New(env->isolate(), static_cast<double>(stream->id())),
- Number::New(env->isolate(), static_cast<double>(stream->push_id()))
- };
-
- // Grab a shared pointer to this to prevent the QuicSession
- // from being freed while the MakeCallback is running.
- BaseObjectPtr<QuicSession> ptr(session());
-
- USE(env->quic_on_stream_ready_function()->Call(
- env->context(),
- session()->object(),
- arraysize(argv),
- argv));
-}
-
-void JSQuicSessionListener::OnHandshakeCompleted() {
- Environment* env = session()->env();
- HandleScope scope(env->isolate());
- Context::Scope context_scope(env->context());
-
- QuicCryptoContext* ctx = session()->crypto_context();
-
- QuicCallbackScope cb_scope(session());
-
- Local<Value> servername = Undefined(env->isolate());
- Local<Value> validationErrorReason = v8::Null(env->isolate());
- Local<Value> validationErrorCode = v8::Null(env->isolate());
- Local<Value> cipher_name;
- Local<Value> cipher_version;
-
- const char* hostname = ctx->servername();
- if (hostname != nullptr &&
- !String::NewFromUtf8(env->isolate(), hostname).ToLocal(&servername)) {
- return;
- }
-
- if (!ctx->cipher_name().ToLocal(&cipher_name) ||
- !ctx->cipher_version().ToLocal(&cipher_version)) {
- return;
- }
-
- int err = ctx->VerifyPeerIdentity();
- if (err != X509_V_OK &&
- (!crypto::GetValidationErrorReason(env, err)
- .ToLocal(&validationErrorReason) ||
- !crypto::GetValidationErrorCode(env, err)
- .ToLocal(&validationErrorCode))) {
- return;
- }
-
- Local<Value> argv[] = {
- servername,
- GetALPNProtocol(*session()),
- cipher_name,
- cipher_version,
- Integer::New(env->isolate(), session()->max_pktlen_),
- validationErrorReason,
- validationErrorCode,
- session()->crypto_context()->early_data() ?
- v8::True(env->isolate()) :
- v8::False(env->isolate())
- };
-
- // Grab a shared pointer to this to prevent the QuicSession
- // from being freed while the MakeCallback is running.
- BaseObjectPtr<QuicSession> ptr(session());
-
- USE(env->quic_on_session_handshake_function()->Call(
- env->context(),
- session()->object(),
- arraysize(argv),
- argv));
-}
-
-void JSQuicSessionListener::OnPathValidation(
- ngtcp2_path_validation_result res,
- const sockaddr* local,
- const sockaddr* remote) {
- // This is a fairly expensive operation because both the local and
- // remote addresses have to converted into JavaScript objects. We
- // only do this if a pathValidation handler is registered.
- Environment* env = session()->env();
- HandleScope scope(env->isolate());
- Local<Context> context = env->context();
- Context::Scope context_scope(context);
-
- QuicCallbackScope cb_scope(session());
-
- Local<Value> argv[] = {
- Integer::New(env->isolate(), res),
- AddressToJS(env, local),
- AddressToJS(env, remote)
- };
- // Grab a shared pointer to this to prevent the QuicSession
- // from being freed while the MakeCallback is running.
- BaseObjectPtr<QuicSession> ptr(session());
-
- USE(env->quic_on_session_path_validation_function()->Call(
- env->context(),
- session()->object(),
- arraysize(argv),
- argv));
-}
-
-void JSQuicSessionListener::OnSessionTicket(int size, SSL_SESSION* sess) {
- Environment* env = session()->env();
- HandleScope scope(env->isolate());
- Context::Scope context_scope(env->context());
-
- QuicCallbackScope cb_scope(session());
-
- Local<Value> argv[] = {
- v8::Undefined(env->isolate()),
- v8::Undefined(env->isolate())
- };
-
- if (size > 0) {
- AllocatedBuffer session_ticket =
- AllocatedBuffer::AllocateManaged(env, size);
- unsigned char* session_data =
- reinterpret_cast<unsigned char*>(session_ticket.data());
- memset(session_data, 0, size);
- if (i2d_SSL_SESSION(sess, &session_data) > 0 &&
- !session_ticket.ToBuffer().ToLocal(&argv[0])) {
- return;
- }
- }
-
- if (session()->is_transport_params_set() &&
- !Buffer::Copy(env,
- reinterpret_cast<const char*>(&session()->transport_params_),
- sizeof(session()->transport_params_)).ToLocal(&argv[1])) {
- return;
- }
-
- // Grab a shared pointer to this to prevent the QuicSession
- // from being freed while the MakeCallback is running.
- BaseObjectPtr<QuicSession> ptr(session());
-
- USE(env->quic_on_session_ticket_function()->Call(
- env->context(),
- session()->object(),
- arraysize(argv),
- argv));
-}
-
-void JSQuicSessionListener::OnUsePreferredAddress(
- int family,
- const PreferredAddress& preferred_address) {
- Environment* env = session()->env();
- HandleScope scope(env->isolate());
- Local<Context> context = env->context();
- Context::Scope context_scope(context);
-
- QuicCallbackScope cb_scope(session());
-
- std::string hostname = family == AF_INET ?
- preferred_address.ipv4_address():
- preferred_address.ipv6_address();
- uint16_t port =
- family == AF_INET ?
- preferred_address.ipv4_port() :
- preferred_address.ipv6_port();
-
- Local<Value> argv[] = {
- OneByteString(env->isolate(), hostname.c_str()),
- Integer::NewFromUnsigned(env->isolate(), port),
- Integer::New(env->isolate(), family)
- };
-
- BaseObjectPtr<QuicSession> ptr(session());
-
- USE(env->quic_on_session_use_preferred_address_function()->Call(
- env->context(),
- session()->object(),
- arraysize(argv),
- argv));
-}
-
-void JSQuicSessionListener::OnVersionNegotiation(
- uint32_t supported_version,
- const uint32_t* vers,
- size_t vcnt) {
- Environment* env = session()->env();
- HandleScope scope(env->isolate());
- Local<Context> context = env->context();
- Context::Scope context_scope(context);
-
- QuicCallbackScope cb_scope(session());
-
- MaybeStackBuffer<Local<Value>, 4> versions(vcnt);
- for (size_t n = 0; n < vcnt; n++)
- versions[n] = Integer::New(env->isolate(), vers[n]);
-
- // Currently, we only support one version of QUIC but in
- // the future that may change. The callback below passes
- // an array back to the JavaScript side to future-proof.
- Local<Value> supported =
- Integer::New(env->isolate(), supported_version);
-
- Local<Value> argv[] = {
- Integer::New(env->isolate(), NGTCP2_PROTO_VER),
- Array::New(env->isolate(), versions.out(), vcnt),
- Array::New(env->isolate(), &supported, 1)
- };
-
- // Grab a shared pointer to this to prevent the QuicSession
- // from being freed while the MakeCallback is running.
- BaseObjectPtr<QuicSession> ptr(session());
- USE(env->quic_on_session_version_negotiation_function()->Call(
- env->context(),
- session()->object(),
- arraysize(argv),
- argv));
-}
-
-void JSQuicSessionListener::OnQLog(QLogStream* qlog_stream) {
- CHECK_NOT_NULL(qlog_stream);
- Environment* env = session()->env();
- HandleScope handle_scope(env->isolate());
- Context::Scope context_scope(env->context());
- QuicCallbackScope cb_scope(session());
- Local<Value> obj = qlog_stream->object();
- USE(env->quic_on_session_qlog_function()->Call(
- env->context(),
- session()->object(),
- 1, &obj));
-}
-
-// Generates a new random connection ID.
-void QuicSession::RandomConnectionIDStrategy(
- QuicSession* session,
- ngtcp2_cid* cid,
- size_t cidlen) {
- // CID min and max length is determined by the QUIC specification.
- CHECK_LE(cidlen, NGTCP2_MAX_CIDLEN);
- CHECK_GE(cidlen, NGTCP2_MIN_CIDLEN);
- cid->datalen = cidlen;
- // cidlen shouldn't ever be zero here but just in case that
- // behavior changes in ngtcp2 in the future...
- if (LIKELY(cidlen > 0))
- EntropySource(cid->data, cidlen);
-}
-
-// Check required capabilities were not excluded from the OpenSSL build:
-// - OPENSSL_NO_SSL_TRACE excludes SSL_trace()
-// - OPENSSL_NO_STDIO excludes BIO_new_fp()
-// HAVE_SSL_TRACE is available on the internal tcp_wrap binding for the tests.
-#if defined(OPENSSL_NO_SSL_TRACE) || defined(OPENSSL_NO_STDIO)
-# define HAVE_SSL_TRACE 0
-#else
-# define HAVE_SSL_TRACE 1
-#endif
-
-QuicCryptoContext::~QuicCryptoContext() {
- // Free any remaining crypto handshake data (if any)
- Cancel();
-}
-
-void QuicCryptoContext::MemoryInfo(MemoryTracker* tracker) const {
- tracker->TrackField("initial_crypto", handshake_[0]);
- tracker->TrackField("handshake_crypto", handshake_[1]);
- tracker->TrackField("app_crypto", handshake_[2]);
- tracker->TrackField("ocsp_response", ocsp_response_);
-}
-
-bool QuicCryptoContext::SetSecrets(
- ngtcp2_crypto_level level,
- const uint8_t* rx_secret,
- const uint8_t* tx_secret,
- size_t secretlen) {
-
- static constexpr int kCryptoKeylen = 64;
- static constexpr int kCryptoIvlen = 64;
- static constexpr char kQuicClientEarlyTrafficSecret[] =
- "QUIC_CLIENT_EARLY_TRAFFIC_SECRET";
- static constexpr char kQuicClientHandshakeTrafficSecret[] =
- "QUIC_CLIENT_HANDSHAKE_TRAFFIC_SECRET";
- static constexpr char kQuicClientTrafficSecret0[] =
- "QUIC_CLIENT_TRAFFIC_SECRET_0";
- static constexpr char kQuicServerHandshakeTrafficSecret[] =
- "QUIC_SERVER_HANDSHAKE_TRAFFIC_SECRET";
- static constexpr char kQuicServerTrafficSecret[] =
- "QUIC_SERVER_TRAFFIC_SECRET_0";
-
- uint8_t rx_key[kCryptoKeylen];
- uint8_t rx_hp[kCryptoKeylen];
- uint8_t tx_key[kCryptoKeylen];
- uint8_t tx_hp[kCryptoKeylen];
- uint8_t rx_iv[kCryptoIvlen];
- uint8_t tx_iv[kCryptoIvlen];
-
- if (NGTCP2_ERR(ngtcp2_crypto_derive_and_install_rx_key(
- session()->connection(),
- rx_key,
- rx_iv,
- rx_hp,
- level,
- rx_secret,
- secretlen))) {
- return false;
- }
-
- if (NGTCP2_ERR(ngtcp2_crypto_derive_and_install_tx_key(
- session()->connection(),
- tx_key,
- tx_iv,
- tx_hp,
- level,
- tx_secret,
- secretlen))) {
- return false;
- }
-
- switch (level) {
- case NGTCP2_CRYPTO_LEVEL_EARLY:
- crypto::LogSecret(
- ssl_,
- kQuicClientEarlyTrafficSecret,
- rx_secret,
- secretlen);
- break;
- case NGTCP2_CRYPTO_LEVEL_HANDSHAKE:
- crypto::LogSecret(
- ssl_,
- kQuicClientHandshakeTrafficSecret,
- rx_secret,
- secretlen);
- crypto::LogSecret(
- ssl_,
- kQuicServerHandshakeTrafficSecret,
- tx_secret,
- secretlen);
- break;
- case NGTCP2_CRYPTO_LEVEL_APP:
- crypto::LogSecret(
- ssl_,
- kQuicClientTrafficSecret0,
- rx_secret,
- secretlen);
- crypto::LogSecret(
- ssl_,
- kQuicServerTrafficSecret,
- tx_secret,
- secretlen);
- break;
- default:
- UNREACHABLE();
- }
-
- return true;
-}
-
-void QuicCryptoContext::AcknowledgeCryptoData(
- ngtcp2_crypto_level level,
- uint64_t datalen) {
- // It is possible for the QuicSession to have been destroyed but not yet
- // deconstructed. In such cases, we want to ignore the callback as there
- // is nothing to do but wait for further cleanup to happen.
- if (UNLIKELY(session_->is_destroyed()))
- return;
- Debug(session(),
- "Acknowledging %" PRIu64 " crypto bytes for %s level",
- datalen,
- crypto_level_name(level));
-
- // Consumes (frees) the given number of bytes in the handshake buffer.
- handshake_[level].Consume(static_cast<size_t>(datalen));
-
- // Update the statistics for the handshake, allowing us to track
- // how long the handshake is taking to be acknowledged. A malicious
- // peer could potentially force the QuicSession to hold on to
- // crypto data for a long time by not sending an acknowledgement.
- // The histogram will allow us to track the time periods between
- // acknowlegements.
- session()->RecordAck(&QuicSessionStats::handshake_acked_at);
-}
-
-void QuicCryptoContext::EnableTrace() {
-#if HAVE_SSL_TRACE
- if (!bio_trace_) {
- bio_trace_.reset(BIO_new_fp(stderr, BIO_NOCLOSE | BIO_FP_TEXT));
- SSL_set_msg_callback(
- ssl_.get(),
- [](int write_p,
- int version,
- int content_type,
- const void* buf,
- size_t len,
- SSL* ssl,
- void* arg) -> void {
- crypto::MarkPopErrorOnReturn mark_pop_error_on_return;
- SSL_trace(write_p, version, content_type, buf, len, ssl, arg);
- });
- SSL_set_msg_callback_arg(ssl_.get(), bio_trace_.get());
- }
-#endif
-}
-
-// If a 'clientHello' event listener is registered on the JavaScript
-// QuicServerSession object, the STATE_CLIENT_HELLO_ENABLED state
-// will be set and the OnClientHello will cause the 'clientHello'
-// event to be emitted.
-//
-// The 'clientHello' callback will be given it's own callback function
-// that must be called when the client has completed handling the event.
-// The handshake will not continue until it is called.
-//
-// The intent here is to allow user code the ability to modify or
-// replace the SecurityContext based on the server name, ALPN, or
-// other handshake characteristics.
-//
-// The user can also set a 'cert' event handler that will be called
-// when the peer certificate is received, allowing additional tweaks
-// and verifications to be performed.
-int QuicCryptoContext::OnClientHello() {
- if (LIKELY(session_->state_->client_hello_enabled == 0))
- return 0;
-
- TLSCallbackScope callback_scope(this);
-
- if (is_in_client_hello())
- return -1;
- set_in_client_hello();
-
- QuicCryptoContext* ctx = session_->crypto_context();
- session_->listener()->OnClientHello(
- ctx->hello_alpn(),
- ctx->hello_servername());
-
- // Returning -1 here will keep the TLS handshake paused until the
- // client hello callback is invoked. Returning 0 means that the
- // handshake is ready to proceed. When the OnClientHello callback
- // is called above, it may be resolved synchronously or asynchronously.
- // In case it is resolved synchronously, we need the check below.
- return is_in_client_hello() ? -1 : 0;
-}
-
-// The OnCert callback provides an opportunity to prompt the server to
-// perform on OCSP request on behalf of the client (when the client
-// requests it). If there is a listener for the 'OCSPRequest' event
-// on the JavaScript side, the IDX_QUIC_SESSION_STATE_CERT_ENABLED
-// session state slot will equal 1, which will cause the callback to
-// be invoked. The callback will be given a reference to a JavaScript
-// function that must be called in order for the TLS handshake to
-// continue.
-int QuicCryptoContext::OnOCSP() {
- if (LIKELY(session_->state_->ocsp_enabled == 0)) {
- Debug(session(), "No OCSPRequest handler registered");
- return 1;
- }
-
- if (!session_->is_server())
- return 1;
-
- Debug(session(), "Client is requesting an OCSP Response");
- TLSCallbackScope callback_scope(this);
-
- // As in node_crypto.cc, this is not an error, but does suspend the
- // handshake to continue when OnOCSP is complete.
- if (is_in_ocsp_request())
- return -1;
- set_in_ocsp_request();
-
- session_->listener()->OnCert(session_->crypto_context()->servername());
-
- // Returning -1 here means that we are still waiting for the OCSP
- // request to be completed. When the OnCert handler is invoked
- // above, it can be resolve synchronously or asynchonously. If
- // resolved synchronously, we need the check below.
- return is_in_ocsp_request() ? -1 : 1;
-}
-
-void QuicCryptoContext::OnClientHelloDone(
- BaseObjectPtr<SecureContext> context) {
- Debug(session(),
- "ClientHello completed. Context Provided? %s\n",
- context ? "Yes" : "No");
-
- // Continue the TLS handshake when this function exits
- // otherwise it will stall and fail.
- TLSHandshakeScope handshake_scope(
- this,
- [&]() { set_in_client_hello(false); });
-
- // Disable the callback at this point so we don't loop continuously
- session_->state_->client_hello_enabled = 0;
-
- if (context) {
- int err = crypto::UseSNIContext(ssl_, context);
- if (!err) {
- unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
- return !err ?
- THROW_ERR_QUIC_FAILURE_SETTING_SNI_CONTEXT(session_->env()) :
- crypto::ThrowCryptoError(session_->env(), err);
- }
- secure_context_ = context;
- }
-}
-
-// The OnCertDone function is called by the QuicSessionOnCertDone
-// function when usercode is done handling the OCSPRequest event.
-void QuicCryptoContext::OnOCSPDone(Local<Value> ocsp_response) {
- Debug(session(),
- "OCSPRequest completed. Response Provided? %s",
- ocsp_response->IsArrayBufferView() ? "Yes" : "No");
- // Continue the TLS handshake when this function exits
- // otherwise it will stall and fail.
- TLSHandshakeScope handshake_scope(
- this,
- [&]() { set_in_ocsp_request(false); });
-
- // Disable the callback at this point so we don't loop continuously
- session_->state_->ocsp_enabled = 0;
-
- if (ocsp_response->IsArrayBufferView()) {
- ocsp_response_.Reset(
- session_->env()->isolate(),
- ocsp_response.As<ArrayBufferView>());
- }
-}
-
-// At this point in time, the TLS handshake secrets have been
-// generated by openssl for this end of the connection and are
-// ready to be used. Within this function, we need to install
-// the secrets into the ngtcp2 connection object, store the
-// remote transport parameters, and begin initialization of
-// the QuicApplication that was selected.
-bool QuicCryptoContext::OnSecrets(
- ngtcp2_crypto_level level,
- const uint8_t* rx_secret,
- const uint8_t* tx_secret,
- size_t secretlen) {
-
- Debug(session(),
- "Received secrets for %s crypto level",
- crypto_level_name(level));
-
- if (!SetSecrets(level, rx_secret, tx_secret, secretlen))
- return false;
-
- if (level == NGTCP2_CRYPTO_LEVEL_APP) {
- session_->set_remote_transport_params();
- if (!session()->InitApplication())
- return false;
- }
-
- return true;
-}
-
-// When the client has requested OSCP, this function will be called to provide
-// the OSCP response. The OnCert() callback should have already been called
-// by this point if any data is to be provided. If it hasn't, and ocsp_response_
-// is empty, no OCSP response will be sent.
-int QuicCryptoContext::OnTLSStatus() {
- Environment* env = session_->env();
- HandleScope scope(env->isolate());
- Context::Scope context_scope(env->context());
- switch (side_) {
- case NGTCP2_CRYPTO_SIDE_SERVER: {
- if (ocsp_response_.IsEmpty()) {
- Debug(session(), "There is no OCSP response");
- return SSL_TLSEXT_ERR_NOACK;
- }
-
- Local<ArrayBufferView> obj =
- PersistentToLocal::Default(
- env->isolate(),
- ocsp_response_);
- size_t len = obj->ByteLength();
-
- unsigned char* data = crypto::MallocOpenSSL<unsigned char>(len);
- obj->CopyContents(data, len);
-
- Debug(session(), "There is an OCSP response of %d bytes", len);
-
- if (!SSL_set_tlsext_status_ocsp_resp(ssl_.get(), data, len))
- OPENSSL_free(data);
-
- ocsp_response_.Reset();
-
- return SSL_TLSEXT_ERR_OK;
- }
- case NGTCP2_CRYPTO_SIDE_CLIENT: {
- // Only invoke the callback if the ocsp handler is actually set
- if (LIKELY(session_->state_->ocsp_enabled == 0))
- return 1;
- Local<Value> res;
- if (ocsp_response().ToLocal(&res))
- session_->listener()->OnOCSP(res);
- return 1;
- }
- default:
- UNREACHABLE();
- }
-}
-
-// Called by ngtcp2 when a chunk of peer TLS handshake data is received.
-// For every chunk, we move the TLS handshake further along until it
-// is complete.
-int QuicCryptoContext::Receive(
- ngtcp2_crypto_level crypto_level,
- uint64_t offset,
- const uint8_t* data,
- size_t datalen) {
- if (UNLIKELY(session_->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- // Statistics are collected so we can monitor how long the
- // handshake is taking to operate and complete.
- if (session_->GetStat(&QuicSessionStats::handshake_start_at) == 0)
- session_->RecordTimestamp(&QuicSessionStats::handshake_start_at);
- session_->RecordTimestamp(&QuicSessionStats::handshake_continue_at);
-
- Debug(session(), "Receiving %d bytes of crypto data", datalen);
-
- // Internally, this passes the handshake data off to openssl
- // for processing. The handshake may or may not complete.
- int ret = ngtcp2_crypto_read_write_crypto_data(
- session_->connection(),
- crypto_level,
- data,
- datalen);
- switch (ret) {
- case 0:
- return 0;
- // In either of following cases, the handshake is being
- // paused waiting for user code to take action (for instance
- // OCSP requests or client hello modification)
- case NGTCP2_CRYPTO_ERR_TLS_WANT_X509_LOOKUP:
- Debug(session(), "TLS handshake wants X509 Lookup");
- return 0;
- case NGTCP2_CRYPTO_ERR_TLS_WANT_CLIENT_HELLO_CB:
- Debug(session(), "TLS handshake wants client hello callback");
- return 0;
- default:
- return ret;
- }
-}
-
-// Triggers key update to begin. This will fail and return false
-// if either a previous key update is in progress and has not been
-// confirmed or if the initial handshake has not yet been confirmed.
-bool QuicCryptoContext::InitiateKeyUpdate() {
- if (UNLIKELY(session_->is_destroyed()))
- return false;
-
- // There's no user code that should be able to run while UpdateKey
- // is running, but we need to gate on it just to be safe.
- auto leave = OnScopeLeave([&]() { set_in_key_update(false); });
- CHECK(!is_in_key_update());
- set_in_key_update();
- Debug(session(), "Initiating Key Update");
-
- session_->IncrementStat(&QuicSessionStats::keyupdate_count);
-
- return ngtcp2_conn_initiate_key_update(
- session_->connection(),
- uv_hrtime()) == 0;
-}
-
-int QuicCryptoContext::VerifyPeerIdentity() {
- return crypto::VerifyPeerCertificate(ssl_);
-}
-
-// Write outbound TLS handshake data into the ngtcp2 connection
-// to prepare it to be serialized. The outbound data must be
-// stored in the handshake_ until it is acknowledged by the
-// remote peer. It's important to keep in mind that there is
-// a potential security risk here -- that is, a malicious peer
-// can cause the local session to keep sent handshake data in
-// memory by failing to acknowledge it or slowly acknowledging
-// it. We currently do not track how much data is being buffered
-// here but we do record statistics on how long the handshake
-// data is foreced to be kept in memory.
-void QuicCryptoContext::WriteHandshake(
- ngtcp2_crypto_level level,
- const uint8_t* data,
- size_t datalen) {
- Debug(session(),
- "Writing %d bytes of %s handshake data.",
- datalen,
- crypto_level_name(level));
- std::unique_ptr<QuicBufferChunk> buffer =
- std::make_unique<QuicBufferChunk>(datalen);
- memcpy(buffer->out(), data, datalen);
- session_->RecordTimestamp(&QuicSessionStats::handshake_send_at);
- CHECK_EQ(
- ngtcp2_conn_submit_crypto_data(
- session_->connection(),
- level,
- buffer->out(),
- datalen), 0);
- handshake_[level].Push(std::move(buffer));
-}
-
-void QuicApplication::Acknowledge(
- int64_t stream_id,
- uint64_t offset,
- size_t datalen) {
- BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id);
- if (LIKELY(stream)) {
- stream->Acknowledge(offset, datalen);
- ResumeStream(stream_id);
- }
-}
-
-bool QuicApplication::SendPendingData() {
- // The maximum number of packets to send per call
- static constexpr size_t kMaxPackets = 16;
- QuicPathStorage path;
- std::unique_ptr<QuicPacket> packet;
- uint8_t* pos = nullptr;
- size_t packets_sent = 0;
- int err;
-
- for (;;) {
- ssize_t ndatalen;
- StreamData stream_data;
- err = GetStreamData(&stream_data);
- if (err < 0) {
- session()->set_last_error(QUIC_ERROR_APPLICATION, err);
- return false;
- }
-
- // If stream_data.id is -1, then we're not serializing any data for any
- // specific stream. We still need to process QUIC session packets tho.
- if (stream_data.id > -1)
- Debug(session(), "Serializing packets for stream id %" PRId64,
- stream_data.id);
- else
- Debug(session(), "Serializing session packets");
-
- // If the packet was sent previously, then packet will have been reset.
- if (!packet) {
- packet = CreateStreamDataPacket();
- pos = packet->data();
- }
-
- ssize_t nwrite = WriteVStream(&path, pos, &ndatalen, stream_data);
-
- if (nwrite <= 0) {
- switch (nwrite) {
- case 0:
- goto congestion_limited;
- case NGTCP2_ERR_PKT_NUM_EXHAUSTED:
- // There is a finite number of packets that can be sent
- // per connection. Once those are exhausted, there's
- // absolutely nothing we can do except immediately
- // and silently tear down the QuicSession. This has
- // to be silent because we can't even send a
- // CONNECTION_CLOSE since even those require a
- // packet number.
- session()->Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
- return false;
- case NGTCP2_ERR_STREAM_DATA_BLOCKED:
- session()->StreamDataBlocked(stream_data.id);
- if (session()->max_data_left() == 0)
- goto congestion_limited;
- // Fall through
- case NGTCP2_ERR_STREAM_SHUT_WR:
- if (UNLIKELY(!BlockStream(stream_data.id)))
- return false;
- continue;
- case NGTCP2_ERR_STREAM_NOT_FOUND:
- continue;
- case NGTCP2_ERR_WRITE_MORE:
- CHECK_GT(ndatalen, 0);
- CHECK(StreamCommit(&stream_data, ndatalen));
- pos += ndatalen;
- continue;
- }
- session()->set_last_error(QUIC_ERROR_SESSION, static_cast<int>(nwrite));
- return false;
- }
-
- pos += nwrite;
-
- if (ndatalen >= 0)
- CHECK(StreamCommit(&stream_data, ndatalen));
-
- Debug(session(), "Sending %" PRIu64 " bytes in serialized packet", nwrite);
- packet->set_length(nwrite);
- if (!session()->SendPacket(std::move(packet), path))
- return false;
- packet.reset();
- pos = nullptr;
- MaybeSetFin(stream_data);
- if (++packets_sent == kMaxPackets)
- break;
- }
- return true;
-
-congestion_limited:
- // We are either congestion limited or done.
- if (pos - packet->data()) {
- // Some data was serialized into the packet. We need to send it.
- packet->set_length(pos - packet->data());
- Debug(session(), "Congestion limited, but %" PRIu64 " bytes pending",
- packet->length());
- if (!session()->SendPacket(std::move(packet), path))
- return false;
- }
- return true;
-}
-
-ssize_t QuicApplication::WriteVStream(
- QuicPathStorage* path,
- uint8_t* buf,
- ssize_t* ndatalen,
- const StreamData& stream_data) {
- CHECK_LE(stream_data.count, kMaxVectorCount);
-
- uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_NONE;
- if (stream_data.remaining > 0)
- flags |= NGTCP2_WRITE_STREAM_FLAG_MORE;
- if (stream_data.fin)
- flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
-
- return ngtcp2_conn_writev_stream(
- session()->connection(),
- &path->path,
- buf,
- session()->max_packet_length(),
- ndatalen,
- flags,
- stream_data.id,
- stream_data.buf,
- stream_data.count,
- uv_hrtime());
-}
-
-void QuicApplication::MaybeSetFin(const StreamData& stream_data) {
- if (ShouldSetFin(stream_data))
- set_stream_fin(stream_data.id);
-}
-
-void QuicApplication::StreamHeaders(
- int64_t stream_id,
- int kind,
- const std::vector<std::unique_ptr<QuicHeader>>& headers,
- int64_t push_id) {
- session()->listener()->OnStreamHeaders(stream_id, kind, headers, push_id);
-}
-
-void QuicApplication::StreamClose(
- int64_t stream_id,
- uint64_t app_error_code) {
- BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id);
- if (stream) {
- CHECK(!stream->is_destroyed()); // Should not be possible
- stream->set_destroyed();
- stream->CancelPendingWrites();
- session()->RemoveStream(stream_id);
- session()->listener()->OnStreamClose(stream_id, app_error_code);
- }
-}
-
-void QuicApplication::StreamReset(
- int64_t stream_id,
- uint64_t app_error_code) {
- session()->listener()->OnStreamReset(stream_id, app_error_code);
-}
-
-// Determines which QuicApplication variant the QuicSession will be using
-// based on the alpn configured for the application. For now, this is
-// determined through configuration when tghe QuicSession is created
-// and is not negotiable. In the future, we may allow it to be negotiated.
-QuicApplication* QuicSession::SelectApplication(QuicSession* session) {
- std::string alpn = session->alpn();
- if (alpn == NGHTTP3_ALPN_H3) {
- Debug(this, "Selecting HTTP/3 Application");
- return new Http3Application(session);
- }
- // In the future, we may end up supporting additional
- // QUIC protocols. As they are added, extend the cases
- // here to create and return them.
-
- Debug(this, "Selecting Default Application");
- return new DefaultApplication(session);
-}
-
-// Server QuicSession Constructor
-QuicSession::QuicSession(
- QuicSocket* socket,
- const QuicSessionConfig& config,
- Local<Object> wrap,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- const QuicCID& dcid,
- const QuicCID& scid,
- const QuicCID& ocid,
- uint32_t version,
- const std::string& alpn,
- uint32_t options,
- QlogMode qlog)
- : QuicSession(
- NGTCP2_CRYPTO_SIDE_SERVER,
- socket,
- wrap,
- socket->server_secure_context(),
- AsyncWrap::PROVIDER_QUICSERVERSESSION,
- alpn,
- std::string(""), // empty hostname. not used on server side
- dcid,
- options,
- nullptr) {
- // The config is copied by assignment in the call below.
- InitServer(config, local_addr, remote_addr, dcid, scid, ocid, version, qlog);
-}
-
-// Client QuicSession Constructor
-QuicSession::QuicSession(
- QuicSocket* socket,
- v8::Local<v8::Object> wrap,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- BaseObjectPtr<SecureContext> secure_context,
- ngtcp2_transport_params* early_transport_params,
- crypto::SSLSessionPointer early_session_ticket,
- Local<Value> dcid,
- PreferredAddressStrategy preferred_address_strategy,
- const std::string& alpn,
- const std::string& hostname,
- uint32_t options,
- QlogMode qlog)
- : QuicSession(
- NGTCP2_CRYPTO_SIDE_CLIENT,
- socket,
- wrap,
- secure_context,
- AsyncWrap::PROVIDER_QUICCLIENTSESSION,
- alpn,
- hostname,
- QuicCID(),
- options,
- preferred_address_strategy) {
- set_wrapped();
- InitClient(
- local_addr,
- remote_addr,
- early_transport_params,
- std::move(early_session_ticket),
- dcid,
- qlog);
-}
-
-// QuicSession is an abstract base class that defines the code used by both
-// server and client sessions.
-QuicSession::QuicSession(
- ngtcp2_crypto_side side,
- QuicSocket* socket,
- Local<Object> wrap,
- BaseObjectPtr<SecureContext> secure_context,
- AsyncWrap::ProviderType provider_type,
- const std::string& alpn,
- const std::string& hostname,
- const QuicCID& dcid,
- uint32_t options,
- PreferredAddressStrategy preferred_address_strategy)
- : AsyncWrap(socket->env(), wrap, provider_type),
- StatsBase(socket->env(), wrap,
- HistogramOptions::ACK |
- HistogramOptions::RATE),
- alloc_info_(MakeAllocator()),
- socket_(socket),
- alpn_(alpn),
- hostname_(hostname),
- idle_(socket->env(), [this]() { OnIdleTimeout(); }),
- retransmit_(socket->env(), [this]() { OnRetransmitTimeout(); }),
- dcid_(dcid),
- state_(env()->isolate()),
- quic_state_(socket->quic_state()) {
- PushListener(&default_listener_);
- set_connection_id_strategy(RandomConnectionIDStrategy);
- set_preferred_address_strategy(preferred_address_strategy);
- crypto_context_ = std::make_unique<QuicCryptoContext>(
-
- this,
- secure_context,
- side,
- options);
- application_.reset(SelectApplication(this));
-
- wrap->DefineOwnProperty(
- env()->context(),
- env()->state_string(),
- state_.GetArrayBuffer(),
- PropertyAttribute::ReadOnly).Check();
-
- idle_.Unref();
- retransmit_.Unref();
-
- // TODO(@jasnell): memory accounting
- // env_->isolate()->AdjustAmountOfExternalAllocatedMemory(kExternalSize);
-}
-
-QuicSession::~QuicSession() {
- CHECK(!NgCallbackScope::InNgCallbackScope(this));
-
- // The next write should be the final one
- if (qlog_stream_)
- qlog_stream_->End();
-
- QuicSessionListener* listener_ = listener();
- if (listener_ == listener())
- RemoveListener(listener_);
-
- // Stop and free the idle and retransmission timers if they are active.
- // In a clean shutdown, using Close(), these will have already been
- // stopped, but if Close() was not called and we're being destroyed
- // in GC, for instance, we need to make sure they get stopped here.
- idle_.Stop();
- retransmit_.Stop();
-
- DebugStats();
-}
-
-template <typename Fn>
-void QuicSessionStatsTraits::ToString(const QuicSession& ptr, Fn&& add_field) {
-#define V(_n, name, label) \
- add_field(label, ptr.GetStat(&QuicSessionStats::name));
- SESSION_STATS(V)
-#undef V
-}
-
-void QuicSession::PushListener(QuicSessionListener* listener) {
- CHECK_NOT_NULL(listener);
- CHECK(!listener->session_);
-
- listener->previous_listener_ = listener_;
- listener->session_.reset(this);
-
- listener_ = listener;
-}
-
-void QuicSession::RemoveListener(QuicSessionListener* listener) {
- CHECK_NOT_NULL(listener);
-
- QuicSessionListener* previous;
- QuicSessionListener* current;
-
- for (current = listener_, previous = nullptr;
- /* No loop condition because we want a crash if listener is not found */
- ; previous = current, current = current->previous_listener_) {
- CHECK_NOT_NULL(current);
- if (current == listener) {
- if (previous != nullptr)
- previous->previous_listener_ = current->previous_listener_;
- else
- listener_ = listener->previous_listener_;
- break;
- }
- }
-
- listener->session_.reset();
- listener->previous_listener_ = nullptr;
-}
-
-// The diagnostic_name is used in Debug output.
-std::string QuicSession::diagnostic_name() const {
- return std::string("QuicSession ") +
- (is_server() ? "Server" : "Client") +
- " (" + alpn().substr(1) + ", " +
- std::to_string(static_cast<int64_t>(get_async_id())) + ")";
-}
-
-// Locate the QuicStream with the given id or return nullptr
-BaseObjectPtr<QuicStream> QuicSession::FindStream(int64_t id) const {
- auto it = streams_.find(id);
- return it == std::end(streams_) ? BaseObjectPtr<QuicStream>() : it->second;
-}
-
-// Invoked when ngtcp2 receives an acknowledgement for stream data.
-void QuicSession::AckedStreamDataOffset(
- int64_t stream_id,
- uint64_t offset,
- uint64_t datalen) {
- Debug(this,
- "Received acknowledgement for %" PRIu64
- " bytes of stream %" PRId64 " data",
- datalen, stream_id);
-
- application_->AcknowledgeStreamData(
- stream_id,
- offset,
- static_cast<size_t>(datalen));
-}
-
-// Attaches the session to the given QuicSocket. The complexity
-// here is that any CID's associated with the session have to
-// be associated with the new QuicSocket.
-void QuicSession::AddToSocket(QuicSocket* socket) {
- CHECK_NOT_NULL(socket);
- Debug(this, "Adding QuicSession to %s", socket->diagnostic_name());
- socket->AddSession(scid_, BaseObjectPtr<QuicSession>(this));
- switch (crypto_context_->side()) {
- case NGTCP2_CRYPTO_SIDE_SERVER: {
- socket->AssociateCID(dcid_, scid_);
- socket->AssociateCID(pscid_, scid_);
- break;
- }
- case NGTCP2_CRYPTO_SIDE_CLIENT: {
- std::vector<ngtcp2_cid> cids(ngtcp2_conn_get_num_scid(connection()));
- ngtcp2_conn_get_scid(connection(), cids.data());
- for (const ngtcp2_cid& cid : cids) {
- socket->AssociateCID(QuicCID(&cid), scid_);
- }
- break;
- }
- default:
- UNREACHABLE();
- }
-
- std::vector<ngtcp2_cid_token> tokens(
- ngtcp2_conn_get_num_active_dcid(connection()));
- ngtcp2_conn_get_active_dcid(connection(), tokens.data());
- for (const ngtcp2_cid_token& token : tokens) {
- if (token.token_present) {
- socket->AssociateStatelessResetToken(
- StatelessResetToken(token.token),
- BaseObjectPtr<QuicSession>(this));
- }
- }
-}
-
-// Add the given QuicStream to this QuicSession's collection of streams. All
-// streams added must be removed before the QuicSession instance is freed.
-void QuicSession::AddStream(BaseObjectPtr<QuicStream> stream) {
- DCHECK(!is_graceful_closing());
- Debug(this, "Adding stream %" PRId64 " to session", stream->id());
- streams_.emplace(stream->id(), stream);
-
- // Update tracking statistics for the number of streams associated with
- // this session.
- switch (stream->origin()) {
- case QuicStreamOrigin::QUIC_STREAM_CLIENT:
- if (is_server())
- IncrementStat(&QuicSessionStats::streams_in_count);
- else
- IncrementStat(&QuicSessionStats::streams_out_count);
- break;
- case QuicStreamOrigin::QUIC_STREAM_SERVER:
- if (is_server())
- IncrementStat(&QuicSessionStats::streams_out_count);
- else
- IncrementStat(&QuicSessionStats::streams_in_count);
- }
- IncrementStat(&QuicSessionStats::streams_out_count);
- switch (stream->direction()) {
- case QuicStreamDirection::QUIC_STREAM_BIRECTIONAL:
- IncrementStat(&QuicSessionStats::bidi_stream_count);
- break;
- case QuicStreamDirection::QUIC_STREAM_UNIDIRECTIONAL:
- IncrementStat(&QuicSessionStats::uni_stream_count);
- break;
- }
-}
-
-// Creates a new stream object and passes it off to the javascript side.
-// This has to be called from within a handlescope/contextscope.
-BaseObjectPtr<QuicStream> QuicSession::CreateStream(int64_t stream_id) {
- CHECK(!is_destroyed());
- CHECK(!is_graceful_closing());
- CHECK(!is_closing());
-
- BaseObjectPtr<QuicStream> stream = QuicStream::New(this, stream_id);
- CHECK(stream);
- listener()->OnStreamReady(stream);
- return stream;
-}
-
-// Initiate a shutdown of the QuicSession.
-void QuicSession::Close(int close_flags) {
- if (is_destroyed())
- return;
- bool silent = close_flags & QuicSessionListener::SESSION_CLOSE_FLAG_SILENT;
- bool stateless_reset = is_stateless_reset();
-
- // If we're not running within a ngtcp2 callback scope, schedule
- // a CONNECTION_CLOSE to be sent when Close exits. If we are
- // within a ngtcp2 callback scope, sending the CONNECTION_CLOSE
- // will be deferred.
- ConnectionCloseScope close_scope(this, silent);
-
- // Once Close has been called, we cannot re-enter
- if (UNLIKELY(is_closing()))
- return;
-
- set_closing();
- set_silent_closing(silent);
-
- if (stateless_reset && silent)
- close_flags |= QuicSessionListener::SESSION_CLOSE_FLAG_STATELESS_RESET;
-
- QuicError error = last_error();
- Debug(this, "Closing with code %" PRIu64
- " (family: %s, silent: %s, stateless reset: %s)",
- error.code,
- error.family_name(),
- silent ? "Y" : "N",
- stateless_reset ? "Y" : "N");
-
- // Ensure that the QuicSession is not freed at least until after we
- // exit this scope.
- BaseObjectPtr<QuicSession> ptr(this);
-
- // If the QuicSession has been wrapped by a JS object, we have to
- // notify the JavaScript side that the session is being closed.
- // If it hasn't yet been wrapped, we can skip the call and and
- // go straight to destroy.
- if (is_wrapped())
- listener()->OnSessionClose(error, close_flags);
- else
- Destroy();
-}
-
-// Mark the QuicSession instance destroyed. This will either be invoked
-// synchronously within the callstack of the QuicSession::Close() method
-// or not. If it is invoked within QuicSession::Close(), the
-// QuicSession::Close() will handle sending the CONNECTION_CLOSE
-// frame.
-void QuicSession::Destroy() {
- if (is_destroyed())
- return;
-
- Debug(this, "Destroying the QuicSession");
-
- // Mark the session destroyed.
- set_destroyed();
- set_closing(false);
- set_graceful_closing(false);
-
- // TODO(@jasnell): Allow overriding the close code
-
- // If we're not already in a ConnectionCloseScope, schedule
- // sending a CONNECTION_CLOSE when destroy exits. If we are
- // running within an ngtcp2 callback scope, sending the
- // CONNECTION_CLOSE will be deferred.
- ConnectionCloseScope close_scope(this, is_silent_closing());
-
- // All existing streams should have already been destroyed
- CHECK(streams_.empty());
-
- // Stop and free the idle and retransmission timers if they are active.
- idle_.Stop();
- retransmit_.Stop();
-
- // The QuicSession instances are kept alive using
- // BaseObjectPtr. The only persistent BaseObjectPtr
- // is the map in the associated QuicSocket. Removing
- // the QuicSession from the QuicSocket will free
- // that pointer, allowing the QuicSession to be
- // deconstructed once the stack unwinds and any
- // remaining BaseObjectPtr<QuicSession> instances
- // fall out of scope.
- RemoveFromSocket();
-}
-
-// Generates and associates a new connection ID for this QuicSession.
-// ngtcp2 will call this multiple times at the start of a new connection
-// in order to build a pool of available CIDs.
-void QuicSession::GetNewConnectionID(
- ngtcp2_cid* cid,
- uint8_t* token,
- size_t cidlen) {
- CHECK_NOT_NULL(connection_id_strategy_);
- connection_id_strategy_(this, cid, cidlen);
- QuicCID cid_(cid);
- StatelessResetToken(token, socket()->session_reset_secret(), cid_);
- socket()->AssociateCID(cid_, scid_);
-}
-
-void QuicSession::HandleError() {
- if (is_destroyed())
- return;
-
- // If the QuicSession is a server, send a CONNECTION_CLOSE. In either
- // case, the closing timer will be set and the QuicSession will be
- // destroyed.
- if (is_server())
- SendConnectionClose();
- else
- UpdateClosingTimer();
-}
-
-// The the retransmit libuv timer fires, it will call OnRetransmitTimeout,
-// which determines whether or not we need to retransmit data to
-// to packet loss or ack delay.
-void QuicSession::OnRetransmitTimeout() {
- if (is_destroyed())
- return;
- uint64_t now = uv_hrtime();
-
- if (ngtcp2_conn_loss_detection_expiry(connection()) <= now) {
- Debug(this, "Retransmitting due to loss detection");
- IncrementStat(&QuicSessionStats::loss_retransmit_count);
- }
-
- if (ngtcp2_conn_ack_delay_expiry(connection()) <= now) {
- Debug(this, "Retransmitting due to ack delay");
- IncrementStat(&QuicSessionStats::ack_delay_retransmit_count);
- }
-
- int rv = ngtcp2_conn_handle_expiry(connection(), now);
- if (rv != 0) {
- Debug(this, "Error handling retransmit timeout: %s", ngtcp2_strerror(rv));
- set_last_error(QUIC_ERROR_SESSION, rv);
- HandleError();
- }
-
- SendPendingData();
-}
-
-bool QuicSession::OpenBidirectionalStream(int64_t* stream_id) {
- DCHECK(!is_destroyed());
- DCHECK(!is_closing());
- DCHECK(!is_graceful_closing());
- return ngtcp2_conn_open_bidi_stream(connection(), stream_id, nullptr) == 0;
-}
-
-bool QuicSession::OpenUnidirectionalStream(int64_t* stream_id) {
- DCHECK(!is_destroyed());
- DCHECK(!is_closing());
- DCHECK(!is_graceful_closing());
- return ngtcp2_conn_open_uni_stream(connection(), stream_id, nullptr) == 0;
-}
-
-// When ngtcp2 receives a successful response to a PATH_CHALLENGE,
-// it will trigger the OnPathValidation callback which will, in turn
-// invoke this. There's really nothing to do here but update stats and
-// and optionally notify the javascript side if there is a handler registered.
-// Notifying the JavaScript side is purely informational.
-void QuicSession::PathValidation(
- const ngtcp2_path* path,
- ngtcp2_path_validation_result res) {
- if (res == NGTCP2_PATH_VALIDATION_RESULT_SUCCESS) {
- IncrementStat(&QuicSessionStats::path_validation_success_count);
- } else {
- IncrementStat(&QuicSessionStats::path_validation_failure_count);
- }
-
- // Only emit the callback if there is a handler for the pathValidation
- // event on the JavaScript QuicSession object.
- if (UNLIKELY(state_->path_validated_enabled == 1)) {
- listener_->OnPathValidation(
- res,
- reinterpret_cast<const sockaddr*>(path->local.addr),
- reinterpret_cast<const sockaddr*>(path->remote.addr));
- }
-}
-
-// Calling Ping will trigger the ngtcp2_conn to serialize any
-// packets it currently has pending along with a probe frame
-// that should keep the connection alive. This is a fire and
-// forget and any errors that may occur will be ignored. The
-// idle_timeout and retransmit timers will be updated. If Ping
-// is called while processing an ngtcp2 callback, or if the
-// closing or draining period has started, this is a non-op.
-void QuicSession::Ping() {
- if (NgCallbackScope::InNgCallbackScope(this) ||
- is_destroyed() ||
- is_closing() ||
- is_in_closing_period() ||
- is_in_draining_period()) {
- return;
- }
- // TODO(@jasnell): We might want to revisit whether to handle
- // errors right here. For now, we're ignoring them with the
- // intent of capturing them elsewhere.
- WritePackets("ping");
- UpdateIdleTimer();
- ScheduleRetransmit();
-}
-
-// When the QuicSocket receives a QUIC packet, it is forwarded on to here
-// for processing.
-bool QuicSession::Receive(
- ssize_t nread,
- const uint8_t* data,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- unsigned int flags) {
-
- CHECK(!is_destroyed());
-
- Debug(this, "Receiving QUIC packet");
- IncrementStat(&QuicSessionStats::bytes_received, nread);
-
- if (is_in_closing_period() && is_server()) {
- Debug(this, "Packet received while in closing period");
- IncrementConnectionCloseAttempts();
- // For server QuicSession instances, we serialize the connection close
- // packet once but may sent it multiple times. If the client keeps
- // transmitting, then the connection close may have gotten lost.
- // We don't want to send the connection close in response to
- // every received packet, however, so we use an exponential
- // backoff, increasing the ratio of packets received to connection
- // close frame sent with every one we send.
- if (UNLIKELY(ShouldAttemptConnectionClose() &&
- !SendConnectionClose())) {
- Debug(this, "Failure sending another connection close");
- return false;
- }
- }
-
- {
- // These are within a scope to ensure that the InternalCallbackScope
- // and HandleScope are both exited before continuing on with the
- // function. This allows any nextTicks and queued tasks to be processed
- // before we continue.
- auto update_stats = OnScopeLeave([&](){
- UpdateDataStats();
- });
- HandleScope handle_scope(env()->isolate());
- InternalCallbackScope callback_scope(this);
- remote_address_ = remote_addr;
- QuicPath path(local_addr, remote_address_);
- if (!ReceivePacket(&path, data, nread)) {
- HandleError();
- return false;
- }
- }
-
- // Only send pending data if we haven't entered draining mode.
- // We enter the draining period when a CONNECTION_CLOSE has been
- // received from the remote peer.
- if (is_in_draining_period()) {
- Debug(this, "In draining period after processing packet");
- // If processing the packet puts us into draining period, there's
- // absolutely nothing left for us to do except silently close
- // and destroy this QuicSession, which we do by updating the
- // closing timer.
- GetConnectionCloseInfo();
- UpdateClosingTimer();
- return true;
- }
-
- if (!is_destroyed())
- UpdateIdleTimer();
- SendPendingData();
- Debug(this, "Successfully processed received packet");
- return true;
-}
-
-// Performs intake processing on a received QUIC packet. The received
-// data is passed on to ngtcp2 for parsing and processing. ngtcp2 will,
-// in turn, invoke a series of callbacks to handle the received packet.
-bool QuicSession::ReceivePacket(
- ngtcp2_path* path,
- const uint8_t* data,
- ssize_t nread) {
- CHECK(!is_destroyed());
-
- uint64_t now = uv_hrtime();
- SetStat(&QuicSessionStats::received_at, now);
- int err = ngtcp2_conn_read_pkt(connection(), path, data, nread, now);
- if (err < 0) {
- switch (err) {
- case NGTCP2_ERR_CALLBACK_FAILURE:
- case NGTCP2_ERR_DRAINING:
- case NGTCP2_ERR_RECV_VERSION_NEGOTIATION:
- break;
- case NGTCP2_ERR_RETRY:
- // This should only ever happen on the server
- CHECK(is_server());
- socket()->SendRetry(scid_, dcid_, local_address_, remote_address_);
- Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
- break;
- case NGTCP2_ERR_DROP_CONN:
- Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
- break;
- default:
- set_last_error(QUIC_ERROR_SESSION, err);
- return false;
- }
- }
-
- // If the QuicSession has been destroyed but it is not
- // in the closing period, a CONNECTION_CLOSE has not yet
- // been sent to the peer. Let's attempt to send one. This
- // will have the effect of setting the idle timer to the
- // closing/draining period, after which the QuicSession
- // will be destroyed.
- if (is_destroyed() && !is_in_closing_period()) {
- Debug(this, "Session was destroyed while processing the packet");
- return SendConnectionClose();
- }
-
- return true;
-}
-
-// Called by ngtcp2 when a chunk of stream data has been received. If
-// the stream does not yet exist, it is created, then the data is
-// forwarded on.
-bool QuicSession::ReceiveStreamData(
- uint32_t flags,
- int64_t stream_id,
- const uint8_t* data,
- size_t datalen,
- uint64_t offset) {
- auto leave = OnScopeLeave([&]() {
- // Unconditionally extend the flow control window for the entire
- // session but not for the individual Stream.
- ExtendOffset(datalen);
- });
-
- return application_->ReceiveStreamData(
- flags,
- stream_id,
- data,
- datalen,
- offset);
-}
-
-// Removes the QuicSession from the current socket. This is
-// done with when the session is being destroyed or being
-// migrated to another QuicSocket. It is important to keep in mind
-// that the QuicSocket uses a BaseObjectPtr for the QuicSession.
-// If the session is removed and there are no other references held,
-// the session object will be destroyed automatically.
-void QuicSession::RemoveFromSocket() {
- CHECK(socket_);
- Debug(this, "Removing QuicSession from %s", socket_->diagnostic_name());
- if (is_server()) {
- socket_->DisassociateCID(dcid_);
- socket_->DisassociateCID(pscid_);
- }
-
- std::vector<ngtcp2_cid> cids(ngtcp2_conn_get_num_scid(connection()));
- std::vector<ngtcp2_cid_token> tokens(
- ngtcp2_conn_get_num_active_dcid(connection()));
- ngtcp2_conn_get_scid(connection(), cids.data());
- ngtcp2_conn_get_active_dcid(connection(), tokens.data());
-
- for (const ngtcp2_cid& cid : cids)
- socket_->DisassociateCID(QuicCID(&cid));
-
- for (const ngtcp2_cid_token& token : tokens) {
- if (token.token_present) {
- socket_->DisassociateStatelessResetToken(
- StatelessResetToken(token.token));
- }
- }
-
- Debug(this, "Removed from the QuicSocket");
- BaseObjectPtr<QuicSocket> socket = std::move(socket_);
- socket->RemoveSession(scid_, remote_address_);
-}
-
-// Removes the given stream from the QuicSession. All streams must
-// be removed before the QuicSession is destroyed.
-void QuicSession::RemoveStream(int64_t stream_id) {
- Debug(this, "Removing stream %" PRId64, stream_id);
-
- // ngtcp2 does not extend the max streams count automatically
- // except in very specific conditions, none of which apply
- // once we've gotten this far. We need to manually extend when
- // a remote peer initiated stream is removed.
- if (!is_in_draining_period() &&
- !is_in_closing_period() &&
- !is_silent_closing() &&
- !ngtcp2_conn_is_local_stream(connection_.get(), stream_id)) {
- if (ngtcp2_is_bidi_stream(stream_id))
- ngtcp2_conn_extend_max_streams_bidi(connection_.get(), 1);
- else
- ngtcp2_conn_extend_max_streams_uni(connection_.get(), 1);
- }
-
- // This will have the side effect of destroying the QuicStream
- // instance.
- streams_.erase(stream_id);
-}
-
-// The retransmit timer allows us to trigger retransmission
-// of packets in case they are considered lost. The exact amount
-// of time is determined internally by ngtcp2 according to the
-// guidelines established by the QUIC spec but we use a libuv
-// timer to actually monitor.
-void QuicSession::ScheduleRetransmit() {
- uint64_t now = uv_hrtime();
- uint64_t expiry = ngtcp2_conn_get_expiry(connection());
- // now and expiry are in nanoseconds, interval is milliseconds
- uint64_t interval = (expiry < now) ? 1 : (expiry - now) / 1000000UL;
- // If interval ends up being 0, the repeating timer won't be
- // scheduled, so set it to 1 instead.
- if (interval == 0) interval = 1;
- Debug(this, "Scheduling the retransmit timer for %" PRIu64, interval);
- UpdateRetransmitTimer(interval);
-}
-
-bool QuicSession::InitApplication() {
- Debug(this, "Initializing application handler for ALPN %s",
- alpn().c_str() + 1);
- return application_->Initialize();
-}
-
-// Captures the error code and family information from a received
-// connection close frame.
-void QuicSession::GetConnectionCloseInfo() {
- ngtcp2_connection_close_error_code close_code;
- ngtcp2_conn_get_connection_close_error_code(connection(), &close_code);
- set_last_error(QuicError(close_code));
-}
-
-// The HandshakeCompleted function is called by ngtcp2 once it
-// determines that the TLS Handshake is done. The only thing we
-// need to do at this point is let the javascript side know.
-void QuicSession::HandshakeCompleted() {
- RemoteTransportParamsDebug transport_params(this);
- Debug(this, "Handshake is completed. %s", transport_params);
- RecordTimestamp(&QuicSessionStats::handshake_completed_at);
- if (is_server()) HandshakeConfirmed();
- listener()->OnHandshakeCompleted();
-}
-
-void QuicSession::HandshakeConfirmed() {
- Debug(this, "Handshake is confirmed");
- RecordTimestamp(&QuicSessionStats::handshake_confirmed_at);
- state_->handshake_confirmed = 1;
-}
-
-// Every QUIC session has a remote address and local address.
-// Those endpoints can change through the lifetime of a connection,
-// so whenever a packet is successfully processed, or when a
-// response is to be sent, we have to keep track of the path
-// and update as we go.
-void QuicSession::UpdateEndpoint(const ngtcp2_path& path) {
- remote_address_.Update(path.remote.addr, path.remote.addrlen);
- local_address_.Update(path.local.addr, path.local.addrlen);
-
- // If the updated remote address is IPv6, set the flow label
- if (remote_address_.family() == AF_INET6) {
- // TODO(@jasnell): Currently, this reuses the session reset secret.
- // That may or may not be a good idea, we need to verify and may
- // need to have a distinct secret for flow labels.
- uint32_t flow_label =
- GenerateFlowLabel(
- local_address_,
- remote_address_,
- scid_,
- socket()->session_reset_secret(),
- NGTCP2_STATELESS_RESET_TOKENLEN);
- remote_address_.set_flow_label(flow_label);
- }
-}
-
-// Called by the OnVersionNegotiation callback when a version
-// negotiation frame has been received by the client. The sv
-// parameter is an array of versions supported by the remote peer.
-void QuicSession::VersionNegotiation(const uint32_t* sv, size_t nsv) {
- CHECK(!is_server());
- listener()->OnVersionNegotiation(NGTCP2_PROTO_VER, sv, nsv);
-}
-
-// The retransmit timer allows us to trigger retransmission
-// of packets in case they are considered lost. The exact amount
-// of time is determined internally by ngtcp2 according to the
-// guidelines established by the QUIC spec but we use a libuv
-// timer to actually monitor. Here we take the calculated timeout
-// and extend out the libuv timer.
-void QuicSession::UpdateRetransmitTimer(uint64_t timeout) {
- retransmit_.Update(timeout, timeout);
-}
-
-void QuicSession::IncrementConnectionCloseAttempts() {
- if (connection_close_attempts_ < kMaxSizeT)
- connection_close_attempts_++;
-}
-
-bool QuicSession::ShouldAttemptConnectionClose() {
- if (connection_close_attempts_ == connection_close_limit_) {
- if (connection_close_limit_ * 2 <= kMaxSizeT)
- connection_close_limit_ *= 2;
- else
- connection_close_limit_ = kMaxSizeT;
- return true;
- }
- return false;
-}
-
-// Transmits either a protocol or application connection
-// close to the peer. The choice of which is send is
-// based on the current value of last_error_.
-bool QuicSession::SendConnectionClose() {
- CHECK(!NgCallbackScope::InNgCallbackScope(this));
-
- // Do not send any frames at all if we're in the draining period
- // or in the middle of a silent close
- if (is_in_draining_period() || is_silent_closing())
- return true;
-
- // The specific handling of connection close varies for client
- // and server QuicSession instances. For servers, we will
- // serialize the connection close once but may end up transmitting
- // it multiple times; whereas for clients, we will serialize it
- // once and send once only.
- QuicError error = last_error();
- Debug(this, "Sending connection close with code: %" PRIu64 " (family: %s)",
- error.code, error.family_name());
-
- UpdateClosingTimer();
-
- // If initial keys have not yet been installed, use the alternative
- // ImmediateConnectionClose to send a stateless connection close to
- // the peer.
- if (crypto_context()->write_crypto_level() ==
- NGTCP2_CRYPTO_LEVEL_INITIAL) {
- socket()->ImmediateConnectionClose(
- dcid(),
- scid_,
- local_address_,
- remote_address_,
- error.code);
- return true;
- }
-
- switch (crypto_context_->side()) {
- case NGTCP2_CRYPTO_SIDE_SERVER: {
- if (!is_in_closing_period() && !StartClosingPeriod()) {
- Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
- return false;
- }
- CHECK_GT(conn_closebuf_->length(), 0);
- return SendPacket(QuicPacket::Copy(conn_closebuf_));
- }
- case NGTCP2_CRYPTO_SIDE_CLIENT: {
- auto packet = QuicPacket::Create("client connection close");
-
- ssize_t nwrite =
- SelectCloseFn(error.family)(
- connection(),
- nullptr,
- packet->data(),
- max_pktlen_,
- error.code,
- uv_hrtime());
- if (UNLIKELY(nwrite < 0)) {
- Debug(this, "Error writing connection close: %d", nwrite);
- set_last_error(QUIC_ERROR_SESSION, static_cast<int>(nwrite));
- Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
- return false;
- }
- packet->set_length(nwrite);
- return SendPacket(std::move(packet));
- }
- default:
- UNREACHABLE();
- }
-}
-
-void QuicSession::IgnorePreferredAddressStrategy(
- QuicSession* session,
- const PreferredAddress& preferred_address) {
- Debug(session, "Ignoring server preferred address");
-}
-
-void QuicSession::UsePreferredAddressStrategy(
- QuicSession* session,
- const PreferredAddress& preferred_address) {
- int family = session->socket()->local_address().family();
- if (preferred_address.Use(family)) {
- Debug(session, "Using server preferred address");
- // Emit only if the QuicSession has a usePreferredAddress handler
- // on the JavaScript side.
- if (UNLIKELY(session->state_->use_preferred_address_enabled == 1)) {
- session->listener()->OnUsePreferredAddress(family, preferred_address);
- }
- } else {
- // If Use returns false, the advertised preferred address does not
- // match the current local preferred endpoint IP version.
- Debug(session,
- "Not using server preferred address due to IP version mismatch");
- }
-}
-
-// Passes a serialized packet to the associated QuicSocket.
-bool QuicSession::SendPacket(std::unique_ptr<QuicPacket> packet) {
- CHECK(!is_in_draining_period());
-
- // There's nothing to send.
- if (packet->length() == 0)
- return true;
-
- IncrementStat(&QuicSessionStats::bytes_sent, packet->length());
- RecordTimestamp(&QuicSessionStats::sent_at);
-// ScheduleRetransmit();
-
- Debug(this, "Sending %" PRIu64 " bytes to %s from %s",
- packet->length(),
- remote_address_,
- local_address_);
-
- int err = socket()->SendPacket(
- local_address_,
- remote_address_,
- std::move(packet),
- BaseObjectPtr<QuicSession>(this));
-
- if (err != 0) {
- set_last_error(QUIC_ERROR_SESSION, err);
- return false;
- }
-
- return true;
-}
-
-// Sends any pending handshake or session packet data.
-void QuicSession::SendPendingData() {
- if (is_unable_to_send_packets())
- return;
-
- Debug(this, "Sending pending data");
- if (!application_->SendPendingData()) {
- Debug(this, "Error sending QUIC application data");
- HandleError();
- }
- ScheduleRetransmit();
-}
-
-// When completing the TLS handshake, the TLS session information
-// is provided to the QuicSession so that the session ticket and
-// the remote transport parameters can be captured to support 0RTT
-// session resumption.
-int QuicSession::set_session(SSL_SESSION* session) {
- CHECK(!is_server());
- CHECK(!is_destroyed());
- int size = i2d_SSL_SESSION(session, nullptr);
- if (size > SecureContext::kMaxSessionSize)
- return 0;
- listener_->OnSessionTicket(size, session);
- return 0;
-}
-
-// A client QuicSession can be migrated to a different QuicSocket instance.
-bool QuicSession::set_socket(QuicSocket* socket, bool nat_rebinding) {
- CHECK(!is_server());
- CHECK(!is_destroyed());
-
- if (is_graceful_closing())
- return false;
-
- if (socket == nullptr || socket == socket_.get())
- return true;
-
- Debug(this, "Migrating to %s", socket->diagnostic_name());
-
- // Ensure that we maintain a reference to keep this from being
- // destroyed while we are starting the migration.
- BaseObjectPtr<QuicSession> ptr(this);
-
- // Step 1: Remove the session from the current socket
- RemoveFromSocket();
-
- // Step 2: Add this Session to the given Socket
- AddToSocket(socket);
-
- // Step 3: Update the internal references and make sure
- // we are listening.
- socket_.reset(socket);
- socket->ReceiveStart();
-
- // Step 4: Update ngtcp2
- auto local_address = socket->local_address();
-
- // The nat_rebinding option here should rarely, if ever
- // be used in a real application. It is intended to serve
- // as a way of simulating a silent local address change,
- // such as when the NAT binding changes. Currently, Node.js
- // does not really have an effective way of detecting that.
- // Manual user code intervention to handle the migration
- // to the new QuicSocket is required, which should always
- // trigger path validation using the ngtcp2_conn_initiate_migration.
- if (LIKELY(!nat_rebinding)) {
- SendSessionScope send(this);
- QuicPath path(local_address, remote_address_);
- return ngtcp2_conn_initiate_migration(
- connection(),
- &path,
- uv_hrtime()) == 0;
- } else {
- ngtcp2_addr addr;
- ngtcp2_conn_set_local_addr(
- connection(),
- ngtcp2_addr_init(
- &addr,
- local_address.data(),
- local_address.length(),
- nullptr));
- }
-
- return true;
-}
-
-void QuicSession::ResumeStream(int64_t stream_id) {
- application()->ResumeStream(stream_id);
-}
-
-// Begin connection close by serializing the CONNECTION_CLOSE packet.
-// There are two variants: one to serialize an application close, the
-// other to serialize a protocol close. The frames are generally
-// identical with the exception of a bit in the header. On server
-// QuicSessions, we serialize the frame once and may retransmit it
-// multiple times. On client QuicSession instances, we only ever
-// serialize the connection close once.
-bool QuicSession::StartClosingPeriod() {
- if (is_destroyed())
- return false;
- if (is_in_closing_period())
- return true;
-
- QuicError error = last_error();
- Debug(this, "Closing period has started. Error %d", error.code);
-
- // Once the CONNECTION_CLOSE packet is written,
- // is_in_closing_period will return true.
- conn_closebuf_ = QuicPacket::Create("server connection close");
- ssize_t nwrite =
- SelectCloseFn(error.family)(
- connection(),
- nullptr,
- conn_closebuf_->data(),
- max_pktlen_,
- error.code,
- uv_hrtime());
- if (nwrite < 0) {
- set_last_error(QUIC_ERROR_SESSION, static_cast<int>(nwrite));
- return false;
- }
- conn_closebuf_->set_length(nwrite);
- return true;
-}
-
-// Called by ngtcp2 when a stream has been closed. If the stream does
-// not exist, the close is ignored.
-void QuicSession::StreamClose(int64_t stream_id, uint64_t app_error_code) {
- Debug(this, "Closing stream %" PRId64 " with code %" PRIu64,
- stream_id,
- app_error_code);
-
- application_->StreamClose(stream_id, app_error_code);
-}
-
-// Called when the QuicSession has received a RESET_STREAM frame from the
-// peer, indicating that it will no longer send additional frames for the
-// stream. If the stream is not yet known, reset is ignored. If the stream
-// has already received a STREAM frame with fin set, the stream reset is
-// ignored (the QUIC spec permits implementations to handle this situation
-// however they want.) If the stream has not yet received a STREAM frame
-// with the fin set, then the RESET_STREAM causes the readable side of the
-// stream to be abruptly closed and any additional stream frames that may
-// be received will be discarded if their offset is greater than final_size.
-// On the JavaScript side, receiving a C is undistinguishable from
-// a normal end-of-stream. No additional data events will be emitted, the
-// end event will be emitted, and the readable side of the duplex will be
-// closed.
-//
-// If the stream is still writable, no additional action is taken. If,
-// however, the writable side of the stream has been closed (or was never
-// open in the first place as in the case of peer-initiated unidirectional
-// streams), the reset will cause the stream to be immediately destroyed.
-void QuicSession::StreamReset(
- int64_t stream_id,
- uint64_t final_size,
- uint64_t app_error_code) {
- Debug(this,
- "Reset stream %" PRId64 " with code %" PRIu64
- " and final size %" PRIu64,
- stream_id,
- app_error_code,
- final_size);
-
- BaseObjectPtr<QuicStream> stream = FindStream(stream_id);
-
- if (stream) {
- stream->set_final_size(final_size);
- application_->StreamReset(stream_id, app_error_code);
- }
-}
-
-void QuicSession::UpdateConnectionID(
- int type,
- const QuicCID& cid,
- const StatelessResetToken& token) {
- switch (type) {
- case NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE:
- socket_->AssociateStatelessResetToken(
- token,
- BaseObjectPtr<QuicSession>(this));
- break;
- case NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE:
- socket_->DisassociateStatelessResetToken(token);
- break;
- }
-}
-
-// Updates the idle timer timeout. If the idle timer fires, the connection
-// will be silently closed. It is important to update this as activity
-// occurs to keep the idle timer from firing.
-void QuicSession::UpdateIdleTimer() {
- if (is_closing_timer_enabled())
- return;
- uint64_t now = uv_hrtime();
- uint64_t expiry = ngtcp2_conn_get_idle_expiry(connection());
- // nano to millis
- uint64_t timeout = expiry > now ? (expiry - now) / 1000000ULL : 1;
- if (timeout == 0) timeout = 1;
- Debug(this, "Updating idle timeout to %" PRIu64, timeout);
- idle_.Update(timeout, timeout);
-}
-
-void QuicSession::UpdateClosingTimer() {
- set_closing_timer_enabled(true);
- uint64_t timeout =
- is_server() ? (ngtcp2_conn_get_pto(connection()) / 1000000ULL) * 3 : 0;
- Debug(this, "Setting closing timeout to %" PRIu64, timeout);
- retransmit_.Stop();
- idle_.Update(timeout, 0);
- idle_.Ref();
-}
-
-// Write any packets current pending for the ngtcp2 connection based on
-// the current state of the QuicSession. If the QuicSession is in the
-// closing period, only CONNECTION_CLOSE packets may be written. If the
-// QuicSession is in the draining period, no packets may be written.
-//
-// Packets are flushed to the underlying QuicSocket uv_udp_t as soon
-// as they are written. The WritePackets method may cause zero or more
-// packets to be serialized.
-//
-// If there are any acks or retransmissions pending, those will be
-// serialized at this point as well. However, WritePackets does not
-// serialize stream data that is being sent initially.
-bool QuicSession::WritePackets(const char* diagnostic_label) {
- CHECK(!NgCallbackScope::InNgCallbackScope(this));
-
- // During either the draining or closing period,
- // we are not permitted to send any additional packets.
- if (is_in_draining_period() || is_in_closing_period())
- return true;
-
- // Otherwise, serialize and send pending frames
- QuicPathStorage path;
- for (;;) {
- auto packet = QuicPacket::Create(diagnostic_label, max_pktlen_);
- // ngtcp2_conn_write_pkt will fill the created QuicPacket up
- // as much as possible, and then should be called repeatedly
- // until it returns 0 or fatally errors. On each call, it
- // will return the number of bytes encoded into the packet.
- ssize_t nwrite =
- ngtcp2_conn_write_pkt(
- connection(),
- &path.path,
- packet->data(),
- max_pktlen_,
- uv_hrtime());
-
- if (nwrite <= 0) {
- switch (nwrite) {
- case 0:
- // There was nothing to write.
- return true;
- case NGTCP2_ERR_PKT_NUM_EXHAUSTED:
- // There is a finite number of packets that can be sent
- // per connection. Once those are exhausted, there's
- // absolutely nothing we can do except immediately
- // and silently tear down the QuicSession. This has
- // to be silent because we can't even send a
- // CONNECTION_CLOSE since even those require a
- // packet number.
- Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
- return false;
- default:
- set_last_error(QUIC_ERROR_SESSION, static_cast<int>(nwrite));
- return false;
- }
- }
-
- packet->set_length(nwrite);
- UpdateEndpoint(path.path);
- UpdateDataStats();
-
- if (!SendPacket(std::move(packet)))
- return false;
- }
-}
-
-void QuicSession::MemoryInfo(MemoryTracker* tracker) const {
- tracker->TrackField("crypto_context", crypto_context_.get());
- tracker->TrackField("alpn", alpn_);
- tracker->TrackField("hostname", hostname_);
- tracker->TrackField("idle", idle_);
- tracker->TrackField("retransmit", retransmit_);
- tracker->TrackField("streams", streams_);
- tracker->TrackFieldWithSize("current_ngtcp2_memory", current_ngtcp2_memory_);
- tracker->TrackField("conn_closebuf", conn_closebuf_);
- tracker->TrackField("application", application_);
- tracker->TrackField("quic_state", quic_state_);
- tracker->TrackField("qlog_stream", qlog_stream_);
- StatsBase::StatsMemoryInfo(tracker);
-}
-
-void QuicSession::ExtendMaxStreamsBidi(uint64_t max_streams) {
- state_->max_streams_bidi = max_streams;
-}
-
-void QuicSession::ExtendMaxStreamsUni(uint64_t max_streams) {
- state_->max_streams_uni = max_streams;
-}
-
-void QuicSession::ExtendMaxStreamsRemoteUni(uint64_t max_streams) {
- Debug(this, "Extend remote max unidirectional streams: %" PRIu64,
- max_streams);
- application_->ExtendMaxStreamsRemoteUni(max_streams);
-}
-
-void QuicSession::ExtendMaxStreamsRemoteBidi(uint64_t max_streams) {
- Debug(this, "Extend remote max bidirectional streams: %" PRIu64,
- max_streams);
- application_->ExtendMaxStreamsRemoteBidi(max_streams);
-}
-
-void QuicSession::ExtendMaxStreamData(int64_t stream_id, uint64_t max_data) {
- Debug(this,
- "Extending max stream %" PRId64 " data to %" PRIu64,
- stream_id, max_data);
- application_->ExtendMaxStreamData(stream_id, max_data);
-}
-
-// Static function to create a new server QuicSession instance
-BaseObjectPtr<QuicSession> QuicSession::CreateServer(
- QuicSocket* socket,
- const QuicSessionConfig& config,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- const QuicCID& dcid,
- const QuicCID& scid,
- const QuicCID& ocid,
- uint32_t version,
- const std::string& alpn,
- uint32_t options,
- QlogMode qlog) {
- Local<Object> obj;
- if (!socket->env()
- ->quicserversession_instance_template()
- ->NewInstance(socket->env()->context()).ToLocal(&obj)) {
- return {};
- }
- BaseObjectPtr<QuicSession> session =
- MakeDetachedBaseObject<QuicSession>(
- socket,
- config,
- obj,
- local_addr,
- remote_addr,
- dcid,
- scid,
- ocid,
- version,
- alpn,
- options,
- qlog);
-
- session->AddToSocket(socket);
- return session;
-}
-
-// Initializes a newly created server QuicSession.
-void QuicSession::InitServer(
- QuicSessionConfig config,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- const QuicCID& dcid,
- const QuicCID& scid,
- const QuicCID& ocid,
- uint32_t version,
- QlogMode qlog) {
-
- CHECK_NULL(connection_);
-
- ExtendMaxStreamsBidi(DEFAULT_MAX_STREAMS_BIDI);
- ExtendMaxStreamsUni(DEFAULT_MAX_STREAMS_UNI);
-
- local_address_ = local_addr;
- remote_address_ = remote_addr;
- max_pktlen_ = GetMaxPktLen(remote_addr);
-
- config.set_original_connection_id(ocid, scid);
-
- connection_id_strategy_(this, scid_.cid(), kScidLen);
-
- config.GenerateStatelessResetToken(this, scid_);
- config.GeneratePreferredAddressToken(connection_id_strategy_, this, &pscid_);
-
- QuicPath path(local_addr, remote_address_);
-
- // NOLINTNEXTLINE(readability/pointer_notation)
- if (qlog == QlogMode::kEnabled) config.set_qlog({ *ocid, OnQlogWrite });
-
- ngtcp2_conn* conn;
- CHECK_EQ(
- ngtcp2_conn_server_new(
- &conn,
- dcid.cid(),
- scid_.cid(),
- &path,
- version,
- &callbacks[crypto_context_->side()],
- &config,
- &alloc_info_,
- static_cast<QuicSession*>(this)), 0);
-
- connection_.reset(conn);
-
- crypto_context_->Initialize();
- UpdateDataStats();
- UpdateIdleTimer();
-}
-
-namespace {
-void QuicSessionOnClientHelloDone(const FunctionCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
- QuicSession* session;
- ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
- Local<FunctionTemplate> cons = env->secure_context_constructor_template();
- SecureContext* context = nullptr;
- if (args[0]->IsObject() && cons->HasInstance(args[0]))
- context = Unwrap<SecureContext>(args[0].As<Object>());
- session->crypto_context()->OnClientHelloDone(
- BaseObjectPtr<SecureContext>(context));
-}
-
-void QuicSessionOnCertDone(const FunctionCallbackInfo<Value>& args) {
- QuicSession* session;
- ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
- session->crypto_context()->OnOCSPDone(args[0]);
-}
-} // namespace
-
-
-// Data stats are used to allow user code to keep track of important
-// statistics such as amount of data in flight through the lifetime
-// of a connection.
-void QuicSession::UpdateDataStats() {
- if (is_destroyed())
- return;
- state_->max_data_left = ngtcp2_conn_get_max_data_left(connection());
-
- ngtcp2_conn_stat stat;
- ngtcp2_conn_get_conn_stat(connection(), &stat);
-
- SetStat(&QuicSessionStats::latest_rtt, stat.latest_rtt);
- SetStat(&QuicSessionStats::min_rtt, stat.min_rtt);
- SetStat(&QuicSessionStats::smoothed_rtt, stat.smoothed_rtt);
- SetStat(&QuicSessionStats::receive_rate, stat.recv_rate_sec);
- SetStat(&QuicSessionStats::send_rate, stat.delivery_rate_sec);
- SetStat(&QuicSessionStats::cwnd, stat.cwnd);
-
- state_->bytes_in_flight = stat.bytes_in_flight;
- // The max_bytes_in_flight is a highwater mark that can be used
- // in performance analysis operations.
- if (stat.bytes_in_flight > GetStat(&QuicSessionStats::max_bytes_in_flight))
- SetStat(&QuicSessionStats::max_bytes_in_flight, stat.bytes_in_flight);
-}
-
-// Static method for creating a new client QuicSession instance.
-BaseObjectPtr<QuicSession> QuicSession::CreateClient(
- QuicSocket* socket,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- BaseObjectPtr<SecureContext> secure_context,
- ngtcp2_transport_params* early_transport_params,
- crypto::SSLSessionPointer early_session_ticket,
- Local<Value> dcid,
- PreferredAddressStrategy preferred_address_strategy,
- const std::string& alpn,
- const std::string& hostname,
- uint32_t options,
- QlogMode qlog) {
- Local<Object> obj;
- if (!socket->env()
- ->quicclientsession_instance_template()
- ->NewInstance(socket->env()->context()).ToLocal(&obj)) {
- return {};
- }
-
- BaseObjectPtr<QuicSession> session =
- MakeDetachedBaseObject<QuicSession>(
- socket,
- obj,
- local_addr,
- remote_addr,
- secure_context,
- early_transport_params,
- std::move(early_session_ticket),
- dcid,
- preferred_address_strategy,
- alpn,
- hostname,
- options,
- qlog);
-
- session->AddToSocket(socket);
-
- return session;
-}
-
-// Initialize a newly created client QuicSession.
-// The early_transport_params and session_ticket are optional to
-// perform a 0RTT resumption of a prior session.
-// The dcid_value parameter is optional to allow user code the
-// ability to provide an explicit dcid (this should be rare)
-void QuicSession::InitClient(
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- ngtcp2_transport_params* early_transport_params,
- crypto::SSLSessionPointer early_session_ticket,
- Local<Value> dcid_value,
- QlogMode qlog) {
- CHECK_NULL(connection_);
-
- local_address_ = local_addr;
- remote_address_ = remote_addr;
- Debug(this, "Initializing connection from %s to %s",
- local_address_,
- remote_address_);
-
- // The maximum packet length is determined largely
- // by the IP version (IPv4 vs IPv6). Packet sizes
- // should be limited to the maximum MTU necessary to
- // prevent IP fragmentation.
- max_pktlen_ = GetMaxPktLen(remote_address_);
-
- QuicSessionConfig config(quic_state());
- ExtendMaxStreamsBidi(DEFAULT_MAX_STREAMS_BIDI);
- ExtendMaxStreamsUni(DEFAULT_MAX_STREAMS_UNI);
-
- connection_id_strategy_(this, scid_.cid(), NGTCP2_MAX_CIDLEN);
-
- ngtcp2_cid dcid;
- if (dcid_value->IsArrayBufferView()) {
- ArrayBufferViewContents<uint8_t> sbuf(
- dcid_value.As<ArrayBufferView>());
- CHECK_LE(sbuf.length(), NGTCP2_MAX_CIDLEN);
- CHECK_GE(sbuf.length(), NGTCP2_MIN_CIDLEN);
- memcpy(dcid.data, sbuf.data(), sbuf.length());
- dcid.datalen = sbuf.length();
- } else {
- connection_id_strategy_(this, &dcid, NGTCP2_MAX_CIDLEN);
- }
-
- QuicPath path(local_address_, remote_address_);
-
- if (qlog == QlogMode::kEnabled) config.set_qlog({ dcid, OnQlogWrite });
-
- ngtcp2_conn* conn;
- CHECK_EQ(
- ngtcp2_conn_client_new(
- &conn,
- &dcid,
- scid_.cid(),
- &path,
- NGTCP2_PROTO_VER,
- &callbacks[crypto_context_->side()],
- &config,
- &alloc_info_,
- static_cast<QuicSession*>(this)), 0);
-
-
- connection_.reset(conn);
-
- crypto_context_->Initialize();
-
- if (early_transport_params != nullptr)
- ngtcp2_conn_set_early_remote_transport_params(conn, early_transport_params);
- crypto_context_->set_session(std::move(early_session_ticket));
-
- UpdateIdleTimer();
- UpdateDataStats();
-}
-
-// Static ngtcp2 callbacks are registered when ngtcp2 when a new ngtcp2_conn is
-// created. These are static functions that, for the most part, simply defer to
-// a QuicSession instance that is passed through as user_data.
-
-// Called by ngtcp2 for both client and server connections when
-// TLS handshake data has been received and needs to be processed.
-// This will be called multiple times during the TLS handshake
-// process and may be called during key updates.
-int QuicSession::OnReceiveCryptoData(
- ngtcp2_conn* conn,
- ngtcp2_crypto_level crypto_level,
- uint64_t offset,
- const uint8_t* data,
- size_t datalen,
- void* user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
- int ret = session->crypto_context()->Receive(
- crypto_level,
- offset,
- data,
- datalen);
- return ret == 0 ? 0 : NGTCP2_ERR_CALLBACK_FAILURE;
-}
-
-// Called by ngtcp2 for both client and server connections
-// when a request to extend the maximum number of bidirectional
-// streams has been received.
-int QuicSession::OnExtendMaxStreamsBidi(
- ngtcp2_conn* conn,
- uint64_t max_streams,
- void* user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
- session->ExtendMaxStreamsBidi(max_streams);
- return 0;
-}
-
-// Called by ngtcp2 for both client and server connections
-// when a request to extend the maximum number of unidirectional
-// streams has been received
-int QuicSession::OnExtendMaxStreamsUni(
- ngtcp2_conn* conn,
- uint64_t max_streams,
- void* user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
- session->ExtendMaxStreamsUni(max_streams);
- return 0;
-}
-
-// Triggered by ngtcp2 when the local peer has received an
-// indication from the remote peer indicating that additional
-// unidirectional streams may be sent. The max_streams parameter
-// identifies the highest unidirectional stream ID that may be
-// opened.
-int QuicSession::OnExtendMaxStreamsRemoteUni(
- ngtcp2_conn* conn,
- uint64_t max_streams,
- void* user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
- session->ExtendMaxStreamsRemoteUni(max_streams);
- return 0;
-}
-
-// Triggered by ngtcp2 when the local peer has received an
-// indication from the remote peer indicating that additional
-// bidirectional streams may be sent. The max_streams parameter
-// identifies the highest bidirectional stream ID that may be
-// opened.
-int QuicSession::OnExtendMaxStreamsRemoteBidi(
- ngtcp2_conn* conn,
- uint64_t max_streams,
- void* user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
- session->ExtendMaxStreamsRemoteUni(max_streams);
- return 0;
-}
-
-// Triggered by ngtcp2 when the local peer has received a flow
-// control signal from the remote peer indicating that additional
-// data can be sent. The max_data parameter identifies the maximum
-// data offset that may be sent. That is, a value of 99 means that
-// out of a stream of 1000 bytes, only the first 100 may be sent.
-// (offsets 0 through 99).
-int QuicSession::OnExtendMaxStreamData(
- ngtcp2_conn* conn,
- int64_t stream_id,
- uint64_t max_data,
- void* user_data,
- void* stream_user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
- session->ExtendMaxStreamData(stream_id, max_data);
- return 0;
-}
-
-int QuicSession::OnConnectionIDStatus(
- ngtcp2_conn* conn,
- int type,
- uint64_t seq,
- const ngtcp2_cid* cid,
- const uint8_t* token,
- void* user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- if (token != nullptr) {
- QuicCID qcid(cid);
- Debug(session, "Updating connection ID %s with reset token", qcid);
- session->UpdateConnectionID(type, qcid, StatelessResetToken(token));
- }
- return 0;
-}
-
-// Called by ngtcp2 for both client and server connections
-// when ngtcp2 has determined that the TLS handshake has
-// been completed. It is important to understand that this
-// is only an indication of the local peer's handshake state.
-// The remote peer might not yet have completed its part
-// of the handshake.
-int QuicSession::OnHandshakeCompleted(
- ngtcp2_conn* conn,
- void* user_data) {
-
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
- session->HandshakeCompleted();
- return 0;
-}
-
-// Called by ngtcp2 for clients when the handshake has been
-// confirmed. Confirmation occurs *after* handshake completion.
-int QuicSession::OnHandshakeConfirmed(
- ngtcp2_conn* conn,
- void* user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
- session->HandshakeConfirmed();
- return 0;
-}
-
-// Called by ngtcp2 when a chunk of stream data has been received.
-// Currently, ngtcp2 ensures that this callback is always called
-// with an offset parameter strictly larger than the previous call's
-// offset + datalen (that is, data will never be delivered out of
-// order). That behavior may change in the future but only via a
-// configuration option.
-int QuicSession::OnReceiveStreamData(
- ngtcp2_conn* conn,
- uint32_t flags,
- int64_t stream_id,
- uint64_t offset,
- const uint8_t* data,
- size_t datalen,
- void* user_data,
- void* stream_user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
- return session->ReceiveStreamData(
- flags,
- stream_id,
- data,
- datalen,
- offset) ? 0 : NGTCP2_ERR_CALLBACK_FAILURE;
-}
-
-// Called by ngtcp2 when a new stream has been opened
-int QuicSession::OnStreamOpen(
- ngtcp2_conn* conn,
- int64_t stream_id,
- void* user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- // We currently do not do anything with this callback.
- // QuicStream instances are created implicitly once the
- // first chunk of stream data is received.
-
- return 0;
-}
-
-// Called by ngtcp2 when an acknowledgement for a chunk of
-// TLS handshake data has been received by the remote peer.
-// This is only an indication that data was received, not that
-// it was successfully processed. Acknowledgements are a key
-// part of the QUIC reliability mechanism.
-int QuicSession::OnAckedCryptoOffset(
- ngtcp2_conn* conn,
- ngtcp2_crypto_level crypto_level,
- uint64_t offset,
- uint64_t datalen,
- void* user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
- session->crypto_context()->AcknowledgeCryptoData(crypto_level, datalen);
- return 0;
-}
-
-// Called by ngtcp2 when an acknowledgement for a chunk of
-// stream data has been received successfully by the remote peer.
-// This is only an indication that data was received, not that
-// it was successfully processed. Acknowledgements are a key
-// part of the QUIC reliability mechanism.
-int QuicSession::OnAckedStreamDataOffset(
- ngtcp2_conn* conn,
- int64_t stream_id,
- uint64_t offset,
- uint64_t datalen,
- void* user_data,
- void* stream_user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
- session->AckedStreamDataOffset(stream_id, offset, datalen);
- return 0;
-}
-
-// Called by ngtcp2 for a client connection when the server
-// has indicated a preferred address in the transport
-// params.
-// For now, there are two modes: we can accept the preferred address
-// or we can reject it. Later, we may want to implement a callback
-// to ask the user if they want to accept the preferred address or
-// not.
-int QuicSession::OnSelectPreferredAddress(
- ngtcp2_conn* conn,
- ngtcp2_addr* dest,
- const ngtcp2_preferred_addr* paddr,
- void* user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
-
- // The paddr parameter contains the server advertised preferred
- // address. The dest parameter contains the address that is
- // actually being used. If the preferred address is selected,
- // then the contents of paddr are copied over to dest. It is
- // important to remember that SelectPreferredAddress should
- // return true regardless of whether the preferred address was
- // selected or not. It should only return false if there was
- // an actual failure processing things. Note, however, that
- // even in such a failure, we debug log and ignore it.
- // If the preferred address is not selected, dest remains
- // unchanged.
- PreferredAddress preferred_address(session->env(), dest, paddr);
- session->SelectPreferredAddress(preferred_address);
- return 0;
-}
-
-// Called by ngtcp2 when a stream has been closed.
-int QuicSession::OnStreamClose(
- ngtcp2_conn* conn,
- int64_t stream_id,
- uint64_t app_error_code,
- void* user_data,
- void* stream_user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
- session->StreamClose(stream_id, app_error_code);
- return 0;
-}
-
-// Stream reset means the remote peer will no longer send data
-// on the identified stream. It is essentially a premature close.
-// The final_size parameter is important here in that it identifies
-// exactly how much data the *remote peer* is aware that it sent.
-// If there are lost packets, then the local peer's idea of the final
-// size might not match.
-int QuicSession::OnStreamReset(
- ngtcp2_conn* conn,
- int64_t stream_id,
- uint64_t final_size,
- uint64_t app_error_code,
- void* user_data,
- void* stream_user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
- session->StreamReset(stream_id, final_size, app_error_code);
- return 0;
-}
-
-// Called by ngtcp2 when it needs to generate some random data.
-// We currently do not use it, but the ngtcp2_rand_ctx identifies
-// why the random data is necessary. When ctx is equal to
-// NGTCP2_RAND_CTX_NONE, it typically means the random data
-// is being used during the TLS handshake. When ctx is equal to
-// NGTCP2_RAND_CTX_PATH_CHALLENGE, the random data is being
-// used to construct a PATH_CHALLENGE. These *might* need more
-// secure and robust random number generation given the
-// sensitivity of PATH_CHALLENGE operations (an attacker
-// could use a compromised PATH_CHALLENGE to trick an endpoint
-// into redirecting traffic).
-//
-// The ngtcp2_rand_ctx tells us what the random data is used for.
-// Currently, there is only one use. In the future, we'll want to
-// explore whether we want to handle the different cases uses.
-int QuicSession::OnRand(
- uint8_t* dest,
- size_t destlen,
- ngtcp2_rand_ctx ctx) {
- EntropySource(dest, destlen);
- return 0;
-}
-
-// When a new client connection is established, ngtcp2 will call
-// this multiple times to generate a pool of connection IDs to use.
-int QuicSession::OnGetNewConnectionID(
- ngtcp2_conn* conn,
- ngtcp2_cid* cid,
- uint8_t* token,
- size_t cidlen,
- void* user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- CHECK(!NgCallbackScope::InNgCallbackScope(session));
- session->GetNewConnectionID(cid, token, cidlen);
- return 0;
-}
-
-// When a connection is closed, ngtcp2 will call this multiple
-// times to retire connection IDs. It's also possible for this
-// to be called at times throughout the lifecycle of the connection
-// to remove a CID from the availability pool.
-int QuicSession::OnRemoveConnectionID(
- ngtcp2_conn* conn,
- const ngtcp2_cid* cid,
- void* user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- if (session->is_server())
- session->socket()->DisassociateCID(QuicCID(cid));
- return 0;
-}
-
-// Called by ngtcp2 to perform path validation. Path validation
-// is necessary to ensure that a packet is originating from the
-// expected source. If the res parameter indicates success, it
-// means that the path specified has been verified as being
-// valid.
-//
-// Validity here means only that there has been a successful
-// exchange of PATH_CHALLENGE information between the peers.
-// It's critical to understand that the validity of a path
-// can change at any timee so this is only an indication of
-// validity at a specific point in time.
-int QuicSession::OnPathValidation(
- ngtcp2_conn* conn,
- const ngtcp2_path* path,
- ngtcp2_path_validation_result res,
- void* user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
- session->PathValidation(path, res);
- return 0;
-}
-
-// Triggered by ngtcp2 when a version negotiation is received.
-// What this means is that the remote peer does not support the
-// QUIC version requested. The only thing we can do here (per
-// the QUIC specification) is silently discard the connection
-// and notify the JavaScript side that a different version of
-// QUIC should be used. The sv parameter does list the QUIC
-// versions advertised as supported by the remote peer.
-int QuicSession::OnVersionNegotiation(
- ngtcp2_conn* conn,
- const ngtcp2_pkt_hd* hd,
- const uint32_t* sv,
- size_t nsv,
- void* user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- QuicSession::NgCallbackScope callback_scope(session);
- session->VersionNegotiation(sv, nsv);
- return 0;
-}
-
-// Triggered by ngtcp2 when a stateless reset is received. What this
-// means is that the remote peer might recognize the CID but has lost
-// all state necessary to successfully process it. The only thing we
-// can do is silently close the connection. For server sessions, this
-// means all session state is shut down and discarded, even on the
-// JavaScript side. For client sessions, we discard session state at
-// the C++ layer but -- at least in the future -- we can retain some
-// state at the JavaScript level to allow for automatic session
-// resumption.
-int QuicSession::OnStatelessReset(
- ngtcp2_conn* conn,
- const ngtcp2_pkt_stateless_reset* sr,
- void* user_data) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
-
- if (UNLIKELY(session->is_destroyed()))
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- session->set_stateless_reset();
- return 0;
-}
-
-const ngtcp2_conn_callbacks QuicSession::callbacks[2] = {
- // NGTCP2_CRYPTO_SIDE_CLIENT
- {
- ngtcp2_crypto_client_initial_cb,
- nullptr,
- OnReceiveCryptoData,
- OnHandshakeCompleted,
- OnVersionNegotiation,
- ngtcp2_crypto_encrypt_cb,
- ngtcp2_crypto_decrypt_cb,
- ngtcp2_crypto_hp_mask_cb,
- OnReceiveStreamData,
- OnAckedCryptoOffset,
- OnAckedStreamDataOffset,
- OnStreamOpen,
- OnStreamClose,
- OnStatelessReset,
- ngtcp2_crypto_recv_retry_cb,
- OnExtendMaxStreamsBidi,
- OnExtendMaxStreamsUni,
- OnRand,
- OnGetNewConnectionID,
- OnRemoveConnectionID,
- ngtcp2_crypto_update_key_cb,
- OnPathValidation,
- OnSelectPreferredAddress,
- OnStreamReset,
- OnExtendMaxStreamsRemoteBidi,
- OnExtendMaxStreamsRemoteUni,
- OnExtendMaxStreamData,
- OnConnectionIDStatus,
- OnHandshakeConfirmed,
- nullptr, // recv_new_token
- ngtcp2_crypto_delete_crypto_aead_ctx_cb,
- ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
- },
- // NGTCP2_CRYPTO_SIDE_SERVER
- {
- nullptr,
- ngtcp2_crypto_recv_client_initial_cb,
- OnReceiveCryptoData,
- OnHandshakeCompleted,
- nullptr, // recv_version_negotiation
- ngtcp2_crypto_encrypt_cb,
- ngtcp2_crypto_decrypt_cb,
- ngtcp2_crypto_hp_mask_cb,
- OnReceiveStreamData,
- OnAckedCryptoOffset,
- OnAckedStreamDataOffset,
- OnStreamOpen,
- OnStreamClose,
- OnStatelessReset,
- nullptr, // recv_retry
- nullptr, // extend_max_streams_bidi
- nullptr, // extend_max_streams_uni
- OnRand,
- OnGetNewConnectionID,
- OnRemoveConnectionID,
- ngtcp2_crypto_update_key_cb,
- OnPathValidation,
- nullptr, // select_preferred_addr
- OnStreamReset,
- OnExtendMaxStreamsRemoteBidi,
- OnExtendMaxStreamsRemoteUni,
- OnExtendMaxStreamData,
- OnConnectionIDStatus,
- nullptr, // handshake_confirmed
- nullptr, // recv_new_token
- ngtcp2_crypto_delete_crypto_aead_ctx_cb,
- ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
- }
-};
-
-BaseObjectPtr<QLogStream> QuicSession::qlog_stream() {
- if (!qlog_stream_) {
- CHECK(qlog_stream_ = QLogStream::Create(env()));
- listener_->OnQLog(qlog_stream_.get());
- }
- return qlog_stream_;
-}
-
-void QuicSession::OnQlogWrite(
- void* user_data,
- uint32_t flags,
- const void* data,
- size_t len) {
- QuicSession* session = static_cast<QuicSession*>(user_data);
- Environment* env = session->env();
-
- // Fun fact... ngtcp2 does not emit the final qlog statement until the
- // ngtcp2_conn object is destroyed. Ideally, destroying is explicit,
- // but sometimes the QuicSession object can be garbage collected without
- // being explicitly destroyed. During those times, we cannot call out
- // to JavaScript. Because we don't know for sure if we're in in a GC
- // when this is called, it is safer to just defer writes to immediate.
- BaseObjectPtr<QLogStream> ptr = session->qlog_stream();
- std::vector<uint8_t> buffer(len);
- memcpy(buffer.data(), data, len);
- env->SetImmediate([ptr = std::move(ptr),
- buffer = std::move(buffer),
- flags](Environment*) {
- ptr->Emit(buffer.data(), buffer.size(), flags);
- });
-}
-
-BaseObjectPtr<QLogStream> QLogStream::Create(Environment* env) {
- HandleScope scope(env->isolate());
-
- // TODO(@jasnell): There is identical code in heap_utils for the
- // HeapSnapshotStream. We can consolidate the two.
- if (env->qlogoutputstream_constructor_template().IsEmpty()) {
- // Create FunctionTemplate for QLogStream
- Local<FunctionTemplate> os = FunctionTemplate::New(env->isolate());
- os->Inherit(AsyncWrap::GetConstructorTemplate(env));
- Local<ObjectTemplate> ost = os->InstanceTemplate();
- ost->SetInternalFieldCount(StreamBase::kInternalFieldCount);
- os->SetClassName(
- FIXED_ONE_BYTE_STRING(env->isolate(), "QLogStream"));
- StreamBase::AddMethods(env, os);
- env->set_qlogoutputstream_constructor_template(ost);
- }
-
- Local<Object> obj;
- if (!env->qlogoutputstream_constructor_template()
- ->NewInstance(env->context())
- .ToLocal(&obj)) {
- return {};
- }
-
- return MakeBaseObject<QLogStream>(env, obj);
-}
-
-QLogStream::QLogStream(Environment* env, v8::Local<Object> obj)
- : AsyncWrap(env, obj, AsyncWrap::PROVIDER_QLOGSTREAM),
- StreamBase(env) {
- MakeWeak();
- StreamBase::AttachToObject(GetObject());
-}
-
-void QLogStream::Emit(const uint8_t* data, size_t len, uint32_t flags) {
- size_t remaining = len;
- while (remaining != 0) {
- uv_buf_t buf = EmitAlloc(len);
- ssize_t avail = std::min<size_t>(remaining, buf.len);
- memcpy(buf.base, data, avail);
- remaining -= avail;
- data += avail;
- EmitRead(avail, buf);
- }
-
- if (ended_ && flags & NGTCP2_QLOG_WRITE_FLAG_FIN)
- EmitRead(UV_EOF);
-}
-
-int QLogStream::DoShutdown(ShutdownWrap* req_wrap) {
- UNREACHABLE();
-}
-
-int QLogStream::DoWrite(
- WriteWrap* w,
- uv_buf_t* bufs,
- size_t count,
- uv_stream_t* send_handle) {
- UNREACHABLE();
-}
-
-bool QLogStream::IsAlive() { return true; }
-bool QLogStream::IsClosing() { return false; }
-
-// JavaScript API
-
-namespace {
-void QuicSessionSetSocket(const FunctionCallbackInfo<Value>& args) {
- QuicSession* session;
- QuicSocket* socket;
- ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
- CHECK(args[0]->IsObject());
- ASSIGN_OR_RETURN_UNWRAP(&socket, args[0].As<Object>());
- args.GetReturnValue().Set(session->set_socket(socket, args[1]->IsTrue()));
-}
-
-// GracefulClose flips a flag that prevents new local streams
-// from being opened and new remote streams from being received. It is
-// important to note that this does *NOT* send a CONNECTION_CLOSE packet
-// to the peer. Existing streams are permitted to close gracefully.
-void QuicSessionGracefulClose(const FunctionCallbackInfo<Value>& args) {
- QuicSession* session;
- ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
- session->StartGracefulClose();
-}
-
-// Destroying the QuicSession will trigger sending of a CONNECTION_CLOSE
-// packet, after which the QuicSession will be immediately torn down.
-void QuicSessionDestroy(const FunctionCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
- QuicSession* session;
- ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
- session->set_last_error(QuicError(env, args[0], args[1]));
- session->Destroy();
-}
-
-void QuicSessionGetEphemeralKeyInfo(const FunctionCallbackInfo<Value>& args) {
- QuicSession* session;
- ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
- Local<Object> ret;
- if (session->crypto_context()->ephemeral_key().ToLocal(&ret))
- args.GetReturnValue().Set(ret);
-}
-
-void QuicSessionGetPeerCertificate(const FunctionCallbackInfo<Value>& args) {
- QuicSession* session;
- ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
- Local<Value> ret;
- if (session->crypto_context()->peer_cert(!args[0]->IsTrue()).ToLocal(&ret))
- args.GetReturnValue().Set(ret);
-}
-
-void QuicSessionGetRemoteAddress(
- const FunctionCallbackInfo<Value>& args) {
- QuicSession* session;
- ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
- Environment* env = session->env();
- CHECK(args[0]->IsObject());
- args.GetReturnValue().Set(
- session->remote_address().ToJS(env, args[0].As<Object>()));
-}
-
-void QuicSessionGetCertificate(
- const FunctionCallbackInfo<Value>& args) {
- QuicSession* session;
- ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
- Local<Value> ret;
- if (session->crypto_context()->cert().ToLocal(&ret))
- args.GetReturnValue().Set(ret);
-}
-
-void QuicSessionPing(const FunctionCallbackInfo<Value>& args) {
- QuicSession* session;
- ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
- session->Ping();
-}
-
-// Triggers a silent close of a QuicSession. This is currently only used
-// (and should ever only be used) for testing purposes...
-void QuicSessionSilentClose(const FunctionCallbackInfo<Value>& args) {
- QuicSession* session;
- ASSIGN_OR_RETURN_UNWRAP(&session, args[0].As<Object>());
- ProcessEmitWarning(
- session->env(),
- "Forcing silent close of QuicSession for testing purposes only");
- session->Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
-}
-
-// This is used purely for testing.
-void QuicSessionRemoveFromSocket(const FunctionCallbackInfo<Value>& args) {
- QuicSession* session;
- ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
- session->RemoveFromSocket();
-}
-
-void QuicSessionUpdateKey(const FunctionCallbackInfo<Value>& args) {
- QuicSession* session;
- ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
- // Initiating a key update may fail if it is done too early (either
- // before the TLS handshake has been confirmed or while a previous
- // key update is being processed). When it fails, InitiateKeyUpdate()
- // will return false.
- args.GetReturnValue().Set(session->crypto_context()->InitiateKeyUpdate());
-}
-
-// When a client wishes to resume a prior TLS session, it must specify both
-// the remember transport parameters and remembered TLS session ticket. Those
-// will each be provided as a TypedArray. The DecodeTransportParams and
-// DecodeSessionTicket functions handle those. If the argument is undefined,
-// then resumption is not used.
-
-bool DecodeTransportParams(
- Local<Value> value,
- ngtcp2_transport_params* params) {
- if (value->IsUndefined())
- return false;
- CHECK(value->IsArrayBufferView());
- ArrayBufferViewContents<uint8_t> sbuf(value.As<ArrayBufferView>());
- if (sbuf.length() != sizeof(ngtcp2_transport_params))
- return false;
- memcpy(params, sbuf.data(), sizeof(ngtcp2_transport_params));
- return true;
-}
-
-crypto::SSLSessionPointer DecodeSessionTicket(Local<Value> value) {
- if (value->IsUndefined())
- return {};
- CHECK(value->IsArrayBufferView());
- ArrayBufferViewContents<unsigned char> sbuf(value.As<ArrayBufferView>());
- return crypto::GetTLSSession(sbuf.data(), sbuf.length());
-}
-
-void NewQuicClientSession(const FunctionCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
-
- QuicSocket* socket;
- int32_t family;
- uint32_t port;
- SecureContext* sc;
- SocketAddress remote_addr;
- int32_t preferred_address_policy;
- PreferredAddressStrategy preferred_address_strategy;
- uint32_t options = QUICCLIENTSESSION_OPTION_VERIFY_HOSTNAME_IDENTITY;
- std::string alpn(NGHTTP3_ALPN_H3);
-
- enum ARG_IDX : int {
- SOCKET,
- TYPE,
- IP,
- PORT,
- SECURE_CONTEXT,
- SNI,
- REMOTE_TRANSPORT_PARAMS,
- SESSION_TICKET,
- DCID,
- PREFERRED_ADDRESS_POLICY,
- ALPN,
- OPTIONS,
- QLOG
- };
-
- CHECK(args[ARG_IDX::SOCKET]->IsObject());
- CHECK(args[ARG_IDX::SECURE_CONTEXT]->IsObject());
- CHECK(args[ARG_IDX::IP]->IsString());
- CHECK(args[ARG_IDX::ALPN]->IsString());
- CHECK(args[ARG_IDX::TYPE]->Int32Value(env->context()).To(&family));
- CHECK(args[ARG_IDX::PORT]->Uint32Value(env->context()).To(&port));
- CHECK(args[ARG_IDX::OPTIONS]->Uint32Value(env->context()).To(&options));
- if (!args[ARG_IDX::SNI]->IsUndefined())
- CHECK(args[ARG_IDX::SNI]->IsString());
-
- ASSIGN_OR_RETURN_UNWRAP(&socket, args[ARG_IDX::SOCKET].As<Object>());
- ASSIGN_OR_RETURN_UNWRAP(&sc, args[ARG_IDX::SECURE_CONTEXT].As<Object>());
-
- CHECK(args[ARG_IDX::PREFERRED_ADDRESS_POLICY]->Int32Value(
- env->context()).To(&preferred_address_policy));
- switch (preferred_address_policy) {
- case QUIC_PREFERRED_ADDRESS_USE:
- preferred_address_strategy = QuicSession::UsePreferredAddressStrategy;
- break;
- default:
- preferred_address_strategy = QuicSession::IgnorePreferredAddressStrategy;
- }
-
- node::Utf8Value address(env->isolate(), args[ARG_IDX::IP]);
- node::Utf8Value servername(env->isolate(), args[ARG_IDX::SNI]);
-
- if (!SocketAddress::New(family, *address, port, &remote_addr))
- return args.GetReturnValue().Set(ERR_FAILED_TO_CREATE_SESSION);
-
- // ALPN is a string prefixed by the length, followed by values
- Utf8Value val(env->isolate(), args[ARG_IDX::ALPN]);
- alpn = val.length();
- alpn += *val;
-
- crypto::SSLSessionPointer early_session_ticket =
- DecodeSessionTicket(args[ARG_IDX::SESSION_TICKET]);
- ngtcp2_transport_params early_transport_params;
- bool has_early_transport_params =
- DecodeTransportParams(
- args[ARG_IDX::REMOTE_TRANSPORT_PARAMS],
- &early_transport_params);
-
- socket->ReceiveStart();
-
- BaseObjectPtr<QuicSession> session =
- QuicSession::CreateClient(
- socket,
- socket->local_address(),
- remote_addr,
- BaseObjectPtr<SecureContext>(sc),
- has_early_transport_params ? &early_transport_params : nullptr,
- std::move(early_session_ticket),
- args[ARG_IDX::DCID],
- preferred_address_strategy,
- alpn,
- std::string(*servername),
- options,
- args[ARG_IDX::QLOG]->IsTrue() ?
- QlogMode::kEnabled :
- QlogMode::kDisabled);
- session->SendPendingData();
- if (session->is_destroyed())
- return args.GetReturnValue().Set(ERR_FAILED_TO_CREATE_SESSION);
-
- args.GetReturnValue().Set(session->object());
-}
-
-// Add methods that are shared by both client and server QuicSessions
-void AddMethods(Environment* env, Local<FunctionTemplate> session) {
- env->SetProtoMethod(session, "destroy", QuicSessionDestroy);
- env->SetProtoMethod(session, "getRemoteAddress", QuicSessionGetRemoteAddress);
- env->SetProtoMethod(session, "getCertificate", QuicSessionGetCertificate);
- env->SetProtoMethod(session, "getPeerCertificate",
- QuicSessionGetPeerCertificate);
- env->SetProtoMethod(session, "gracefulClose", QuicSessionGracefulClose);
- env->SetProtoMethod(session, "updateKey", QuicSessionUpdateKey);
- env->SetProtoMethod(session, "ping", QuicSessionPing);
- env->SetProtoMethod(session, "removeFromSocket", QuicSessionRemoveFromSocket);
- env->SetProtoMethod(session, "onClientHelloDone",
- QuicSessionOnClientHelloDone);
- env->SetProtoMethod(session, "onCertDone", QuicSessionOnCertDone);
-}
-} // namespace
-
-void QuicSession::Initialize(
- Environment* env,
- Local<Object> target,
- Local<Context> context) {
- {
- Local<String> class_name =
- FIXED_ONE_BYTE_STRING(env->isolate(), "QuicServerSession");
- Local<FunctionTemplate> session = FunctionTemplate::New(env->isolate());
- session->SetClassName(class_name);
- session->Inherit(AsyncWrap::GetConstructorTemplate(env));
- Local<ObjectTemplate> sessiont = session->InstanceTemplate();
- sessiont->SetInternalFieldCount(QuicSession::kInternalFieldCount);
- sessiont->Set(env->owner_symbol(), Null(env->isolate()));
- AddMethods(env, session);
- env->set_quicserversession_instance_template(sessiont);
- }
-
- {
- Local<String> class_name =
- FIXED_ONE_BYTE_STRING(env->isolate(), "QuicClientSession");
- Local<FunctionTemplate> session = FunctionTemplate::New(env->isolate());
- session->SetClassName(class_name);
- session->Inherit(AsyncWrap::GetConstructorTemplate(env));
- Local<ObjectTemplate> sessiont = session->InstanceTemplate();
- sessiont->SetInternalFieldCount(QuicSession::kInternalFieldCount);
- sessiont->Set(env->owner_symbol(), Null(env->isolate()));
- AddMethods(env, session);
- env->SetProtoMethod(session,
- "getEphemeralKeyInfo",
- QuicSessionGetEphemeralKeyInfo);
- env->SetProtoMethod(session,
- "setSocket",
- QuicSessionSetSocket);
- env->set_quicclientsession_instance_template(sessiont);
-
- env->SetMethod(target, "createClientSession", NewQuicClientSession);
- env->SetMethod(target, "silentCloseSession", QuicSessionSilentClose);
- }
-}
-
-} // namespace quic
-} // namespace node
diff --git a/src/quic/node_quic_session.h b/src/quic/node_quic_session.h
deleted file mode 100644
index 10e16cc2689..00000000000
--- a/src/quic/node_quic_session.h
+++ /dev/null
@@ -1,1531 +0,0 @@
-#ifndef SRC_QUIC_NODE_QUIC_SESSION_H_
-#define SRC_QUIC_NODE_QUIC_SESSION_H_
-
-#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#include "aliased_buffer.h"
-#include "aliased_struct.h"
-#include "async_wrap.h"
-#include "env.h"
-#include "handle_wrap.h"
-#include "node.h"
-#include "node_crypto.h"
-#include "node_http_common.h"
-#include "node_mem.h"
-#include "node_quic_state.h"
-#include "node_quic_buffer-inl.h"
-#include "node_quic_crypto.h"
-#include "node_quic_util.h"
-#include "node_sockaddr.h"
-#include "stream_base.h"
-#include "timer_wrap.h"
-#include "v8.h"
-#include "uv.h"
-
-#include <ngtcp2/ngtcp2.h>
-#include <ngtcp2/ngtcp2_crypto.h>
-#include <nghttp3/nghttp3.h>
-#include <openssl/ssl.h>
-
-#include <unordered_map>
-#include <string>
-#include <vector>
-
-namespace node {
-namespace quic {
-
-using ConnectionPointer = DeleteFnPtr<ngtcp2_conn, ngtcp2_conn_del>;
-
-class QuicApplication;
-class QuicPacket;
-class QuicSocket;
-class QuicStream;
-
-using QuicHeader = NgHeaderBase<QuicApplication>;
-
-using StreamsMap = std::unordered_map<int64_t, BaseObjectPtr<QuicStream>>;
-
-enum class QlogMode {
- kDisabled,
- kEnabled
-};
-
-typedef void(*ConnectionIDStrategy)(
- QuicSession* session,
- ngtcp2_cid* cid,
- size_t cidlen);
-
-typedef void(*PreferredAddressStrategy)(
- QuicSession* session,
- const PreferredAddress& preferred_address);
-
-// The QuicSessionConfig class holds the initial transport parameters and
-// configuration options set by the JavaScript side when either a
-// client or server QuicSession is created. Instances are
-// stack created and use a combination of an AliasedBuffer to pass
-// the numeric settings quickly (see node_quic_state.h) and passed
-// in non-numeric settings (e.g. preferred_addr).
-class QuicSessionConfig final : public ngtcp2_settings {
- public:
- QuicSessionConfig() = default;
-
- explicit QuicSessionConfig(QuicState* quic_state) {
- Set(quic_state);
- }
-
- QuicSessionConfig(const QuicSessionConfig& config) {
- initial_ts = uv_hrtime();
- initial_rtt = config.initial_rtt;
- transport_params = config.transport_params;
- max_udp_payload_size = config.max_udp_payload_size;
- cc_algo = config.cc_algo;
- cc = config.cc;
- qlog = config.qlog;
- log_printf = config.log_printf;
- token = config.token;
- }
-
- void ResetToDefaults(QuicState* quic_state);
-
- // QuicSessionConfig::Set() pulls values out of the AliasedBuffer
- // defined in node_quic_state.h and stores the values in settings_.
- // If preferred_addr is not nullptr, it is copied into the
- // settings_.preferred_addr field
- void Set(QuicState* quic_state,
- const struct sockaddr* preferred_addr = nullptr);
-
- inline void set_original_connection_id(
- const QuicCID& ocid,
- const QuicCID& scid);
-
- // Generates the stateless reset token for the settings_
- inline void GenerateStatelessResetToken(
- QuicSession* session,
- const QuicCID& cid);
-
- // If the preferred address is set, generates the associated tokens
- inline void GeneratePreferredAddressToken(
- ConnectionIDStrategy connection_id_strategy,
- QuicSession* session,
- QuicCID* pscid);
-
- inline void set_qlog(const ngtcp2_qlog_settings& qlog);
-};
-
-// Options to alter the behavior of various functions on the
-// server QuicSession. These are set on the QuicSocket when
-// the listen() function is called and are passed to the
-// constructor of the server QuicSession.
-enum QuicServerSessionOptions : uint32_t {
- // When set, instructs the server QuicSession to reject
- // client authentication certs that cannot be verified.
- QUICSERVERSESSION_OPTION_REJECT_UNAUTHORIZED = 0x1,
-
- // When set, instructs the server QuicSession to request
- // a client authentication cert
- QUICSERVERSESSION_OPTION_REQUEST_CERT = 0x2
-};
-
-// Options to alter the behavior of various functions on the
-// client QuicSession. These are set on the client QuicSession
-// constructor.
-enum QuicClientSessionOptions : uint32_t {
- // When set, instructs the client QuicSession to include an
- // OCSP request in the initial TLS handshake
- QUICCLIENTSESSION_OPTION_REQUEST_OCSP = 0x1,
-
- // When set, instructs the client QuicSession to verify the
- // hostname identity. This is required by QUIC and enabled
- // by default. We allow disabling it only for debugging
- // purposes.
- QUICCLIENTSESSION_OPTION_VERIFY_HOSTNAME_IDENTITY = 0x2,
-
- // When set, instructs the client QuicSession to perform
- // additional checks on TLS session resumption.
- QUICCLIENTSESSION_OPTION_RESUME = 0x4
-};
-
-#define QUICSESSION_SHARED_STATE(V) \
- V(KEYLOG_ENABLED, keylog_enabled, uint8_t) \
- V(CLIENT_HELLO_ENABLED, client_hello_enabled, uint8_t) \
- V(OCSP_ENABLED, ocsp_enabled, uint8_t) \
- V(PATH_VALIDATED_ENABLED, path_validated_enabled, uint8_t) \
- V(USE_PREFERRED_ADDRESS_ENABLED, use_preferred_address_enabled, uint8_t) \
- V(HANDSHAKE_CONFIRMED, handshake_confirmed, uint8_t) \
- V(IDLE_TIMEOUT, idle_timeout, uint8_t) \
- V(MAX_STREAMS_BIDI, max_streams_bidi, uint64_t) \
- V(MAX_STREAMS_UNI, max_streams_uni, uint64_t) \
- V(MAX_DATA_LEFT, max_data_left, uint64_t) \
- V(BYTES_IN_FLIGHT, bytes_in_flight, uint64_t)
-
-#define V(_, name, type) type name;
-struct QuicSessionState {
- QUICSESSION_SHARED_STATE(V)
-};
-#undef V
-
-#define V(id, name, _) \
- IDX_QUICSESSION_STATE_##id = offsetof(QuicSessionState, name),
-enum QuicSessionStateFields {
- QUICSESSION_SHARED_STATE(V)
- IDX_QUICSESSION_STATE_END
-};
-#undef V
-
-#define SESSION_STATS(V) \
- V(CREATED_AT, created_at, "Created At") \
- V(HANDSHAKE_START_AT, handshake_start_at, "Handshake Started") \
- V(HANDSHAKE_SEND_AT, handshake_send_at, "Handshke Last Sent") \
- V(HANDSHAKE_CONTINUE_AT, handshake_continue_at, "Handshke Continued") \
- V(HANDSHAKE_COMPLETED_AT, handshake_completed_at, "Handshake Completed") \
- V(HANDSHAKE_CONFIRMED_AT, handshake_confirmed_at, "Handshake Confirmed") \
- V(HANDSHAKE_ACKED_AT, handshake_acked_at, "Handshake Last Acknowledged") \
- V(SENT_AT, sent_at, "Last Sent At") \
- V(RECEIVED_AT, received_at, "Last Received At") \
- V(CLOSING_AT, closing_at, "Closing") \
- V(DESTROYED_AT, destroyed_at, "Destroyed At") \
- V(BYTES_RECEIVED, bytes_received, "Bytes Received") \
- V(BYTES_SENT, bytes_sent, "Bytes Sent") \
- V(BIDI_STREAM_COUNT, bidi_stream_count, "Bidi Stream Count") \
- V(UNI_STREAM_COUNT, uni_stream_count, "Uni Stream Count") \
- V(STREAMS_IN_COUNT, streams_in_count, "Streams In Count") \
- V(STREAMS_OUT_COUNT, streams_out_count, "Streams Out Count") \
- V(KEYUPDATE_COUNT, keyupdate_count, "Key Update Count") \
- V(LOSS_RETRANSMIT_COUNT, loss_retransmit_count, "Loss Retransmit Count") \
- V(ACK_DELAY_RETRANSMIT_COUNT, \
- ack_delay_retransmit_count, \
- "Ack Delay Retransmit Count") \
- V(PATH_VALIDATION_SUCCESS_COUNT, \
- path_validation_success_count, \
- "Path Validation Success Count") \
- V(PATH_VALIDATION_FAILURE_COUNT, \
- path_validation_failure_count, \
- "Path Validation Failure Count") \
- V(MAX_BYTES_IN_FLIGHT, max_bytes_in_flight, "Max Bytes In Flight") \
- V(BLOCK_COUNT, block_count, "Block Count") \
- V(MIN_RTT, min_rtt, "Minimum RTT") \
- V(LATEST_RTT, latest_rtt, "Latest RTT") \
- V(SMOOTHED_RTT, smoothed_rtt, "Smoothed RTT") \
- V(CWND, cwnd, "Cwnd") \
- V(RECEIVE_RATE, receive_rate, "Receive Rate / Sec") \
- V(SEND_RATE, send_rate, "Send Rate Sec") \
-
-#define V(name, _, __) IDX_QUIC_SESSION_STATS_##name,
-enum QuicSessionStatsIdx : int {
- SESSION_STATS(V)
- IDX_QUIC_SESSION_STATS_COUNT
-};
-#undef V
-
-#define V(_, name, __) uint64_t name;
-struct QuicSessionStats {
- SESSION_STATS(V)
-};
-#undef V
-
-struct QuicSessionStatsTraits {
- using Stats = QuicSessionStats;
- using Base = QuicSession;
-
- template <typename Fn>
- static void ToString(const Base& ptr, Fn&& add_field);
-};
-
-class QLogStream final : public AsyncWrap,
- public StreamBase {
- public:
- static BaseObjectPtr<QLogStream> Create(Environment* env);
-
- QLogStream(Environment* env, v8::Local<v8::Object> obj);
-
- void Emit(const uint8_t* data, size_t len, uint32_t flags);
-
- void End() { ended_ = true; }
-
- int ReadStart() override { return 0; }
- int ReadStop() override { return 0; }
- int DoShutdown(ShutdownWrap* req_wrap) override;
- int DoWrite(WriteWrap* w,
- uv_buf_t* bufs,
- size_t count,
- uv_stream_t* send_handle) override;
- bool IsAlive() override;
- bool IsClosing() override;
- AsyncWrap* GetAsyncWrap() override { return this; }
-
- SET_NO_MEMORY_INFO();
- SET_MEMORY_INFO_NAME(QLogStream);
- SET_SELF_SIZE(QLogStream);
-
- private:
- bool ended_ = false;
-};
-
-class QuicSessionListener {
- public:
- enum SessionCloseFlags {
- SESSION_CLOSE_FLAG_NONE,
- SESSION_CLOSE_FLAG_SILENT,
- SESSION_CLOSE_FLAG_STATELESS_RESET
- };
-
- virtual ~QuicSessionListener();
-
- virtual void OnKeylog(const char* str, size_t size);
- virtual void OnClientHello(
- const char* alpn,
- const char* server_name);
- virtual void OnCert(const char* server_name);
- virtual void OnOCSP(v8::Local<v8::Value> ocsp);
- virtual void OnStreamHeaders(
- int64_t stream_id,
- int kind,
- const std::vector<std::unique_ptr<QuicHeader>>& headers,
- int64_t push_id);
- virtual void OnStreamClose(
- int64_t stream_id,
- uint64_t app_error_code);
- virtual void OnStreamReset(
- int64_t stream_id,
- uint64_t app_error_code);
- virtual void OnSessionClose(
- QuicError error,
- int flags = SESSION_CLOSE_FLAG_NONE);
- virtual void OnStreamReady(BaseObjectPtr<QuicStream> stream);
- virtual void OnHandshakeCompleted();
- virtual void OnPathValidation(
- ngtcp2_path_validation_result res,
- const sockaddr* local,
- const sockaddr* remote);
- virtual void OnUsePreferredAddress(
- int family,
- const PreferredAddress& preferred_address);
- virtual void OnSessionTicket(int size, SSL_SESSION* session);
- virtual void OnStreamBlocked(int64_t stream_id);
- virtual void OnVersionNegotiation(
- uint32_t supported_version,
- const uint32_t* versions,
- size_t vcnt);
- virtual void OnQLog(QLogStream* qlog_stream);
-
- QuicSession* session() const { return session_.get(); }
-
- private:
- BaseObjectWeakPtr<QuicSession> session_;
- QuicSessionListener* previous_listener_ = nullptr;
- friend class QuicSession;
-};
-
-class JSQuicSessionListener final : public QuicSessionListener {
- public:
- void OnKeylog(const char* str, size_t size) override;
- void OnClientHello(
- const char* alpn,
- const char* server_name) override;
- void OnCert(const char* server_name) override;
- void OnOCSP(v8::Local<v8::Value> ocsp) override;
- void OnStreamHeaders(
- int64_t stream_id,
- int kind,
- const std::vector<std::unique_ptr<QuicHeader>>& headers,
- int64_t push_id) override;
- void OnStreamClose(
- int64_t stream_id,
- uint64_t app_error_code) override;
- void OnStreamReset(
- int64_t stream_id,
- uint64_t app_error_code) override;
- void OnSessionClose(
- QuicError error,
- int flags = SESSION_CLOSE_FLAG_NONE) override;
- void OnStreamReady(BaseObjectPtr<QuicStream> stream) override;
- void OnHandshakeCompleted() override;
- void OnPathValidation(
- ngtcp2_path_validation_result res,
- const sockaddr* local,
- const sockaddr* remote) override;
- void OnSessionTicket(int size, SSL_SESSION* session) override;
- void OnUsePreferredAddress(
- int family,
- const PreferredAddress& preferred_address) override;
- void OnStreamBlocked(int64_t stream_id) override;
- void OnVersionNegotiation(
- uint32_t supported_version,
- const uint32_t* versions,
- size_t vcnt) override;
- void OnQLog(QLogStream* qlog_stream) override;
-
- private:
- friend class QuicSession;
-};
-
-#define QUICCRYPTOCONTEXT_FLAGS(V) \
- V(IN_TLS_CALLBACK, in_tls_callback) \
- V(IN_KEY_UPDATE, in_key_update) \
- V(IN_OCSP_RESPONSE, in_ocsp_request) \
- V(IN_CLIENT_HELLO, in_client_hello) \
- V(EARLY_DATA, early_data)
-
-// The QuicCryptoContext class encapsulates all of the crypto/TLS
-// handshake details on behalf of a QuicSession.
-class QuicCryptoContext final : public MemoryRetainer {
- public:
- inline QuicCryptoContext(
- QuicSession* session,
- BaseObjectPtr<crypto::SecureContext> secure_context,
- ngtcp2_crypto_side side,
- uint32_t options);
-
- ~QuicCryptoContext() override;
-
- inline uint64_t Cancel();
-
- // Outgoing crypto data must be retained in memory until it is
- // explicitly acknowledged. AcknowledgeCryptoData will be invoked
- // when ngtcp2 determines that it has received an acknowledgement
- // for crypto data at the specified level. This is our indication
- // that the data for that level can be released.
- void AcknowledgeCryptoData(ngtcp2_crypto_level level, uint64_t datalen);
-
- inline void Initialize();
-
- // Enables openssl's TLS tracing mechanism for this session only.
- void EnableTrace();
-
- // Returns the server's prepared OCSP response for transmission. This
- // is not used by client QuicSession instances.
- inline v8::MaybeLocal<v8::Value> ocsp_response() const;
-
- // Returns ngtcp2's understanding of the current inbound crypto level
- inline ngtcp2_crypto_level read_crypto_level() const;
-
- // Returns ngtcp2's understanding of the current outbound crypto level
- inline ngtcp2_crypto_level write_crypto_level() const;
-
- inline bool early_data() const;
-
- bool is_option_set(uint32_t option) const { return options_ & option; }
-
- // Emits a single keylog line to the JavaScript layer
- inline void Keylog(const char* line);
-
- int OnClientHello();
-
- void OnClientHelloDone(BaseObjectPtr<crypto::SecureContext> context);
-
- int OnOCSP();
-
- void OnOCSPDone(v8::Local<v8::Value> ocsp_response);
-
- bool OnSecrets(
- ngtcp2_crypto_level level,
- const uint8_t* rx_secret,
- const uint8_t* tx_secret,
- size_t secretlen);
-
- int OnTLSStatus();
-
- // Receives and processes TLS handshake details
- int Receive(
- ngtcp2_crypto_level crypto_level,
- uint64_t offset,
- const uint8_t* data,
- size_t datalen);
-
- // Resumes the TLS handshake following a client hello or
- // OCSP callback
- inline void ResumeHandshake();
-
- inline v8::MaybeLocal<v8::Value> cert() const;
- inline v8::MaybeLocal<v8::Value> cipher_name() const;
- inline v8::MaybeLocal<v8::Value> cipher_version() const;
- inline v8::MaybeLocal<v8::Object> ephemeral_key() const;
- inline const char* hello_alpn() const;
- inline v8::MaybeLocal<v8::Array> hello_ciphers() const;
- inline const char* hello_servername() const;
- inline v8::MaybeLocal<v8::Value> peer_cert(bool abbreviated) const;
- inline std::string selected_alpn() const;
- inline const char* servername() const;
-
- void set_option(uint32_t option, bool on = true) {
- if (on)
- options_ |= option;
- else
- options_ &= ~option;
- }
-
-#define V(id, name) \
- inline bool is_##name() const { \
- return flags_ & (1 << QUICCRYPTOCONTEXT_FLAG_##id); } \
- inline void set_##name(bool on = true) { \
- if (on) \
- flags_ |= (1 << QUICCRYPTOCONTEXT_FLAG_##id); \
- else \
- flags_ &= ~(1 << QUICCRYPTOCONTEXT_FLAG_##id); \
- }
- QUICCRYPTOCONTEXT_FLAGS(V)
-#undef V
-
- inline bool set_session(crypto::SSLSessionPointer session);
-
- inline void set_tls_alert(int err);
-
- ngtcp2_crypto_side side() const { return side_; }
-
- void WriteHandshake(
- ngtcp2_crypto_level level,
- const uint8_t* data,
- size_t datalen);
-
- bool InitiateKeyUpdate();
-
- int VerifyPeerIdentity();
-
- QuicSession* session() const { return session_.get(); }
-
- void MemoryInfo(MemoryTracker* tracker) const override;
-
- SET_MEMORY_INFO_NAME(QuicCryptoContext)
- SET_SELF_SIZE(QuicCryptoContext)
-
- private:
- bool SetSecrets(
- ngtcp2_crypto_level level,
- const uint8_t* rx_secret,
- const uint8_t* tx_secret,
- size_t secretlen);
-
- BaseObjectWeakPtr<QuicSession> session_;
- BaseObjectPtr<crypto::SecureContext> secure_context_;
- ngtcp2_crypto_side side_;
- crypto::SSLPointer ssl_;
- QuicBuffer handshake_[3];
- uint32_t options_;
- uint32_t flags_ = 0;
-
- v8::Global<v8::ArrayBufferView> ocsp_response_;
- crypto::BIOPointer bio_trace_;
-
-#define V(id, _) QUICCRYPTOCONTEXT_FLAG_##id,
- enum QuicCryptoContextFlags : uint32_t {
- QUICCRYPTOCONTEXT_FLAGS(V)
- QUICCRYPTOCONTEXT_FLAG_COUNT
- };
-#undef V
-
- class TLSCallbackScope {
- public:
- explicit TLSCallbackScope(QuicCryptoContext* context) :
- context_(context) {
- context_->set_in_tls_callback();
- }
-
- ~TLSCallbackScope() {
- context_->set_in_tls_callback(false);
- }
-
- static bool is_in_callback(QuicCryptoContext* context) {
- return context->is_in_tls_callback();
- }
-
- private:
- QuicCryptoContext* context_;
- };
-
- class TLSHandshakeScope {
- public:
- using DoneCB = std::function<void()>;
- TLSHandshakeScope(
- QuicCryptoContext* context,
- DoneCB done) :
- context_(context),
- done_(done) {}
-
- ~TLSHandshakeScope() {
- if (!is_handshake_suspended())
- return;
-
- done_();
- // Only continue the TLS handshake if we are not currently running
- // synchronously within the TLS handshake function. This can happen
- // when the callback function passed to the clientHello and cert
- // event handlers is called synchronously. If the function is called
- // asynchronously, then we have to manually continue the handshake.
- if (!TLSCallbackScope::is_in_callback(context_))
- context_->ResumeHandshake();
- }
-
- private:
- bool is_handshake_suspended() const {
- return context_->is_in_ocsp_request() || context_->is_in_client_hello();
- }
-
-
- QuicCryptoContext* context_;
- DoneCB done_;
- };
-
- friend class QuicSession;
-};
-
-// A QuicApplication encapsulates the specific details of
-// working with a specific QUIC application (e.g. http/3).
-class QuicApplication : public MemoryRetainer,
- public mem::NgLibMemoryManagerBase {
- public:
- inline explicit QuicApplication(QuicSession* session);
- virtual ~QuicApplication() = default;
-
- // The QuicSession will call Initialize as soon as the TLS
- // secrets have been set. See QuicCryptoContext::OnSecrets
- virtual bool Initialize() = 0;
-
- // QuicSession will forward all received stream data immediately
- // on to the QuicApplication. The only additional processing the
- // QuicSession does is to automatically adjust the QuicSession-level
- // flow control window. It is up to the QuicApplication to do
- // the same for the QuicStream-level flow control.
- //
- // flags are passed on directly from ngtcp2. The most important
- // of which here is NGTCP2_STREAM_DATA_FLAG_FIN, which indicates
- // that this is the final chunk of data that the peer will send
- // for this stream.
- //
- // It is also possible for the NGTCP2_STREAM_DATA_FLAG_0RTT flag
- // to be set, indicating that this chunk of data was received in
- // a 0RTT packet before the TLS handshake completed. This would
- // indicate that it is not as secure and could be replayed by
- // an attacker. We're not currently making use of that flag.
- virtual bool ReceiveStreamData(
- uint32_t flags,
- int64_t stream_id,
- const uint8_t* data,
- size_t datalen,
- uint64_t offset) = 0;
-
- virtual void AcknowledgeStreamData(
- int64_t stream_id,
- uint64_t offset,
- size_t datalen) {
- Acknowledge(stream_id, offset, datalen);
- }
-
- virtual bool BlockStream(int64_t id) { return true; }
-
- virtual void ExtendMaxStreamsRemoteUni(uint64_t max_streams) {}
-
- virtual void ExtendMaxStreamsRemoteBidi(uint64_t max_streams) {}
-
- virtual void ExtendMaxStreamData(int64_t stream_id, uint64_t max_data) {}
-
- virtual void ResumeStream(int64_t stream_id) {}
-
- // Different QUIC applications may set some application data in
- // the session ticket (e.g. http/3 would set server settings in the
- // application data). By default, there's nothing to set.
- virtual void SetSessionTicketAppData(const SessionTicketAppData& app_data) {}
-
- // Different QUIC applications may set some application data in
- // the session ticket (e.g. http/3 would set server settings in the
- // application data). By default, there's nothing to get.
- virtual SessionTicketAppData::Status GetSessionTicketAppData(
- const SessionTicketAppData& app_data,
- SessionTicketAppData::Flag flag) {
- return flag == SessionTicketAppData::Flag::STATUS_RENEW ?
- SessionTicketAppData::Status::TICKET_USE_RENEW :
- SessionTicketAppData::Status::TICKET_USE;
- }
-
- virtual void StreamHeaders(
- int64_t stream_id,
- int kind,
- const std::vector<std::unique_ptr<QuicHeader>>& headers,
- int64_t push_id = 0);
-
- virtual void StreamClose(
- int64_t stream_id,
- uint64_t app_error_code);
-
- virtual void StreamReset(
- int64_t stream_id,
- uint64_t app_error_code);
-
- virtual bool SubmitInformation(
- int64_t stream_id,
- v8::Local<v8::Array> headers) { return false; }
-
- virtual bool SubmitHeaders(
- int64_t stream_id,
- v8::Local<v8::Array> headers,
- uint32_t flags) { return false; }
-
- virtual bool SubmitTrailers(
- int64_t stream_id,
- v8::Local<v8::Array> headers) { return false; }
-
- virtual BaseObjectPtr<QuicStream> SubmitPush(
- int64_t stream_id,
- v8::Local<v8::Array> headers) {
- // By default, push streams are not supported
- // by an application.
- return {};
- }
-
- inline Environment* env() const;
-
- bool SendPendingData();
- size_t max_header_pairs() const { return max_header_pairs_; }
- size_t max_header_length() const { return max_header_length_; }
-
- protected:
- QuicSession* session() const { return session_.get(); }
- bool needs_init() const { return needs_init_; }
- void set_init_done() { needs_init_ = false; }
- inline void set_stream_fin(int64_t stream_id);
- void set_max_header_pairs(size_t max) { max_header_pairs_ = max; }
- void set_max_header_length(size_t max) { max_header_length_ = max; }
- inline std::unique_ptr<QuicPacket> CreateStreamDataPacket();
-
- struct StreamData {
- size_t count = 0;
- size_t remaining = 0;
- int64_t id = -1;
- int fin = 0;
- ngtcp2_vec data[kMaxVectorCount] {};
- ngtcp2_vec* buf = nullptr;
- BaseObjectPtr<QuicStream> stream;
- StreamData() { buf = data; }
- };
-
- void Acknowledge(
- int64_t stream_id,
- uint64_t offset,
- size_t datalen);
- virtual int GetStreamData(StreamData* data) = 0;
- virtual bool StreamCommit(StreamData* data, size_t datalen) = 0;
- virtual bool ShouldSetFin(const StreamData& data) = 0;
-
- ssize_t WriteVStream(
- QuicPathStorage* path,
- uint8_t* buf,
- ssize_t* ndatalen,
- const StreamData& stream_data);
-
- private:
- void MaybeSetFin(const StreamData& stream_data);
- BaseObjectWeakPtr<QuicSession> session_;
- bool needs_init_ = true;
- size_t max_header_pairs_ = 0;
- size_t max_header_length_ = 0;
-};
-
-// QUICSESSION_FLAGS are converted into is_{name}() and set_{name}(bool on)
-// accessors on the QuicSession class.
-#define QUICSESSION_FLAGS(V) \
- V(WRAPPED, wrapped) \
- V(CLOSING, closing) \
- V(GRACEFUL_CLOSING, graceful_closing) \
- V(DESTROYED, destroyed) \
- V(TRANSPORT_PARAMS_SET, transport_params_set) \
- V(NGTCP2_CALLBACK, in_ngtcp2_callback) \
- V(CONNECTION_CLOSE_SCOPE, in_connection_close_scope) \
- V(SILENT_CLOSE, silent_closing) \
- V(STATELESS_RESET, stateless_reset) \
- V(CLOSING_TIMER_ENABLED, closing_timer_enabled)
-
-// QUIC sessions are logical connections that exchange data
-// back and forth between peer endpoints via UDP. Every QuicSession
-// has an associated TLS context and all data transferred between
-// the peers is always encrypted. Unlike TLS over TCP, however,
-// The QuicSession uses a session identifier that is independent
-// of both the local *and* peer IP address, allowing a QuicSession
-// to persist across changes in the network (one of the key features
-// of QUIC). QUIC sessions also support 0RTT, implement error
-// correction mechanisms to recover from lost packets, and flow
-// control. In other words, there's quite a bit going on within
-// a QuicSession object.
-class QuicSession final : public AsyncWrap,
- public mem::NgLibMemoryManager<
- QuicSession,
- ngtcp2_mem>,
- public StatsBase<QuicSessionStatsTraits> {
- public:
- // The default preferred address strategy is to ignore it
- static void IgnorePreferredAddressStrategy(
- QuicSession* session,
- const PreferredAddress& preferred_address);
-
- static void UsePreferredAddressStrategy(
- QuicSession* session,
- const PreferredAddress& preferred_address);
-
- static void Initialize(
- Environment* env,
- v8::Local<v8::Object> target,
- v8::Local<v8::Context> context);
-
- static BaseObjectPtr<QuicSession> CreateServer(
- QuicSocket* socket,
- const QuicSessionConfig& config,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- const QuicCID& dcid,
- const QuicCID& scid,
- const QuicCID& ocid,
- uint32_t version,
- const std::string& alpn = NGHTTP3_ALPN_H3,
- uint32_t options = 0,
- QlogMode qlog = QlogMode::kDisabled);
-
- static BaseObjectPtr<QuicSession> CreateClient(
- QuicSocket* socket,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- BaseObjectPtr<crypto::SecureContext> secure_context,
- ngtcp2_transport_params* early_transport_params,
- crypto::SSLSessionPointer early_session_ticket,
- v8::Local<v8::Value> dcid,
- PreferredAddressStrategy preferred_address_strategy =
- IgnorePreferredAddressStrategy,
- const std::string& alpn = NGHTTP3_ALPN_H3,
- const std::string& hostname = "",
- uint32_t options = 0,
- QlogMode qlog = QlogMode::kDisabled);
-
- static const int kInitialClientBufferLength = 4096;
-
- QuicSession(
- ngtcp2_crypto_side side,
- // The QuicSocket that created this session. Note that
- // it is possible to replace this socket later, after
- // the TLS handshake has completed. The QuicSession
- // should never assume that the socket will always
- // remain the same.
- QuicSocket* socket,
- v8::Local<v8::Object> wrap,
- BaseObjectPtr<crypto::SecureContext> secure_context,
- AsyncWrap::ProviderType provider_type,
- // QUIC is generally just a transport. The ALPN identifier
- // is used to specify the application protocol that is
- // layered on top. If not specified, this will default
- // to the HTTP/3 identifier. For QUIC, the alpn identifier
- // is always required.
- const std::string& alpn,
- const std::string& hostname,
- const QuicCID& dcid,
- uint32_t options = 0,
- PreferredAddressStrategy preferred_address_strategy =
- IgnorePreferredAddressStrategy);
-
- // Server Constructor
- QuicSession(
- QuicSocket* socket,
- const QuicSessionConfig& config,
- v8::Local<v8::Object> wrap,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- const QuicCID& dcid,
- const QuicCID& scid,
- const QuicCID& ocid,
- uint32_t version,
- const std::string& alpn,
- uint32_t options,
- QlogMode qlog);
-
- // Client Constructor
- QuicSession(
- QuicSocket* socket,
- v8::Local<v8::Object> wrap,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- BaseObjectPtr<crypto::SecureContext> secure_context,
- ngtcp2_transport_params* early_transport_params,
- crypto::SSLSessionPointer early_session_ticket,
- v8::Local<v8::Value> dcid,
- PreferredAddressStrategy preferred_address_strategy,
- const std::string& alpn,
- const std::string& hostname,
- uint32_t options,
- QlogMode qlog);
-
- ~QuicSession() override;
-
- std::string diagnostic_name() const override;
-
- inline QuicCID dcid() const;
-
- QuicApplication* application() const { return application_.get(); }
-
- QuicCryptoContext* crypto_context() const { return crypto_context_.get(); }
-
- QuicSessionListener* listener() const { return listener_; }
-
- BaseObjectPtr<QuicStream> CreateStream(int64_t id);
-
- BaseObjectPtr<QuicStream> FindStream(int64_t id) const;
-
- inline bool HasStream(int64_t id) const;
-
- inline bool allow_early_data() const;
-
-#define V(id, name) \
- bool is_##name() const { return flags_ & (1 << QUICSESSION_FLAG_##id); } \
- void set_##name(bool on = true) { \
- if (on) \
- flags_ |= (1 << QUICSESSION_FLAG_##id); \
- else \
- flags_ &= ~(1 << QUICSESSION_FLAG_##id); \
- }
- QUICSESSION_FLAGS(V)
-#undef V
-
- // Returns true if the QuicSession has entered the
- // closing period after sending a CONNECTION_CLOSE.
- // While true, the QuicSession is only permitted to
- // transmit CONNECTION_CLOSE frames until either the
- // idle timeout period elapses or until the QuicSession
- // is explicitly destroyed.
- inline bool is_in_closing_period() const;
-
- // Returns true if the QuicSession has received a
- // CONNECTION_CLOSE frame from the peer. Once in
- // the draining period, the QuicSession is not
- // permitted to send any frames to the peer. The
- // QuicSession will be silently closed after either
- // the idle timeout period elapses or until the
- // QuicSession is explicitly destroyed.
- inline bool is_in_draining_period() const;
-
- inline bool is_server() const;
-
- // Starting a GracefulClose disables the ability to open or accept
- // new streams for this session. Existing streams are allowed to
- // close naturally on their own. Once called, the QuicSession will
- // be immediately closed once there are no remaining streams. Note
- // that no notification is given to the connecting peer that we're
- // in a graceful closing state. A CONNECTION_CLOSE will be sent only
- // once Close() is called.
- inline void StartGracefulClose();
-
- QuicError last_error() const { return last_error_; }
-
- size_t max_packet_length() const { return max_pktlen_; }
-
- BaseObjectPtr<QLogStream> qlog_stream();
-
- // Get the ALPN protocol identifier configured for this QuicSession.
- // For server sessions, this will be compared against the client requested
- // ALPN identifier to determine if there is a protocol match.
- const std::string& alpn() const { return alpn_; }
-
- // Get the hostname configured for this QuicSession. This is generally
- // only used by client sessions.
- const std::string& hostname() const { return hostname_; }
-
- // Returns the associated peer's address. Note that this
- // value can change over the lifetime of the QuicSession.
- // The fact that the session is not tied intrinsically to
- // a single address is one of the benefits of QUIC.
- const SocketAddress& remote_address() const { return remote_address_; }
-
- inline QuicSocket* socket() const;
-
- ngtcp2_conn* connection() const { return connection_.get(); }
-
- void AddStream(BaseObjectPtr<QuicStream> stream);
-
- void AddToSocket(QuicSocket* socket);
-
- // Immediately discards the state of the QuicSession
- // and renders the QuicSession instance completely
- // unusable.
- void Destroy();
-
- // Extends the QUIC stream flow control window. This is
- // called after received data has been consumed and we
- // want to allow the peer to send more data.
- inline void ExtendStreamOffset(int64_t stream_id, size_t amount);
-
- // Extends the QUIC session flow control window
- inline void ExtendOffset(size_t amount);
-
- // Retrieve the local transport parameters established for
- // this ngtcp2_conn
- inline void GetLocalTransportParams(ngtcp2_transport_params* params);
-
- // The QUIC version that has been negotiated for this session
- inline uint32_t negotiated_version() const;
-
- // True only if ngtcp2 considers the TLS handshake to be completed
- inline bool is_handshake_completed() const;
-
- // Checks to see if data needs to be retransmitted
- void OnRetransmitTimeout();
-
- // Called when the session has been determined to have been
- // idle for too long and needs to be torn down.
- inline void OnIdleTimeout();
-
- bool OpenBidirectionalStream(int64_t* stream_id);
-
- bool OpenUnidirectionalStream(int64_t* stream_id);
-
- // Ping causes the QuicSession to serialize any currently
- // pending frames in it's queue, including any necessary
- // PROBE packets. This is a best attempt, fire-and-forget
- // type of operation. There is no way to listen for a ping
- // response. The main intent of using Ping is to either keep
- // the connection from becoming idle or to update RTT stats.
- void Ping();
-
- // Receive and process a QUIC packet received from the peer
- bool Receive(
- ssize_t nread,
- const uint8_t* data,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- unsigned int flags);
-
- // Receive a chunk of QUIC stream data received from the peer
- bool ReceiveStreamData(
- uint32_t flags,
- int64_t stream_id,
- const uint8_t* data,
- size_t datalen,
- uint64_t offset);
-
- void RemoveStream(int64_t stream_id);
-
- void RemoveFromSocket();
-
- // Causes pending ngtcp2 frames to be serialized and sent
- void SendPendingData();
-
- inline void ShutdownStream(int64_t stream_id, uint64_t code);
-
- inline bool SendPacket(
- std::unique_ptr<QuicPacket> packet,
- const ngtcp2_path_storage& path);
-
- inline uint64_t max_data_left() const;
-
- inline uint64_t max_local_streams_uni() const;
-
- inline void set_last_error(
- QuicError error = {
- uint32_t{QUIC_ERROR_SESSION},
- uint64_t{NGTCP2_NO_ERROR}
- });
-
- inline void set_last_error(int32_t family, uint64_t error_code);
-
- inline void set_last_error(int32_t family, int error_code);
-
- inline void set_remote_transport_params();
-
- bool set_socket(QuicSocket* socket, bool nat_rebinding = false);
-
- int set_session(SSL_SESSION* session);
-
- const StreamsMap& streams() const { return streams_; }
-
- void ResumeStream(int64_t stream_id);
-
- // Submits informational headers to the QUIC Application
- // implementation. If headers are not supported, false
- // will be returned. Otherwise, returns true
- inline bool SubmitInformation(
- int64_t stream_id,
- v8::Local<v8::Array> headers);
-
- // Submits initial headers to the QUIC Application
- // implementation. If headers are not supported, false
- // will be returned. Otherwise, returns true
- inline bool SubmitHeaders(
- int64_t stream_id,
- v8::Local<v8::Array> headers,
- uint32_t flags);
-
- // Submits trailing headers to the QUIC Application
- // implementation. If headers are not supported, false
- // will be returned. Otherwise, returns true
- inline bool SubmitTrailers(
- int64_t stream_id,
- v8::Local<v8::Array> headers);
-
- inline BaseObjectPtr<QuicStream> SubmitPush(
- int64_t stream_id,
- v8::Local<v8::Array> headers);
-
- // Error handling for the QuicSession. client and server
- // instances will do different things here, but ultimately
- // an error means that the QuicSession
- // should be torn down.
- void HandleError();
-
- bool SendConnectionClose();
-
- bool IsResetToken(
- const QuicCID& cid,
- const uint8_t* data,
- size_t datalen);
-
- // Implementation for mem::NgLibMemoryManager
- inline void CheckAllocatedSize(size_t previous_size) const;
-
- inline void IncreaseAllocatedSize(size_t size);
-
- inline void DecreaseAllocatedSize(size_t size);
-
- // Initiate closing of the QuicSession. This will round trip
- // through JavaScript, causing all currently opened streams
- // to be closed. If the SESSION_CLOSE_FLAG_SILENT flag is
- // set, the connected peer will not be notified, otherwise
- // an attempt will be made to send a CONNECTION_CLOSE frame
- // to the peer. If Close is called while within the ngtcp2
- // callback scope, sending the CONNECTION_CLOSE will be
- // deferred until the ngtcp2 callback scope exits.
- void Close(
- int close_flags = QuicSessionListener::SESSION_CLOSE_FLAG_NONE);
-
- void PushListener(QuicSessionListener* listener);
-
- void RemoveListener(QuicSessionListener* listener);
-
- inline bool is_unable_to_send_packets();
-
- inline void set_connection_id_strategy(
- ConnectionIDStrategy strategy);
-
- inline void set_preferred_address_strategy(
- PreferredAddressStrategy strategy);
-
- inline void SetSessionTicketAppData(
- const SessionTicketAppData& app_data);
-
- inline SessionTicketAppData::Status GetSessionTicketAppData(
- const SessionTicketAppData& app_data,
- SessionTicketAppData::Flag flag);
-
- inline void SelectPreferredAddress(
- const PreferredAddress& preferred_address);
-
- // Report that the stream data is flow control blocked
- inline void StreamDataBlocked(int64_t stream_id);
-
- // SendSessionScope triggers SendPendingData() when not executing
- // within the context of an ngtcp2 callback. When within an ngtcp2
- // callback, SendPendingData will always be called when the callbacks
- // complete.
- class SendSessionScope final {
- public:
- explicit SendSessionScope(QuicSession* session)
- : session_(session) {
- CHECK(session_);
- }
-
- SendSessionScope(const SendSessionScope& other) = delete;
-
- ~SendSessionScope() {
- if (NgCallbackScope::InNgCallbackScope(session_.get()) ||
- session_->is_in_closing_period() ||
- session_->is_in_draining_period()) {
- return;
- }
- session_->SendPendingData();
- }
-
- private:
- BaseObjectPtr<QuicSession> session_;
- };
-
- // ConnectionCloseScope triggers sending a CONNECTION_CLOSE
- // when not executing within the context of an ngtcp2 callback
- // and the session is in the correct state.
- class ConnectionCloseScope final {
- public:
- ConnectionCloseScope(QuicSession* session, bool silent = false)
- : session_(session),
- silent_(silent) {
- CHECK(session_);
- // If we are already in a ConnectionCloseScope, ignore.
- if (session_->is_in_connection_close_scope())
- silent_ = true;
- else
- session_->set_in_connection_close_scope();
- }
-
- ConnectionCloseScope(const ConnectionCloseScope& other) = delete;
-
- ~ConnectionCloseScope() {
- if (silent_ ||
- NgCallbackScope::InNgCallbackScope(session_.get()) ||
- session_->is_in_closing_period() ||
- session_->is_in_draining_period()) {
- return;
- }
- session_->set_in_connection_close_scope(false);
- session_->SendConnectionClose();
- }
-
- private:
- BaseObjectPtr<QuicSession> session_;
- bool silent_ = false;
- };
-
- // Tracks whether or not we are currently within an ngtcp2 callback
- // function. Certain ngtcp2 APIs are not supposed to be called when
- // within a callback. We use this as a gate to check.
- class NgCallbackScope final {
- public:
- explicit NgCallbackScope(QuicSession* session) : session_(session) {
- CHECK(session_);
- CHECK(!InNgCallbackScope(session));
- session_->set_in_ngtcp2_callback();
- }
-
- NgCallbackScope(const NgCallbackScope& other) = delete;
-
- ~NgCallbackScope() {
- session_->set_in_ngtcp2_callback(false);
- }
-
- static bool InNgCallbackScope(QuicSession* session) {
- return session->is_in_ngtcp2_callback();
- }
-
- private:
- BaseObjectPtr<QuicSession> session_;
- };
-
- QuicState* quic_state() { return quic_state_.get(); }
-
- void MemoryInfo(MemoryTracker* tracker) const override;
- SET_MEMORY_INFO_NAME(QuicSession)
- SET_SELF_SIZE(QuicSession)
-
- private:
- static void RandomConnectionIDStrategy(
- QuicSession* session,
- ngtcp2_cid* cid,
- size_t cidlen);
-
- // Initialize the QuicSession as a server
- void InitServer(
- QuicSessionConfig config,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- const QuicCID& dcid,
- const QuicCID& scid,
- const QuicCID& ocid,
- uint32_t version,
- QlogMode qlog);
-
- // Initialize the QuicSession as a client
- void InitClient(
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- ngtcp2_transport_params* early_transport_params,
- crypto::SSLSessionPointer early_session_ticket,
- v8::Local<v8::Value> dcid,
- QlogMode qlog);
-
- bool InitApplication();
-
- void AckedStreamDataOffset(
- int64_t stream_id,
- uint64_t offset,
- uint64_t datalen);
-
- void ExtendMaxStreamData(int64_t stream_id, uint64_t max_data);
-
- void ExtendMaxStreams(bool bidi, uint64_t max_streams);
-
- void ExtendMaxStreamsUni(uint64_t max_streams);
-
- void ExtendMaxStreamsBidi(uint64_t max_streams);
-
- void ExtendMaxStreamsRemoteUni(uint64_t max_streams);
-
- void ExtendMaxStreamsRemoteBidi(uint64_t max_streams);
-
- void GetNewConnectionID(ngtcp2_cid* cid, uint8_t* token, size_t cidlen);
-
- void GetConnectionCloseInfo();
-
- void HandshakeCompleted();
-
- void HandshakeConfirmed();
-
- void PathValidation(
- const ngtcp2_path* path,
- ngtcp2_path_validation_result res);
-
- bool ReceivePacket(ngtcp2_path* path, const uint8_t* data, ssize_t nread);
-
- void ScheduleRetransmit();
-
- bool SendPacket(std::unique_ptr<QuicPacket> packet);
-
- void StreamClose(int64_t stream_id, uint64_t app_error_code);
-
- void StreamReset(
- int64_t stream_id,
- uint64_t final_size,
- uint64_t app_error_code);
-
- bool WritePackets(const char* diagnostic_label = nullptr);
-
- void UpdateConnectionID(
- int type,
- const QuicCID& cid,
- const StatelessResetToken& token);
-
- void UpdateDataStats();
-
- void UpdateEndpoint(const ngtcp2_path& path);
-
- void VersionNegotiation(const uint32_t* sv, size_t nsv);
-
- void UpdateIdleTimer();
-
- void UpdateClosingTimer();
-
- void UpdateRetransmitTimer(uint64_t timeout);
-
- bool StartClosingPeriod();
-
- void IncrementConnectionCloseAttempts();
-
- bool ShouldAttemptConnectionClose();
-
- static int OnReceiveCryptoData(
- ngtcp2_conn* conn,
- ngtcp2_crypto_level crypto_level,
- uint64_t offset,
- const uint8_t* data,
- size_t datalen,
- void* user_data);
-
- static int OnHandshakeCompleted(
- ngtcp2_conn* conn,
- void* user_data);
-
- static int OnHandshakeConfirmed(
- ngtcp2_conn* conn,
- void* user_data);
-
- static int OnReceiveStreamData(
- ngtcp2_conn* conn,
- uint32_t flags,
- int64_t stream_id,
- uint64_t offset,
- const uint8_t* data,
- size_t datalen,
- void* user_data,
- void* stream_user_data);
-
- static int OnAckedCryptoOffset(
- ngtcp2_conn* conn,
- ngtcp2_crypto_level crypto_level,
- uint64_t offset,
- uint64_t datalen,
- void* user_data);
-
- static int OnAckedStreamDataOffset(
- ngtcp2_conn* conn,
- int64_t stream_id,
- uint64_t offset,
- uint64_t datalen,
- void* user_data,
- void* stream_user_data);
-
- static int OnSelectPreferredAddress(
- ngtcp2_conn* conn,
- ngtcp2_addr* dest,
- const ngtcp2_preferred_addr* paddr,
- void* user_data);
-
- static int OnStreamClose(
- ngtcp2_conn* conn,
- int64_t stream_id,
- uint64_t app_error_code,
- void* user_data,
- void* stream_user_data);
-
- static int OnStreamOpen(
- ngtcp2_conn* conn,
- int64_t stream_id,
- void* user_data);
-
- static int OnStreamReset(
- ngtcp2_conn* conn,
- int64_t stream_id,
- uint64_t final_size,
- uint64_t app_error_code,
- void* user_data,
- void* stream_user_data);
-
- static int OnRand(
- uint8_t* dest,
- size_t destlen,
- ngtcp2_rand_ctx ctx);
-
- static int OnGetNewConnectionID(
- ngtcp2_conn* conn,
- ngtcp2_cid* cid,
- uint8_t* token,
- size_t cidlen,
- void* user_data);
-
- static int OnRemoveConnectionID(
- ngtcp2_conn* conn,
- const ngtcp2_cid* cid,
- void* user_data);
-
- static int OnPathValidation(
- ngtcp2_conn* conn,
- const ngtcp2_path* path,
- ngtcp2_path_validation_result res,
- void* user_data);
-
- static int OnExtendMaxStreamsUni(
- ngtcp2_conn* conn,
- uint64_t max_streams,
- void* user_data);
-
- static int OnExtendMaxStreamsBidi(
- ngtcp2_conn* conn,
- uint64_t max_streams,
- void* user_data);
-
- static int OnExtendMaxStreamData(
- ngtcp2_conn* conn,
- int64_t stream_id,
- uint64_t max_data,
- void* user_data,
- void* stream_user_data);
-
- static int OnVersionNegotiation(
- ngtcp2_conn* conn,
- const ngtcp2_pkt_hd* hd,
- const uint32_t* sv,
- size_t nsv,
- void* user_data);
-
- static int OnStatelessReset(
- ngtcp2_conn* conn,
- const ngtcp2_pkt_stateless_reset* sr,
- void* user_data);
-
- static int OnExtendMaxStreamsRemoteUni(
- ngtcp2_conn* conn,
- uint64_t max_streams,
- void* user_data);
-
- static int OnExtendMaxStreamsRemoteBidi(
- ngtcp2_conn* conn,
- uint64_t max_streams,
- void* user_data);
-
- static int OnConnectionIDStatus(
- ngtcp2_conn* conn,
- int type,
- uint64_t seq,
- const ngtcp2_cid* cid,
- const uint8_t* token,
- void* user_data);
-
- static void OnQlogWrite(
- void* user_data,
- uint32_t flags,
- const void* data,
- size_t len);
-
-#define V(id, _) QUICSESSION_FLAG_##id,
- enum QuicSessionFlags : uint32_t {
- QUICSESSION_FLAGS(V)
- QUICSESSION_FLAG_COUNT
- };
-#undef V
-
- // Select the QUIC Application based on the configured ALPN identifier
- QuicApplication* SelectApplication(QuicSession* session);
-
- ngtcp2_mem alloc_info_;
- std::unique_ptr<QuicCryptoContext> crypto_context_;
- std::unique_ptr<QuicApplication> application_;
- BaseObjectWeakPtr<QuicSocket> socket_;
- std::string alpn_;
- std::string hostname_;
- QuicError last_error_ = {
- uint32_t{QUIC_ERROR_SESSION},
- uint64_t{NGTCP2_NO_ERROR}
- };
- ConnectionPointer connection_;
- SocketAddress local_address_{};
- SocketAddress remote_address_{};
- uint32_t flags_ = 0;
- size_t max_pktlen_ = 0;
- size_t current_ngtcp2_memory_ = 0;
- size_t connection_close_attempts_ = 0;
- size_t connection_close_limit_ = 1;
-
- ConnectionIDStrategy connection_id_strategy_ = nullptr;
- PreferredAddressStrategy preferred_address_strategy_ = nullptr;
-
- QuicSessionListener* listener_ = nullptr;
- JSQuicSessionListener default_listener_;
-
- TimerWrapHandle idle_;
- TimerWrapHandle retransmit_;
-
- QuicCID scid_;
- QuicCID dcid_;
- QuicCID pscid_;
- ngtcp2_transport_params transport_params_;
-
- std::unique_ptr<QuicPacket> conn_closebuf_;
-
- StreamsMap streams_;
-
- AliasedStruct<QuicSessionState> state_;
-
- struct RemoteTransportParamsDebug {
- QuicSession* session;
- explicit RemoteTransportParamsDebug(QuicSession* session_)
- : session(session_) {}
- std::string ToString() const;
- };
-
- static const ngtcp2_conn_callbacks callbacks[2];
-
- BaseObjectPtr<QuicState> quic_state_;
- BaseObjectWeakPtr<QLogStream> qlog_stream_;
-
- friend class QuicCryptoContext;
- friend class QuicSessionListener;
- friend class JSQuicSessionListener;
-};
-
-class QuicCallbackScope {
- public:
- explicit QuicCallbackScope(QuicSession* session);
- ~QuicCallbackScope();
-
- void operator=(const QuicCallbackScope&) = delete;
- void operator=(QuicCallbackScope&&) = delete;
- QuicCallbackScope(const QuicCallbackScope&) = delete;
- QuicCallbackScope(QuicCallbackScope&&) = delete;
-
- private:
- BaseObjectPtr<QuicSession> session_;
- std::unique_ptr<InternalCallbackScope> private_;
- v8::TryCatch try_catch_;
-};
-
-} // namespace quic
-} // namespace node
-
-#endif // NODE_WANT_INTERNALS
-#endif // SRC_QUIC_NODE_QUIC_SESSION_H_
diff --git a/src/quic/node_quic_socket-inl.h b/src/quic/node_quic_socket-inl.h
deleted file mode 100644
index 8e7bc65d784..00000000000
--- a/src/quic/node_quic_socket-inl.h
+++ /dev/null
@@ -1,192 +0,0 @@
-#ifndef SRC_QUIC_NODE_QUIC_SOCKET_INL_H_
-#define SRC_QUIC_NODE_QUIC_SOCKET_INL_H_
-
-#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#include "node_quic_socket.h"
-#include "node_sockaddr-inl.h"
-#include "node_quic_session.h"
-#include "node_crypto.h"
-#include "debug_utils-inl.h"
-
-namespace node {
-
-using crypto::EntropySource;
-
-namespace quic {
-
-std::unique_ptr<QuicPacket> QuicPacket::Create(
- const char* diagnostic_label,
- size_t len) {
- CHECK_LE(len, MAX_PKTLEN);
- return std::make_unique<QuicPacket>(diagnostic_label, len);
-}
-
-std::unique_ptr<QuicPacket> QuicPacket::Copy(
- const std::unique_ptr<QuicPacket>& other) {
- return std::make_unique<QuicPacket>(*other.get());
-}
-
-void QuicPacket::set_length(size_t len) {
- CHECK_LE(len, MAX_PKTLEN);
- len_ = len;
-}
-
-int QuicEndpoint::Send(
- uv_buf_t* buf,
- size_t len,
- const sockaddr* addr) {
- int ret = static_cast<int>(udp_->Send(buf, len, addr));
- if (ret == 0)
- IncrementPendingCallbacks();
- return ret;
-}
-
-int QuicEndpoint::ReceiveStart() {
- return udp_->RecvStart();
-}
-
-int QuicEndpoint::ReceiveStop() {
- return udp_->RecvStop();
-}
-
-void QuicEndpoint::WaitForPendingCallbacks() {
- if (!has_pending_callbacks()) {
- listener_->OnEndpointDone(this);
- return;
- }
- waiting_for_callbacks_ = true;
-}
-
-void QuicSocket::AssociateCID(
- const QuicCID& cid,
- const QuicCID& scid) {
- if (cid && scid)
- dcid_to_scid_[cid] = scid;
-}
-
-void QuicSocket::DisassociateCID(const QuicCID& cid) {
- if (cid) {
- Debug(this, "Removing association for cid %s", cid);
- dcid_to_scid_.erase(cid);
- }
-}
-
-void QuicSocket::AssociateStatelessResetToken(
- const StatelessResetToken& token,
- BaseObjectPtr<QuicSession> session) {
- Debug(this, "Associating stateless reset token %s", token);
- token_map_[token] = session;
-}
-
-SocketAddress QuicSocket::local_address() const {
- DCHECK(preferred_endpoint_);
- return preferred_endpoint_->local_address();
-}
-
-void QuicSocket::DisassociateStatelessResetToken(
- const StatelessResetToken& token) {
- Debug(this, "Removing stateless reset token %s", token);
- token_map_.erase(token);
-}
-
-void QuicSocket::ReceiveStart() {
- for (const auto& endpoint : endpoints_)
- CHECK_EQ(endpoint->ReceiveStart(), 0);
-}
-
-void QuicSocket::ReceiveStop() {
- for (const auto& endpoint : endpoints_)
- CHECK_EQ(endpoint->ReceiveStop(), 0);
-}
-
-void QuicSocket::RemoveSession(
- const QuicCID& cid,
- const SocketAddress& addr) {
- DecrementSocketAddressCounter(addr);
- sessions_.erase(cid);
-}
-
-void QuicSocket::ReportSendError(int error) {
- listener_->OnError(error);
-}
-
-void QuicSocket::IncrementStatelessResetCounter(const SocketAddress& addr) {
- addrLRU_.Upsert(addr)->reset_count++;
-}
-
-void QuicSocket::IncrementSocketAddressCounter(const SocketAddress& addr) {
- addrLRU_.Upsert(addr)->active_connections++;
-}
-
-void QuicSocket::DecrementSocketAddressCounter(const SocketAddress& addr) {
- SocketAddressInfo* counts = addrLRU_.Peek(addr);
- if (counts != nullptr && counts->active_connections > 0)
- counts->active_connections--;
-}
-
-size_t QuicSocket::GetCurrentSocketAddressCounter(const SocketAddress& addr) {
- SocketAddressInfo* counts = addrLRU_.Peek(addr);
- return counts != nullptr ? counts->active_connections : 0;
-}
-
-size_t QuicSocket::GetCurrentStatelessResetCounter(const SocketAddress& addr) {
- SocketAddressInfo* counts = addrLRU_.Peek(addr);
- return counts != nullptr ? counts->reset_count : 0;
-}
-
-void QuicSocket::ServerBusy(bool on) {
- Debug(this, "Turning Server Busy Response %s", on ? "on" : "off");
- state_->server_busy = on ? 1 : 0;
- listener_->OnServerBusy();
-}
-
-bool QuicSocket::is_diagnostic_packet_loss(double prob) const {
- if (LIKELY(prob == 0.0)) return false;
- unsigned char c = 255;
- EntropySource(&c, 1);
- return (static_cast<double>(c) / 255) < prob;
-}
-
-void QuicSocket::set_diagnostic_packet_loss(double rx, double tx) {
- rx_loss_ = rx;
- tx_loss_ = tx;
-}
-
-void QuicSocket::set_validated_address(const SocketAddress& addr) {
- addrLRU_.Upsert(addr)->validated = true;
-}
-
-bool QuicSocket::is_validated_address(const SocketAddress& addr) const {
- auto info = addrLRU_.Peek(addr);
- return info != nullptr ? info->validated : false;
-}
-
-void QuicSocket::AddSession(
- const QuicCID& cid,
- BaseObjectPtr<QuicSession> session) {
- sessions_[cid] = session;
- IncrementSocketAddressCounter(session->remote_address());
- IncrementStat(
- session->is_server() ?
- &QuicSocketStats::server_sessions :
- &QuicSocketStats::client_sessions);
-}
-
-void QuicSocket::AddEndpoint(
- BaseObjectPtr<QuicEndpoint> endpoint_,
- bool preferred) {
- Debug(this, "Adding %sendpoint", preferred ? "preferred " : "");
- if (preferred || endpoints_.empty())
- preferred_endpoint_ = endpoint_;
- endpoints_.emplace_back(endpoint_);
- if (state_->server_listening)
- endpoint_->ReceiveStart();
-}
-
-} // namespace quic
-} // namespace node
-
-#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#endif // SRC_QUIC_NODE_QUIC_SOCKET_INL_H_
diff --git a/src/quic/node_quic_socket.cc b/src/quic/node_quic_socket.cc
deleted file mode 100644
index 810705014ca..00000000000
--- a/src/quic/node_quic_socket.cc
+++ /dev/null
@@ -1,1202 +0,0 @@
-#include "node_quic_socket-inl.h" // NOLINT(build/include)
-#include "aliased_struct-inl.h"
-#include "allocated_buffer-inl.h"
-#include "async_wrap-inl.h"
-#include "debug_utils-inl.h"
-#include "env-inl.h"
-#include "memory_tracker-inl.h"
-#include "nghttp2/nghttp2.h"
-#include "nghttp3/nghttp3.h"
-#include "node.h"
-#include "node_buffer.h"
-#include "node_crypto.h"
-#include "node_internals.h"
-#include "node_mem-inl.h"
-#include "node_quic_crypto.h"
-#include "node_quic_session-inl.h"
-#include "node_quic_util-inl.h"
-#include "node_sockaddr-inl.h"
-#include "req_wrap-inl.h"
-#include "util.h"
-#include "uv.h"
-#include "v8.h"
-
-#include <random>
-
-namespace node {
-
-using crypto::EntropySource;
-using crypto::SecureContext;
-
-using v8::ArrayBufferView;
-using v8::Context;
-using v8::FunctionCallbackInfo;
-using v8::FunctionTemplate;
-using v8::HandleScope;
-using v8::Isolate;
-using v8::Local;
-using v8::Number;
-using v8::Object;
-using v8::ObjectTemplate;
-using v8::PropertyAttribute;
-using v8::Value;
-
-namespace quic {
-
-namespace {
-// The reserved version is a mechanism QUIC endpoints
-// can use to ensure correct handling of version
-// negotiation. It is defined by the QUIC spec in
-// https://tools.ietf.org/html/draft-ietf-quic-transport-24#section-6.3
-// Specifically, any version that follows the pattern
-// 0x?a?a?a?a may be used to force version negotiation.
-inline uint32_t GenerateReservedVersion(
- const SocketAddress& addr,
- uint32_t version) {
- socklen_t addrlen = addr.length();
- uint32_t h = 0x811C9DC5u;
- const uint8_t* p = addr.raw();
- const uint8_t* ep = p + addrlen;
- for (; p != ep; ++p) {
- h ^= *p;
- h *= 0x01000193u;
- }
- version = htonl(version);
- p = reinterpret_cast<const uint8_t*>(&version);
- ep = p + sizeof(version);
- for (; p != ep; ++p) {
- h ^= *p;
- h *= 0x01000193u;
- }
- h &= 0xf0f0f0f0u;
- h |= 0x0a0a0a0au;
- return h;
-}
-
-bool IsShortHeader(
- uint32_t version,
- const uint8_t* pscid,
- size_t pscidlen) {
- return version == NGTCP2_PROTO_VER &&
- pscid == nullptr &&
- pscidlen == 0;
-}
-} // namespace
-
-QuicPacket::QuicPacket(const char* diagnostic_label, size_t len)
- : data_{0},
- len_(len),
- diagnostic_label_(diagnostic_label) {
- CHECK_LE(len, MAX_PKTLEN);
-}
-
-QuicPacket::QuicPacket(const QuicPacket& other) :
- QuicPacket(other.diagnostic_label_, other.len_) {
- memcpy(&data_, &other.data_, other.len_);
-}
-
-const char* QuicPacket::diagnostic_label() const {
- return diagnostic_label_ != nullptr ?
- diagnostic_label_ : "unspecified";
-}
-
-QuicSocketListener::~QuicSocketListener() {
- if (socket_)
- socket_->RemoveListener(this);
-}
-
-void QuicSocketListener::OnError(ssize_t code) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnError(code);
-}
-
-void QuicSocketListener::OnSessionReady(BaseObjectPtr<QuicSession> session) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnSessionReady(session);
-}
-
-void QuicSocketListener::OnServerBusy() {
- if (previous_listener_ != nullptr)
- previous_listener_->OnServerBusy();
-}
-
-void QuicSocketListener::OnEndpointDone(QuicEndpoint* endpoint) {
- if (previous_listener_ != nullptr)
- previous_listener_->OnEndpointDone(endpoint);
-}
-
-void QuicSocketListener::OnDestroy() {
- if (previous_listener_ != nullptr)
- previous_listener_->OnDestroy();
-}
-
-void JSQuicSocketListener::OnError(ssize_t code) {
- Environment* env = socket()->env();
- HandleScope scope(env->isolate());
- Context::Scope context_scope(env->context());
- Local<Value> arg = Number::New(env->isolate(), static_cast<double>(code));
- socket()->MakeCallback(env->quic_on_socket_close_function(), 1, &arg);
-}
-
-void JSQuicSocketListener::OnSessionReady(BaseObjectPtr<QuicSession> session) {
- Environment* env = socket()->env();
- Local<Value> arg = session->object();
- Context::Scope context_scope(env->context());
- socket()->MakeCallback(env->quic_on_session_ready_function(), 1, &arg);
-}
-
-void JSQuicSocketListener::OnServerBusy() {
- Environment* env = socket()->env();
- HandleScope handle_scope(env->isolate());
- Context::Scope context_scope(env->context());
- socket()->MakeCallback(
- env->quic_on_socket_server_busy_function(), 0, nullptr);
-}
-
-void JSQuicSocketListener::OnEndpointDone(QuicEndpoint* endpoint) {
- Environment* env = socket()->env();
- HandleScope scope(env->isolate());
- Context::Scope context_scope(env->context());
- MakeCallback(
- env->isolate(),
- endpoint->object(),
- env->ondone_string(),
- 0, nullptr);
-}
-
-void JSQuicSocketListener::OnDestroy() {
- // Do nothing here.
-}
-
-QuicEndpoint::QuicEndpoint(
- QuicState* quic_state,
- Local<Object> wrap,
- QuicSocket* listener,
- Local<Object> udp_wrap)
- : BaseObject(quic_state->env(), wrap),
- listener_(listener),
- quic_state_(quic_state) {
- MakeWeak();
- udp_ = static_cast<UDPWrapBase*>(
- udp_wrap->GetAlignedPointerFromInternalField(
- UDPWrapBase::kUDPWrapBaseField));
- CHECK_NOT_NULL(udp_);
- udp_->set_listener(this);
- strong_ptr_.reset(udp_->GetAsyncWrap());
-}
-
-QuicEndpoint::~QuicEndpoint() {
- udp_->set_listener(nullptr);
-}
-
-uv_buf_t QuicEndpoint::OnAlloc(size_t suggested_size) {
- return AllocatedBuffer::AllocateManaged(env(), suggested_size).release();
-}
-
-void QuicEndpoint::OnRecv(
- ssize_t nread,
- const uv_buf_t& buf_,
- const sockaddr* addr,
- unsigned int flags) {
- AllocatedBuffer buf(env(), buf_);
-
- if (nread <= 0) {
- if (nread < 0)
- listener_->OnError(this, nread);
- return;
- }
-
- listener_->OnReceive(
- nread,
- std::move(buf),
- local_address(),
- SocketAddress(addr),
- flags);
-}
-
-ReqWrap<uv_udp_send_t>* QuicEndpoint::CreateSendWrap(size_t msg_size) {
- return listener_->OnCreateSendWrap(msg_size);
-}
-
-void QuicEndpoint::OnSendDone(ReqWrap<uv_udp_send_t>* wrap, int status) {
- DecrementPendingCallbacks();
- listener_->OnSendDone(wrap, status);
- if (!has_pending_callbacks() && waiting_for_callbacks_)
- listener_->OnEndpointDone(this);
-}
-
-void QuicEndpoint::OnAfterBind() {
- listener_->OnBind(this);
-}
-
-template <typename Fn>
-void QuicSocketStatsTraits::ToString(const QuicSocket& ptr, Fn&& add_field) {
-#define V(_n, name, label) \
- add_field(label, ptr.GetStat(&QuicSocketStats::name));
- SOCKET_STATS(V)
-#undef V
-}
-
-QuicSocket::QuicSocket(
- QuicState* quic_state,
- Local<Object> wrap,
- uint64_t retry_token_expiration,
- size_t max_connections,
- size_t max_connections_per_host,
- size_t max_stateless_resets_per_host,
- uint32_t options,
- QlogMode qlog,
- const uint8_t* session_reset_secret,
- bool disable_stateless_reset)
- : AsyncWrap(quic_state->env(), wrap, AsyncWrap::PROVIDER_QUICSOCKET),
- StatsBase(quic_state->env(), wrap),
- alloc_info_(MakeAllocator()),
- block_list_(SocketAddressBlockListWrap::New(quic_state->env())),
- options_(options),
- state_(quic_state->env()->isolate()),
- max_connections_(max_connections),
- max_connections_per_host_(max_connections_per_host),
- max_stateless_resets_per_host_(max_stateless_resets_per_host),
- retry_token_expiration_(retry_token_expiration),
- qlog_(qlog),
- server_alpn_(NGHTTP3_ALPN_H3),
- addrLRU_(DEFAULT_MAX_SOCKETADDRESS_LRU_SIZE),
- quic_state_(quic_state) {
- MakeWeak();
- PushListener(&default_listener_);
-
- Debug(this, "New QuicSocket created");
-
- EntropySource(token_secret_, kTokenSecretLen);
-
- wrap->DefineOwnProperty(
- env()->context(),
- env()->block_list_string(),
- block_list_->object(),
- PropertyAttribute::ReadOnly).Check();
-
- wrap->DefineOwnProperty(
- env()->context(),
- env()->state_string(),
- state_.GetArrayBuffer(),
- PropertyAttribute::ReadOnly).Check();
-
- if (disable_stateless_reset)
- state_->stateless_reset_disabled = 1;
-
- // Set the session reset secret to the one provided or random.
- // Note that a random secret is going to make it exceedingly
- // difficult for the session reset token to be useful.
- if (session_reset_secret != nullptr) {
- memcpy(reset_token_secret_,
- session_reset_secret,
- NGTCP2_STATELESS_RESET_TOKENLEN);
- } else {
- EntropySource(reset_token_secret_, NGTCP2_STATELESS_RESET_TOKENLEN);
- }
-}
-
-QuicSocket::~QuicSocket() {
- QuicSocketListener* listener = listener_;
- listener_->OnDestroy();
- if (listener == listener_)
- RemoveListener(listener_);
-
- // In a clean shutdown, all QuicSessions associated with the QuicSocket
- // would have been destroyed explicitly. However, if the QuicSocket is
- // garbage collected / freed before Destroy having been called, there
- // may be sessions remaining. This is not really a good thing.
- Debug(this, "Destroying with %d sessions remaining", sessions_.size());
-
- DebugStats();
-}
-
-void QuicSocket::MemoryInfo(MemoryTracker* tracker) const {
- tracker->TrackField("endpoints", endpoints_);
- tracker->TrackField("sessions", sessions_);
- tracker->TrackField("dcid_to_scid", dcid_to_scid_);
- tracker->TrackField("address_counts", addrLRU_);
- tracker->TrackField("token_map", token_map_);
- StatsBase::StatsMemoryInfo(tracker);
- tracker->TrackFieldWithSize(
- "current_ngtcp2_memory",
- current_ngtcp2_memory_);
-}
-
-void QuicSocket::Listen(
- BaseObjectPtr<SecureContext> sc,
- const sockaddr* preferred_address,
- const std::string& alpn,
- uint32_t options) {
- CHECK(sc);
- CHECK_NE(state_->server_listening, 1);
- Debug(this, "Starting to listen");
- server_session_config_.Set(quic_state(), preferred_address);
- server_secure_context_ = sc;
- server_alpn_ = alpn;
- server_options_ = options;
- state_->server_listening = 1;
- RecordTimestamp(&QuicSocketStats::listen_at);
- ReceiveStart();
-}
-
-void QuicSocket::OnError(QuicEndpoint* endpoint, ssize_t error) {
- // TODO(@jasnell): What should we do with the endpoint?
- Debug(this, "Reading data from UDP socket failed. Error %" PRId64, error);
- listener_->OnError(error);
-}
-
-ReqWrap<uv_udp_send_t>* QuicSocket::OnCreateSendWrap(size_t msg_size) {
- HandleScope handle_scope(env()->isolate());
- Local<Object> obj;
- if (!env()->quicsocketsendwrap_instance_template()
- ->NewInstance(env()->context()).ToLocal(&obj)) return nullptr;
- return last_created_send_wrap_ = new SendWrap(quic_state(), obj, msg_size);
-}
-
-void QuicSocket::OnEndpointDone(QuicEndpoint* endpoint) {
- Debug(this, "Endpoint has no pending callbacks");
- listener_->OnEndpointDone(endpoint);
-}
-
-void QuicSocket::OnBind(QuicEndpoint* endpoint) {
- SocketAddress local_address = endpoint->local_address();
- bound_endpoints_[local_address] =
- BaseObjectWeakPtr<QuicEndpoint>(endpoint);
- Debug(this, "Endpoint %s bound", local_address);
- RecordTimestamp(&QuicSocketStats::bound_at);
-}
-
-BaseObjectPtr<QuicSession> QuicSocket::FindSession(const QuicCID& cid) {
- BaseObjectPtr<QuicSession> session;
- auto session_it = sessions_.find(cid);
- if (session_it == std::end(sessions_)) {
- auto scid_it = dcid_to_scid_.find(cid);
- if (scid_it != std::end(dcid_to_scid_)) {
- session_it = sessions_.find(scid_it->second);
- CHECK_NE(session_it, std::end(sessions_));
- session = session_it->second;
- }
- } else {
- session = session_it->second;
- }
- return session;
-}
-
-// When a received packet contains a QUIC short header but cannot be
-// matched to a known QuicSession, it is either (a) garbage,
-// (b) a valid packet for a connection we no longer have state
-// for, or (c) a stateless reset. Because we do not yet know if
-// we are going to process the packet, we need to try to quickly
-// determine -- with as little cost as possible -- whether the
-// packet contains a reset token. We do so by checking the final
-// NGTCP2_STATELESS_RESET_TOKENLEN bytes in the packet to see if
-// they match one of the known reset tokens previously given by
-// the remote peer. If there's a match, then it's a reset token,
-// if not, we move on the to the next check. It is very important
-// that this check be as inexpensive as possible to avoid a DOS
-// vector.
-bool QuicSocket::MaybeStatelessReset(
- const QuicCID& dcid,
- const QuicCID& scid,
- ssize_t nread,
- const uint8_t* data,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- unsigned int flags) {
- if (UNLIKELY(state_->stateless_reset_disabled || nread < 16))
- return false;
- StatelessResetToken possible_token(
- data + nread - NGTCP2_STATELESS_RESET_TOKENLEN);
- Debug(this, "Possible stateless reset token: %s", possible_token);
- auto it = token_map_.find(possible_token);
- if (it == token_map_.end())
- return false;
- Debug(this, "Received a stateless reset token %s", possible_token);
- return it->second->Receive(nread, data, local_addr, remote_addr, flags);
-}
-
-// When a packet is received here, we do not yet know if we can
-// process it successfully as a QUIC packet or not. Given the
-// nature of UDP, we may receive a great deal of garbage here
-// so it is extremely important not to commit resources until
-// we're certain we can process the data we received as QUIC
-// packet.
-// Any packet we choose not to process must be ignored.
-void QuicSocket::OnReceive(
- ssize_t nread,
- AllocatedBuffer buf,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- unsigned int flags) {
- Debug(this, "Receiving %d bytes from the UDP socket", nread);
-
- // When diagnostic packet loss is enabled, the packet will be randomly
- // dropped based on the rx_loss_ probability.
- if (UNLIKELY(is_diagnostic_packet_loss(rx_loss_))) {
- Debug(this, "Simulating received packet loss");
- return;
- }
-
- if (UNLIKELY(block_list_->Apply(remote_addr))) {
- Debug(this, "Ignoring blocked remote address: %s", remote_addr);
- IncrementStat(&QuicSocketStats::packets_ignored);
- return;
- }
-
- IncrementStat(&QuicSocketStats::bytes_received, nread);
-
- const uint8_t* data = reinterpret_cast<const uint8_t*>(buf.data());
-
- uint32_t pversion;
- const uint8_t* pdcid;
- size_t pdcidlen;
- const uint8_t* pscid;
- size_t pscidlen;
-
- // This is our first check to see if the received data can be
- // processed as a QUIC packet. If this fails, then the QUIC packet
- // header is invalid and cannot be processed; all we can do is ignore
- // it. It's questionable whether we should even increment the
- // packets_ignored statistic here but for now we do. If it succeeds,
- // we have a valid QUIC header but there's still no guarantee that
- // the packet can be successfully processed.
- if (ngtcp2_pkt_decode_version_cid(
- &pversion,
- &pdcid,
- &pdcidlen,
- &pscid,
- &pscidlen,
- data, nread, kScidLen) < 0) {
- IncrementStat(&QuicSocketStats::packets_ignored);
- return;
- }
-
- // QUIC currently requires CID lengths of max NGTCP2_MAX_CIDLEN. The
- // ngtcp2 API allows non-standard lengths, and we may want to allow
- // non-standard lengths later. But for now, we're going to ignore any
- // packet with a non-standard CID length.
- if (pdcidlen > NGTCP2_MAX_CIDLEN || pscidlen > NGTCP2_MAX_CIDLEN) {
- IncrementStat(&QuicSocketStats::packets_ignored);
- return;
- }
-
- QuicCID dcid(pdcid, pdcidlen);
- QuicCID scid(pscid, pscidlen);
-
- Debug(this, "Received a QUIC packet for dcid %s", dcid);
-
- BaseObjectPtr<QuicSession> session = FindSession(dcid);
-
- // If a session is not found, there are four possible reasons:
- // 1. The session has not been created yet
- // 2. The session existed once but we've lost the local state for it
- // 3. The packet is a stateless reset sent by the peer
- // 4. This is a malicious or malformed packet.
- if (!session) {
- Debug(this, "There is no existing session for dcid %s", dcid);
- bool is_short_header = IsShortHeader(pversion, pscid, pscidlen);
-
- // Handle possible reception of a stateless reset token...
- // If it is a stateless reset, the packet will be handled with
- // no additional action necessary here. We want to return immediately
- // without committing any further resources.
- if (is_short_header &&
- MaybeStatelessReset(
- dcid,
- scid,
- nread,
- data,
- local_addr,
- remote_addr,
- flags)) {
- Debug(this, "Handled stateless reset");
- return;
- }
-
- // AcceptInitialPacket will first validate that the packet can be
- // accepted, then create a new server QuicSession instance if able
- // to do so. If a new instance cannot be created (for any reason),
- // the session BaseObjectPtr will be empty on return.
- session = AcceptInitialPacket(
- pversion,
- dcid,
- scid,
- nread,
- data,
- local_addr,
- remote_addr,
- flags);
-
- // There are many reasons why a server QuicSession could not be
- // created. The most common will be invalid packets or incorrect
- // QUIC version. In any of these cases, however, to prevent a
- // potential attacker from causing us to consume resources,
- // we're just going to ignore the packet. It is possible that
- // the AcceptInitialPacket sent a version negotiation packet,
- // or a CONNECTION_CLOSE packet.
- if (!session) {
- Debug(this, "Unable to create a new server QuicSession");
- // If the packet contained a short header, we might need to send
- // a stateless reset. The stateless reset contains a token derived
- // from the received destination connection ID.
- //
- // TODO(@jasnell): Stateless resets are generated programmatically
- // using HKDF with the sender provided dcid and a locally provided
- // secret as input. It is entirely possible that a malicious
- // peer could send multiple stateless reset eliciting packets
- // with the specific intent of using the returned stateless
- // reset to guess the stateless reset token secret used by
- // the server. Once guessed, the malicious peer could use
- // that secret as a DOS vector against other peers. We currently
- // implement some mitigations for this by limiting the number
- // of stateless resets that can be sent to a specific remote
- // address but there are other possible mitigations, such as
- // including the remote address as input in the generation of
- // the stateless token.
- if (is_short_header &&
- SendStatelessReset(dcid, local_addr, remote_addr, nread)) {
- Debug(this, "Sent stateless reset");
- IncrementStat(&QuicSocketStats::stateless_reset_count);
- return;
- }
- IncrementStat(&QuicSocketStats::packets_ignored);
- return;
- }
- }
-
- CHECK(session);
- // If the QuicSession is already destroyed, there's nothing to do.
- if (session->is_destroyed())
- return IncrementStat(&QuicSocketStats::packets_ignored);
-
- // If the packet could not successfully processed for any reason (possibly
- // due to being malformed or malicious in some way) we mark it ignored.
- if (!session->Receive(nread, data, local_addr, remote_addr, flags)) {
- IncrementStat(&QuicSocketStats::packets_ignored);
- return;
- }
-
- IncrementStat(&QuicSocketStats::packets_received);
-}
-
-// Generates and sends a version negotiation packet. This is
-// terminal for the connection and is sent only when a QUIC
-// packet is received for an unsupported Node.js version.
-// It is possible that a malicious packet triggered this
-// so we need to be careful not to commit too many resources.
-// Currently, we only support one QUIC version at a time.
-void QuicSocket::SendVersionNegotiation(
- uint32_t version,
- const QuicCID& dcid,
- const QuicCID& scid,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr) {
- uint32_t sv[2];
- sv[0] = GenerateReservedVersion(remote_addr, version);
- sv[1] = NGTCP2_PROTO_VER;
-
- uint8_t unused_random;
- EntropySource(&unused_random, 1);
-
- size_t pktlen = dcid.length() + scid.length() + (sizeof(sv)) + 7;
-
- auto packet = QuicPacket::Create("version negotiation", pktlen);
- ssize_t nwrite = ngtcp2_pkt_write_version_negotiation(
- packet->data(),
- NGTCP2_MAX_PKTLEN_IPV6,
- unused_random,
- dcid.data(),
- dcid.length(),
- scid.data(),
- scid.length(),
- sv,
- arraysize(sv));
- if (nwrite <= 0)
- return;
- packet->set_length(nwrite);
- SocketAddress remote_address(remote_addr);
- SendPacket(local_addr, remote_address, std::move(packet));
-}
-
-// Possible generates and sends a stateless reset packet.
-// This is terminal for the connection. It is possible
-// that a malicious packet triggered this so we need to
-// be careful not to commit too many resources.
-bool QuicSocket::SendStatelessReset(
- const QuicCID& cid,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- size_t source_len) {
- if (UNLIKELY(state_->stateless_reset_disabled))
- return false;
- constexpr static size_t kRandlen = NGTCP2_MIN_STATELESS_RESET_RANDLEN * 5;
- constexpr static size_t kMinStatelessResetLen = 41;
- uint8_t random[kRandlen];
-
- // Per the QUIC spec, we need to protect against sending too
- // many stateless reset tokens to an endpoint to prevent
- // endless looping.
- if (GetCurrentStatelessResetCounter(remote_addr) >=
- max_stateless_resets_per_host_) {
- return false;
- }
- // Per the QUIC spec, a stateless reset token must be strictly
- // smaller than the packet that triggered it. This is one of the
- // mechanisms to prevent infinite looping exchange of stateless
- // tokens with the peer.
- // An endpoint should never send a stateless reset token smaller than
- // 41 bytes per the QUIC spec. The reason is that packets less than
- // 41 bytes may allow an observer to determine that it's a stateless
- // reset.
- size_t pktlen = source_len - 1;
- if (pktlen < kMinStatelessResetLen)
- return false;
-
- StatelessResetToken token(reset_token_secret_, cid);
- EntropySource(random, kRandlen);
-
- auto packet = QuicPacket::Create("stateless reset", pktlen);
- ssize_t nwrite =
- ngtcp2_pkt_write_stateless_reset(
- packet->data(),
- NGTCP2_MAX_PKTLEN_IPV4,
- const_cast<uint8_t*>(token.data()),
- random,
- kRandlen);
- if (nwrite < static_cast<ssize_t>(kMinStatelessResetLen))
- return false;
- packet->set_length(nwrite);
- SocketAddress remote_address(remote_addr);
- IncrementStatelessResetCounter(remote_address);
- return SendPacket(local_addr, remote_address, std::move(packet)) == 0;
-}
-
-// Generates and sends a retry packet. This is terminal
-// for the connection. Retry packets are used to force
-// explicit path validation by issuing a token to the
-// peer that it must thereafter include in all subsequent
-// initial packets. Upon receiving a retry packet, the
-// peer must termination it's initial attempt to
-// establish a connection and start a new attempt.
-//
-// Retry packets will only ever be generated by QUIC servers,
-// and only if the QuicSocket is configured for explicit path
-// validation. There is no way for a client to force a retry
-// packet to be created. However, once a client determines that
-// explicit path validation is enabled, it could attempt to
-// DOS by sending a large number of malicious initial packets
-// to intentionally ellicit retry packets (It can do so by
-// intentionally sending initial packets that ignore the retry
-// token). To help mitigate that risk, we limit the number of
-// retries we send to a given remote endpoint.
-bool QuicSocket::SendRetry(
- const QuicCID& dcid,
- const QuicCID& scid,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr) {
- auto info = addrLRU_.Upsert(remote_addr);
- // Do not send a retry if the retry count is greater
- // than the retry limit.
- // TODO(@jasnell): Make the retry limit configurable.
- if (++(info->retry_count) > DEFAULT_MAX_RETRY_LIMIT)
- return true;
- std::unique_ptr<QuicPacket> packet =
- GenerateRetryPacket(token_secret_, dcid, scid, local_addr, remote_addr);
- return packet ?
- SendPacket(local_addr, remote_addr, std::move(packet)) == 0 : false;
-}
-
-// Shutdown a connection prematurely, before a QuicSession is created.
-// This should only be called t the start of a session before the crypto
-// keys have been established.
-void QuicSocket::ImmediateConnectionClose(
- const QuicCID& scid,
- const QuicCID& dcid,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- int64_t reason) {
- Debug(this, "Sending stateless connection close to %s", scid);
- auto packet = QuicPacket::Create("immediate connection close");
- ssize_t nwrite = ngtcp2_crypto_write_connection_close(
- packet->data(),
- packet->length(),
- scid.cid(),
- dcid.cid(),
- reason);
- if (nwrite > 0) {
- packet->set_length(nwrite);
- SendPacket(local_addr, remote_addr, std::move(packet));
- }
-}
-
-// Inspects the packet and possibly accepts it as a new
-// initial packet creating a new QuicSession instance.
-// If the packet is not acceptable, it is very important
-// not to commit resources.
-BaseObjectPtr<QuicSession> QuicSocket::AcceptInitialPacket(
- uint32_t version,
- const QuicCID& dcid,
- const QuicCID& scid,
- ssize_t nread,
- const uint8_t* data,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- unsigned int flags) {
- HandleScope handle_scope(env()->isolate());
- Context::Scope context_scope(env()->context());
- ngtcp2_pkt_hd hd;
- QuicCID ocid;
-
- // If the QuicSocket is not listening, the paket will be ignored.
- if (!state_->server_listening) {
- Debug(this, "QuicSocket is not listening");
- return {};
- }
-
- switch (ngtcp2_accept(&hd, data, static_cast<size_t>(nread))) {
- case 1:
- // Send Version Negotiation
- SendVersionNegotiation(version, dcid, scid, local_addr, remote_addr);
- // Fall through
- case -1:
- // Either a version negotiation packet was sent or the packet is
- // an invalid initial packet. Either way, there's nothing more we
- // can do here.
- return {};
- }
-
- // If the server is busy, new connections will be shut down immediately
- // after the initial keys are installed. The busy state is controlled
- // entirely by local user code. It is important to understand that
- // a QuicSession is created and resources are committed even though
- // the QuicSession will be torn down as quickly as possible.
- // Else, check to see if the number of connections total for this QuicSocket
- // has been exceeded. If the count has been exceeded, shutdown the connection
- // immediately after the initial keys are installed.
- if (UNLIKELY(state_->server_busy == 1) ||
- sessions_.size() >= max_connections_ ||
- GetCurrentSocketAddressCounter(remote_addr) >=
- max_connections_per_host_) {
- Debug(this, "QuicSocket is busy or connection count exceeded");
- IncrementStat(&QuicSocketStats::server_busy_count);
- ImmediateConnectionClose(
- QuicCID(hd.scid),
- QuicCID(hd.dcid),
- local_addr,
- remote_addr,
- NGTCP2_CONNECTION_REFUSED);
- return {};
- }
-
- // QUIC has address validation built in to the handshake but allows for
- // an additional explicit validation request using RETRY frames. If we
- // are using explicit validation, we check for the existence of a valid
- // retry token in the packet. If one does not exist, we send a retry with
- // a new token. If it does exist, and if it's valid, we grab the original
- // cid and continue.
- if (!is_validated_address(remote_addr)) {
- switch (hd.type) {
- case NGTCP2_PKT_INITIAL:
- if (has_option_validate_address() || hd.token.len > 0) {
- Debug(this, "Performing explicit address validation");
- if (hd.token.len == 0) {
- Debug(this, "No retry token was detected. Generating one");
- SendRetry(dcid, scid, local_addr, remote_addr);
- // Sending a retry token terminates this connection attempt.
- return {};
- }
- if (InvalidRetryToken(
- hd.token,
- remote_addr,
- &ocid,
- token_secret_,
- retry_token_expiration_)) {
- Debug(this, "Invalid retry token was detected. Failing");
- ImmediateConnectionClose(
- QuicCID(hd.scid),
- QuicCID(hd.dcid),
- local_addr,
- remote_addr);
- return {};
- }
- }
- break;
- case NGTCP2_PKT_0RTT:
- SendRetry(dcid, scid, local_addr, remote_addr);
- return {};
- }
- }
-
- BaseObjectPtr<QuicSession> session =
- QuicSession::CreateServer(
- this,
- server_session_config_,
- local_addr,
- remote_addr,
- scid,
- dcid,
- ocid,
- version,
- server_alpn_,
- server_options_,
- qlog_);
- CHECK(session);
-
- listener_->OnSessionReady(session);
-
- // It's possible that the session was destroyed while processing
- // the ready callback. If it was, then we need to send an early
- // CONNECTION_CLOSE.
- if (session->is_destroyed()) {
- ImmediateConnectionClose(
- QuicCID(hd.scid),
- QuicCID(hd.dcid),
- local_addr,
- remote_addr,
- NGTCP2_CONNECTION_REFUSED);
- } else {
- session->set_wrapped();
- }
-
- return session;
-}
-
-QuicSocket::SendWrap::SendWrap(
- QuicState* quic_state,
- Local<Object> req_wrap_obj,
- size_t total_length)
- : ReqWrap(quic_state->env(), req_wrap_obj, PROVIDER_QUICSOCKET),
- total_length_(total_length),
- quic_state_(quic_state) {
-}
-
-std::string QuicSocket::SendWrap::MemoryInfoName() const {
- return "QuicSendWrap";
-}
-
-void QuicSocket::SendWrap::MemoryInfo(MemoryTracker* tracker) const {
- tracker->TrackField("session", session_);
- tracker->TrackField("packet", packet_);
-}
-
-int QuicSocket::SendPacket(
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- std::unique_ptr<QuicPacket> packet,
- BaseObjectPtr<QuicSession> session) {
- // If the packet is empty, there's nothing to do
- if (packet->length() == 0)
- return 0;
-
- Debug(this, "Sending %" PRIu64 " bytes to %s from %s (label: %s)",
- packet->length(),
- remote_addr,
- local_addr,
- packet->diagnostic_label());
-
- // If DiagnosticPacketLoss returns true, it will call Done() internally
- if (UNLIKELY(is_diagnostic_packet_loss(tx_loss_))) {
- Debug(this, "Simulating transmitted packet loss");
- return 0;
- }
-
- last_created_send_wrap_ = nullptr;
- uv_buf_t buf = packet->buf();
-
- auto endpoint = bound_endpoints_.find(local_addr);
- CHECK_NE(endpoint, bound_endpoints_.end());
- int err = endpoint->second->Send(&buf, 1, remote_addr.data());
-
- if (err != 0) {
- if (err > 0) err = 0;
- OnSend(err, packet.get());
- } else {
- CHECK_NOT_NULL(last_created_send_wrap_);
- last_created_send_wrap_->set_packet(std::move(packet));
- if (session)
- last_created_send_wrap_->set_session(session);
- }
- return err;
-}
-
-void QuicSocket::OnSend(int status, QuicPacket* packet) {
- if (status == 0) {
- Debug(this, "Sent %" PRIu64 " bytes (label: %s)",
- packet->length(),
- packet->diagnostic_label());
- IncrementStat(&QuicSocketStats::bytes_sent, packet->length());
- IncrementStat(&QuicSocketStats::packets_sent);
- } else {
- Debug(this, "Failed to send %" PRIu64 " bytes (status: %d, label: %s)",
- packet->length(),
- status,
- packet->diagnostic_label());
- }
-}
-
-void QuicSocket::OnSendDone(ReqWrap<uv_udp_send_t>* wrap, int status) {
- std::unique_ptr<SendWrap> req_wrap(static_cast<SendWrap*>(wrap));
- OnSend(status, req_wrap->packet());
-}
-
-void QuicSocket::CheckAllocatedSize(size_t previous_size) const {
- CHECK_GE(current_ngtcp2_memory_, previous_size);
-}
-
-void QuicSocket::IncreaseAllocatedSize(size_t size) {
- current_ngtcp2_memory_ += size;
-}
-
-void QuicSocket::DecreaseAllocatedSize(size_t size) {
- current_ngtcp2_memory_ -= size;
-}
-
-void QuicSocket::PushListener(QuicSocketListener* listener) {
- CHECK_NOT_NULL(listener);
- CHECK(!listener->socket_);
-
- listener->previous_listener_ = listener_;
- listener->socket_.reset(this);
-
- listener_ = listener;
-}
-
-void QuicSocket::RemoveListener(QuicSocketListener* listener) {
- CHECK_NOT_NULL(listener);
-
- QuicSocketListener* previous;
- QuicSocketListener* current;
-
- for (current = listener_, previous = nullptr;
- /* No loop condition because we want a crash if listener is not found */
- ; previous = current, current = current->previous_listener_) {
- CHECK_NOT_NULL(current);
- if (current == listener) {
- if (previous != nullptr)
- previous->previous_listener_ = current->previous_listener_;
- else
- listener_ = listener->previous_listener_;
- break;
- }
- }
-
- listener->socket_.reset();
- listener->previous_listener_ = nullptr;
-}
-
-bool QuicSocket::SocketAddressInfoTraits::CheckExpired(
- const SocketAddress& address,
- const Type& type) {
- return (uv_hrtime() - type.timestamp) > 1e10; // 10 seconds.
-}
-
-void QuicSocket::SocketAddressInfoTraits::Touch(
- const SocketAddress& address,
- Type* type) {
- type->timestamp = uv_hrtime();
-}
-
-// JavaScript API
-namespace {
-void NewQuicEndpoint(const FunctionCallbackInfo<Value>& args) {
- QuicState* state = Environment::GetBindingData<QuicState>(args);
- CHECK(args.IsConstructCall());
- CHECK(args[0]->IsObject());
- QuicSocket* socket;
- ASSIGN_OR_RETURN_UNWRAP(&socket, args[0].As<Object>());
- CHECK(args[1]->IsObject());
- CHECK_GE(args[1].As<Object>()->InternalFieldCount(),
- UDPWrapBase::kUDPWrapBaseField);
- new QuicEndpoint(state, args.This(), socket, args[1].As<Object>());
-}
-
-void NewQuicSocket(const FunctionCallbackInfo<Value>& args) {
- QuicState* state = Environment::GetBindingData<QuicState>(args);
- Environment* env = state->env();
- CHECK(args.IsConstructCall());
-
- uint32_t options;
- uint32_t retry_token_expiration;
- uint32_t max_connections;
- uint32_t max_connections_per_host;
- uint32_t max_stateless_resets_per_host;
-
- if (!args[0]->Uint32Value(env->context()).To(&options) ||
- !args[1]->Uint32Value(env->context()).To(&retry_token_expiration) ||
- !args[2]->Uint32Value(env->context()).To(&max_connections) ||
- !args[3]->Uint32Value(env->context()).To(&max_connections_per_host) ||
- !args[4]->Uint32Value(env->context())
- .To(&max_stateless_resets_per_host)) {
- return;
- }
- CHECK_GE(retry_token_expiration, MIN_RETRYTOKEN_EXPIRATION);
- CHECK_LE(retry_token_expiration, MAX_RETRYTOKEN_EXPIRATION);
-
- const uint8_t* session_reset_secret = nullptr;
- if (args[6]->IsArrayBufferView()) {
- ArrayBufferViewContents<uint8_t> buf(args[6].As<ArrayBufferView>());
- CHECK_EQ(buf.length(), kTokenSecretLen);
- session_reset_secret = buf.data();
- }
-
- new QuicSocket(
- state,
- args.This(),
- retry_token_expiration,
- max_connections,
- max_connections_per_host,
- max_stateless_resets_per_host,
- options,
- args[5]->IsTrue() ? QlogMode::kEnabled : QlogMode::kDisabled,
- session_reset_secret,
- args[7]->IsTrue());
-}
-
-void QuicSocketAddEndpoint(const FunctionCallbackInfo<Value>& args) {
- QuicSocket* socket;
- ASSIGN_OR_RETURN_UNWRAP(&socket, args.Holder());
- CHECK(args[0]->IsObject());
- QuicEndpoint* endpoint;
- ASSIGN_OR_RETURN_UNWRAP(&endpoint, args[0].As<Object>());
- socket->AddEndpoint(
- BaseObjectPtr<QuicEndpoint>(endpoint),
- args[1]->IsTrue());
-}
-
-// Enabling diagnostic packet loss enables a mode where the QuicSocket
-// instance will randomly ignore received packets in order to simulate
-// packet loss. This is not an API that should be enabled in production
-// but is useful when debugging and diagnosing performance issues.
-// Diagnostic packet loss is enabled by setting either the tx or rx
-// arguments to a value between 0.0 and 1.0. Setting both values to 0.0
-// disables the mechanism.
-void QuicSocketSetDiagnosticPacketLoss(
- const FunctionCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
- QuicSocket* socket;
- ASSIGN_OR_RETURN_UNWRAP(&socket, args.Holder());
- double rx, tx;
- if (!args[0]->NumberValue(env->context()).To(&rx) ||
- !args[1]->NumberValue(env->context()).To(&tx)) return;
- CHECK_GE(rx, 0.0f);
- CHECK_GE(tx, 0.0f);
- CHECK_LE(rx, 1.0f);
- CHECK_LE(tx, 1.0f);
- socket->set_diagnostic_packet_loss(rx, tx);
-}
-
-void QuicSocketDestroy(const FunctionCallbackInfo<Value>& args) {
- QuicSocket* socket;
- ASSIGN_OR_RETURN_UNWRAP(&socket, args.Holder());
- socket->ReceiveStop();
-}
-
-void QuicSocketListen(const FunctionCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
- QuicSocket* socket;
- ASSIGN_OR_RETURN_UNWRAP(&socket, args.Holder(),
- args.GetReturnValue().Set(UV_EBADF));
- CHECK(args[0]->IsObject() &&
- env->secure_context_constructor_template()->HasInstance(args[0]));
- SecureContext* sc;
- ASSIGN_OR_RETURN_UNWRAP(&sc, args[0].As<Object>(),
- args.GetReturnValue().Set(UV_EBADF));
-
- sockaddr_storage preferred_address_storage;
- const sockaddr* preferred_address = nullptr;
- if (args[1]->IsString()) {
- node::Utf8Value preferred_address_host(args.GetIsolate(), args[1]);
- int32_t preferred_address_family;
- uint32_t preferred_address_port;
- if (!args[2]->Int32Value(env->context()).To(&preferred_address_family) ||
- !args[3]->Uint32Value(env->context()).To(&preferred_address_port))
- return;
- if (SocketAddress::ToSockAddr(
- preferred_address_family,
- *preferred_address_host,
- preferred_address_port,
- &preferred_address_storage)) {
- preferred_address =
- reinterpret_cast<const sockaddr*>(&preferred_address_storage);
- }
- }
-
- std::string alpn(NGHTTP3_ALPN_H3);
- if (args[4]->IsString()) {
- Utf8Value val(env->isolate(), args[4]);
- alpn = val.length();
- alpn += *val;
- }
-
- uint32_t options = 0;
- if (!args[5]->Uint32Value(env->context()).To(&options)) return;
-
- socket->Listen(
- BaseObjectPtr<SecureContext>(sc),
- preferred_address,
- alpn,
- options);
-}
-
-void QuicEndpointWaitForPendingCallbacks(
- const FunctionCallbackInfo<Value>& args) {
- QuicEndpoint* endpoint;
- ASSIGN_OR_RETURN_UNWRAP(&endpoint, args.Holder());
- endpoint->WaitForPendingCallbacks();
-}
-
-} // namespace
-
-void QuicEndpoint::Initialize(
- Environment* env,
- Local<Object> target,
- Local<Context> context) {
- Isolate* isolate = env->isolate();
- Local<FunctionTemplate> endpoint = env->NewFunctionTemplate(NewQuicEndpoint);
- endpoint->Inherit(BaseObject::GetConstructorTemplate(env));
- endpoint->InstanceTemplate()->SetInternalFieldCount(
- QuicEndpoint::kInternalFieldCount);
- env->SetProtoMethod(endpoint,
- "waitForPendingCallbacks",
- QuicEndpointWaitForPendingCallbacks);
- endpoint->InstanceTemplate()->Set(env->owner_symbol(), Null(isolate));
-
- env->SetConstructorFunction(target, "QuicEndpoint", endpoint);
-}
-
-void QuicSocket::Initialize(
- Environment* env,
- Local<Object> target,
- Local<Context> context) {
- Isolate* isolate = env->isolate();
- Local<FunctionTemplate> socket = env->NewFunctionTemplate(NewQuicSocket);
- socket->Inherit(AsyncWrap::GetConstructorTemplate(env));
- socket->InstanceTemplate()->SetInternalFieldCount(
- QuicSocket::kInternalFieldCount);
- socket->InstanceTemplate()->Set(env->owner_symbol(), Null(isolate));
- env->SetProtoMethod(socket,
- "addEndpoint",
- QuicSocketAddEndpoint);
- env->SetProtoMethod(socket,
- "destroy",
- QuicSocketDestroy);
- env->SetProtoMethod(socket,
- "listen",
- QuicSocketListen);
- env->SetProtoMethod(socket,
- "setDiagnosticPacketLoss",
- QuicSocketSetDiagnosticPacketLoss);
- socket->Inherit(HandleWrap::GetConstructorTemplate(env));
- env->SetConstructorFunction(target, "QuicSocket", socket);
-
- Local<FunctionTemplate> sendwrap_ctor = FunctionTemplate::New(isolate);
- sendwrap_ctor->Inherit(AsyncWrap::GetConstructorTemplate(env));
- sendwrap_ctor->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "SendWrap"));
- Local<ObjectTemplate> sendwrap_template = sendwrap_ctor->InstanceTemplate();
- sendwrap_template->SetInternalFieldCount(SendWrap::kInternalFieldCount);
- env->set_quicsocketsendwrap_instance_template(sendwrap_template);
-}
-
-} // namespace quic
-} // namespace node
diff --git a/src/quic/node_quic_socket.h b/src/quic/node_quic_socket.h
deleted file mode 100644
index 63bb49091b1..00000000000
--- a/src/quic/node_quic_socket.h
+++ /dev/null
@@ -1,609 +0,0 @@
-#ifndef SRC_QUIC_NODE_QUIC_SOCKET_H_
-#define SRC_QUIC_NODE_QUIC_SOCKET_H_
-
-#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#include "aliased_struct.h"
-#include "base_object.h"
-#include "node.h"
-#include "node_crypto.h"
-#include "node_internals.h"
-#include "ngtcp2/ngtcp2.h"
-#include "nghttp3/nghttp3.h"
-#include "node_quic_state.h"
-#include "node_quic_session.h"
-#include "node_quic_util.h"
-#include "node_sockaddr.h"
-#include "env.h"
-#include "udp_wrap.h"
-#include "v8.h"
-#include "uv.h"
-
-#include <deque>
-#include <map>
-#include <string>
-#include <vector>
-
-namespace node {
-
-using v8::Context;
-using v8::Local;
-using v8::Object;
-
-namespace quic {
-
-class QuicSocket;
-class QuicEndpoint;
-
-constexpr size_t DEFAULT_MAX_SOCKETADDRESS_LRU_SIZE = 1000;
-constexpr size_t DEFAULT_MAX_RETRY_LIMIT = 10;
-
-#define QUICSOCKET_OPTIONS(V) \
- V(VALIDATE_ADDRESS, validate_address)
-
-#define V(id, _) QUICSOCKET_OPTIONS_##id,
-enum QuicSocketOptions : uint32_t {
- QUICSOCKET_OPTIONS(V)
- QUICSOCKET_OPTIONS_COUNT
-};
-#undef V
-
-#define QUICSOCKET_SHARED_STATE(V) \
- V(SERVER_LISTENING, server_listening, uint8_t) \
- V(SERVER_BUSY, server_busy, uint8_t) \
- V(STATELESS_RESET_DISABLED, stateless_reset_disabled, uint8_t)
-
-#define V(_, name, type) type name;
-struct QuicSocketState {
- QUICSOCKET_SHARED_STATE(V)
-};
-#undef V
-
-#define V(id, name, _) \
- IDX_QUICSOCKET_STATE_##id = offsetof(QuicSocketState, name),
-enum QuicSocketStateFields {
- QUICSOCKET_SHARED_STATE(V)
- IDX_QUICSOCKET_STATE_END
-};
-#undef V
-
-#define SOCKET_STATS(V) \
- V(CREATED_AT, created_at, "Created At") \
- V(BOUND_AT, bound_at, "Bound At") \
- V(LISTEN_AT, listen_at, "Listen At") \
- V(DESTROYED_AT, destroyed_at, "Destroyed At") \
- V(BYTES_RECEIVED, bytes_received, "Bytes Received") \
- V(BYTES_SENT, bytes_sent, "Bytes Sent") \
- V(PACKETS_RECEIVED, packets_received, "Packets Received") \
- V(PACKETS_IGNORED, packets_ignored, "Packets Ignored") \
- V(PACKETS_SENT, packets_sent, "Packets Sent") \
- V(SERVER_SESSIONS, server_sessions, "Server Sessions") \
- V(CLIENT_SESSIONS, client_sessions, "Client Sessions") \
- V(STATELESS_RESET_COUNT, stateless_reset_count, "Stateless Reset Count") \
- V(SERVER_BUSY_COUNT, server_busy_count, "Server Busy Count")
-
-#define V(name, _, __) IDX_QUIC_SOCKET_STATS_##name,
-enum QuicSocketStatsIdx : int {
- SOCKET_STATS(V)
- IDX_QUIC_SOCKET_STATS_COUNT
-};
-#undef V
-
-#define V(_, name, __) uint64_t name;
-struct QuicSocketStats {
- SOCKET_STATS(V)
-};
-#undef V
-
-struct QuicSocketStatsTraits {
- using Stats = QuicSocketStats;
- using Base = QuicSocket;
-
- template <typename Fn>
- static void ToString(const Base& ptr, Fn&& add_field);
-};
-
-// This is the generic interface for objects that control QuicSocket
-// instances. The default `JSQuicSocketListener` emits events to
-// JavaScript
-class QuicSocketListener {
- public:
- virtual ~QuicSocketListener();
-
- virtual void OnError(ssize_t code);
- virtual void OnSessionReady(BaseObjectPtr<QuicSession> session);
- virtual void OnServerBusy();
- virtual void OnEndpointDone(QuicEndpoint* endpoint);
- virtual void OnDestroy();
-
- QuicSocket* socket() const { return socket_.get(); }
-
- private:
- BaseObjectWeakPtr<QuicSocket> socket_;
- QuicSocketListener* previous_listener_ = nullptr;
- friend class QuicSocket;
-};
-
-class JSQuicSocketListener final : public QuicSocketListener {
- public:
- void OnError(ssize_t code) override;
- void OnSessionReady(BaseObjectPtr<QuicSession> session) override;
- void OnServerBusy() override;
- void OnEndpointDone(QuicEndpoint* endpoint) override;
- void OnDestroy() override;
-};
-
-// This is just a formality as the QUIC spec normatively
-// defines that the ipv4 max pktlen is always going to be
-// larger than the ipv6 max pktlen, but in the off chance
-// ever changes (which is unlikely) we check here.
-constexpr size_t MAX_PKTLEN =
- std::max<size_t>(NGTCP2_MAX_PKTLEN_IPV4, NGTCP2_MAX_PKTLEN_IPV6);
-
-// A serialized QuicPacket to be sent by a QuicSocket instance.
-// QuicPackets are intended to be transient. They are created,
-// filled with the contents of a serialized packet, and passed
-// off immediately to the QuicSocket to be sent. As soon as
-// the packet is sent, it is freed.
-class QuicPacket : public MemoryRetainer {
- public:
- // Creates a new QuicPacket. By default the packet will be
- // stack allocated with a max size of NGTCP2_MAX_PKTLEN_IPV4.
- // If a larger packet size is specified, it will be heap
- // allocated. A QUIC packet should never be larger than the
- // current MTU to avoid IP fragmentation.
- //
- // The content of a QuicPacket is provided by ngtcp2 and is
- // opaque for us. The typical use pattern is to create a QuicPacket
- // instance and then pass a pointer to it's internal buffer and max
- // size in to an ngtcp2 function that serializes the data.
- // ngtcp2 will fill the buffer as much as possible then return
- // the number of bytes serialized. User code is then responsible
- // for calling set_length() to set the final length of the
- // QuicPacket prior to sending it off to the QuicSocket.
- //
- // The diagnostic label is used in NODE_DEBUG_NATIVE output
- // to differentiate send operations. This should always be
- // a statically allocated string or nullptr (in which case
- // the value "unspecified" is used in the debug output).
- //
- // Instances of std::unique_ptr<QuicPacket> are moved through
- // QuicSocket and ultimately become the responsibility of the
- // SendWrap instance. When the SendWrap is cleaned up, the
- // QuicPacket instance will be freed.
- static inline std::unique_ptr<QuicPacket> Create(
- const char* diagnostic_label = nullptr,
- size_t len = MAX_PKTLEN);
-
- // Copy the data of the QuicPacket to a new one. Currently,
- // this is only used when retransmitting close connection
- // packets from a QuicServer.
- static inline std::unique_ptr<QuicPacket> Copy(
- const std::unique_ptr<QuicPacket>& other);
-
- QuicPacket(const char* diagnostic_label, size_t len);
- QuicPacket(const QuicPacket& other);
-
- uint8_t* data() { return data_; }
-
- size_t length() const { return len_; }
-
- uv_buf_t buf() const {
- return uv_buf_init(
- const_cast<char*>(reinterpret_cast<const char*>(&data_)),
- length());
- }
-
- inline void set_length(size_t len);
-
- const char* diagnostic_label() const;
-
- SET_NO_MEMORY_INFO();
- SET_MEMORY_INFO_NAME(QuicPacket);
- SET_SELF_SIZE(QuicPacket);
-
- private:
- uint8_t data_[MAX_PKTLEN];
- size_t len_ = MAX_PKTLEN;
- const char* diagnostic_label_ = nullptr;
-};
-
-// QuicEndpointListener listens to events generated by a QuicEndpoint.
-class QuicEndpointListener {
- public:
- virtual void OnError(QuicEndpoint* endpoint, ssize_t error) = 0;
- virtual void OnReceive(
- ssize_t nread,
- AllocatedBuffer buf,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- unsigned int flags) = 0;
- virtual ReqWrap<uv_udp_send_t>* OnCreateSendWrap(size_t msg_size) = 0;
- virtual void OnSendDone(ReqWrap<uv_udp_send_t>* wrap, int status) = 0;
- virtual void OnBind(QuicEndpoint* endpoint) = 0;
- virtual void OnEndpointDone(QuicEndpoint* endpoint) = 0;
-};
-
-// A QuicEndpoint wraps a UDPBaseWrap. A single QuicSocket may
-// have multiple QuicEndpoints, the lifecycles of which are
-// attached to the QuicSocket.
-class QuicEndpoint final : public BaseObject,
- public UDPListener {
- public:
- static void Initialize(
- Environment* env,
- Local<Object> target,
- Local<Context> context);
-
- QuicEndpoint(
- QuicState* quic_state,
- Local<Object> wrap,
- QuicSocket* listener,
- Local<Object> udp_wrap);
-
- ~QuicEndpoint() override;
-
- SocketAddress local_address() const {
- return udp_->GetSockName();
- }
-
- // Implementation for UDPListener
- uv_buf_t OnAlloc(size_t suggested_size) override;
-
- void OnRecv(ssize_t nread,
- const uv_buf_t& buf,
- const sockaddr* addr,
- unsigned int flags) override;
-
- ReqWrap<uv_udp_send_t>* CreateSendWrap(size_t msg_size) override;
-
- void OnSendDone(ReqWrap<uv_udp_send_t>* wrap, int status) override;
-
- void OnAfterBind() override;
-
- inline int ReceiveStart();
-
- inline int ReceiveStop();
-
- inline int Send(
- uv_buf_t* buf,
- size_t len,
- const sockaddr* addr);
-
- void IncrementPendingCallbacks() { pending_callbacks_++; }
- void DecrementPendingCallbacks() { pending_callbacks_--; }
- bool has_pending_callbacks() const { return pending_callbacks_ > 0; }
- inline void WaitForPendingCallbacks();
-
- QuicState* quic_state() const { return quic_state_.get(); }
-
- SET_NO_MEMORY_INFO();
- SET_MEMORY_INFO_NAME(QuicEndpoint)
- SET_SELF_SIZE(QuicEndpoint)
-
- private:
- BaseObjectWeakPtr<QuicSocket> listener_;
- UDPWrapBase* udp_;
- BaseObjectPtr<AsyncWrap> strong_ptr_;
- size_t pending_callbacks_ = 0;
- bool waiting_for_callbacks_ = false;
- BaseObjectPtr<QuicState> quic_state_;
-};
-
-// QuicSocket manages the flow of data from the UDP socket to the
-// QuicSession. It is responsible for managing the lifecycle of the
-// UDP sockets, listening for new server QuicSession instances, and
-// passing data two and from the remote peer.
-class QuicSocket : public AsyncWrap,
- public QuicEndpointListener,
- public mem::NgLibMemoryManager<QuicSocket, ngtcp2_mem>,
- public StatsBase<QuicSocketStatsTraits> {
- public:
- static void Initialize(
- Environment* env,
- Local<Object> target,
- Local<Context> context);
-
- QuicSocket(
- QuicState* quic_state,
- Local<Object> wrap,
- // A retry token should only be valid for a small window of time.
- // The retry_token_expiration specifies the number of seconds a
- // retry token is permitted to be valid.
- uint64_t retry_token_expiration,
- // To prevent malicious clients from opening too many concurrent
- // connections, we limit the maximum number per remote sockaddr.
- size_t max_connections,
- size_t max_connections_per_host,
- size_t max_stateless_resets_per_host
- = DEFAULT_MAX_STATELESS_RESETS_PER_HOST,
- uint32_t options = 0,
- QlogMode qlog = QlogMode::kDisabled,
- const uint8_t* session_reset_secret = nullptr,
- bool disable_session_reset = false);
-
- ~QuicSocket() override;
-
- // Returns the default/preferred local address. Additional
- // QuicEndpoint instances may be associated with the
- // QuicSocket bound to other local addresses.
- inline SocketAddress local_address() const;
-
- void MaybeClose();
-
- inline void AddSession(
- const QuicCID& cid,
- BaseObjectPtr<QuicSession> session);
-
- inline void AssociateCID(
- const QuicCID& cid,
- const QuicCID& scid);
-
- inline void DisassociateCID(
- const QuicCID& cid);
-
- inline void AssociateStatelessResetToken(
- const StatelessResetToken& token,
- BaseObjectPtr<QuicSession> session);
-
- inline void DisassociateStatelessResetToken(
- const StatelessResetToken& token);
-
- void Listen(
- BaseObjectPtr<crypto::SecureContext> context,
- const sockaddr* preferred_address = nullptr,
- const std::string& alpn = NGHTTP3_ALPN_H3,
- uint32_t options = 0);
-
- inline void ReceiveStart();
-
- inline void ReceiveStop();
-
- inline void RemoveSession(
- const QuicCID& cid,
- const SocketAddress& addr);
-
- inline void ReportSendError(int error);
-
- int SendPacket(
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- std::unique_ptr<QuicPacket> packet,
- BaseObjectPtr<QuicSession> session = BaseObjectPtr<QuicSession>());
-
-#define V(id, name) \
- bool has_option_##name() const { \
- return options_ & (1 << QUICSOCKET_OPTIONS_##id); }
- QUICSOCKET_OPTIONS(V)
-#undef V
-
- // Allows the server busy status to be enabled from C++. A notification
- // will be sent to the JavaScript side informing that the status has
- // changed.
- inline void ServerBusy(bool on);
-
- inline void set_diagnostic_packet_loss(double rx = 0.0, double tx = 0.0);
-
- BaseObjectPtr<crypto::SecureContext> server_secure_context() const {
- return server_secure_context_;
- }
-
- QuicState* quic_state() { return quic_state_.get(); }
-
- void MemoryInfo(MemoryTracker* tracker) const override;
- SET_MEMORY_INFO_NAME(QuicSocket)
- SET_SELF_SIZE(QuicSocket)
-
- // Implementation for mem::NgLibMemoryManager
- void CheckAllocatedSize(size_t previous_size) const;
-
- void IncreaseAllocatedSize(size_t size);
-
- void DecreaseAllocatedSize(size_t size);
-
- const uint8_t* session_reset_secret() { return reset_token_secret_; }
-
- // Implementation for QuicListener
- ReqWrap<uv_udp_send_t>* OnCreateSendWrap(size_t msg_size) override;
-
- // Implementation for QuicListener
- void OnSendDone(ReqWrap<uv_udp_send_t>* wrap, int status) override;
-
- // Implementation for QuicListener
- void OnBind(QuicEndpoint* endpoint) override;
-
- // Implementation for QuicListener
- void OnReceive(
- ssize_t nread,
- AllocatedBuffer buf,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- unsigned int flags) override;
-
- // Implementation for QuicListener
- void OnError(QuicEndpoint* endpoint, ssize_t error) override;
-
- // Implementation for QuicListener
- void OnEndpointDone(QuicEndpoint* endpoint) override;
-
- // Serializes and transmits a RETRY packet to the connected peer.
- bool SendRetry(
- const QuicCID& dcid,
- const QuicCID& scid,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr);
-
- // Serializes and transmits a Stateless Reset to the connected peer.
- bool SendStatelessReset(
- const QuicCID& cid,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- size_t source_len);
-
- // Serializes and transmits a Version Negotiation packet to the
- // connected peer.
- void SendVersionNegotiation(
- uint32_t version,
- const QuicCID& dcid,
- const QuicCID& scid,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr);
-
- void PushListener(QuicSocketListener* listener);
-
- void RemoveListener(QuicSocketListener* listener);
-
- inline void AddEndpoint(
- BaseObjectPtr<QuicEndpoint> endpoint,
- bool preferred = false);
-
- void ImmediateConnectionClose(
- const QuicCID& scid,
- const QuicCID& dcid,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- int64_t reason = NGTCP2_INVALID_TOKEN);
-
- private:
- static void OnAlloc(
- uv_handle_t* handle,
- size_t suggested_size,
- uv_buf_t* buf);
-
- void OnSend(int status, QuicPacket* packet);
-
- inline void set_validated_address(const SocketAddress& addr);
-
- inline bool is_validated_address(const SocketAddress& addr) const;
-
- bool MaybeStatelessReset(
- const QuicCID& dcid,
- const QuicCID& scid,
- ssize_t nread,
- const uint8_t* data,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- unsigned int flags);
-
- BaseObjectPtr<QuicSession> AcceptInitialPacket(
- uint32_t version,
- const QuicCID& dcid,
- const QuicCID& scid,
- ssize_t nread,
- const uint8_t* data,
- const SocketAddress& local_addr,
- const SocketAddress& remote_addr,
- unsigned int flags);
-
- BaseObjectPtr<QuicSession> FindSession(const QuicCID& cid);
-
- inline void IncrementSocketAddressCounter(const SocketAddress& addr);
-
- inline void DecrementSocketAddressCounter(const SocketAddress& addr);
-
- inline void IncrementStatelessResetCounter(const SocketAddress& addr);
-
- inline size_t GetCurrentSocketAddressCounter(const SocketAddress& addr);
-
- inline size_t GetCurrentStatelessResetCounter(const SocketAddress& addr);
-
- // Returns true if, and only if, diagnostic packet loss is enabled
- // and the current packet should be artificially considered lost.
- inline bool is_diagnostic_packet_loss(double prob) const;
-
- ngtcp2_mem alloc_info_;
-
- std::vector<BaseObjectPtr<QuicEndpoint>> endpoints_;
- SocketAddress::Map<BaseObjectWeakPtr<QuicEndpoint>> bound_endpoints_;
- BaseObjectWeakPtr<QuicEndpoint> preferred_endpoint_;
- BaseObjectPtr<SocketAddressBlockListWrap> block_list_;
-
- uint32_t flags_ = 0;
- uint32_t options_ = 0;
- uint32_t server_options_;
-
- AliasedStruct<QuicSocketState> state_;
-
- size_t max_connections_ = DEFAULT_MAX_CONNECTIONS;
- size_t max_connections_per_host_ = DEFAULT_MAX_CONNECTIONS_PER_HOST;
- size_t current_ngtcp2_memory_ = 0;
- size_t max_stateless_resets_per_host_ = DEFAULT_MAX_STATELESS_RESETS_PER_HOST;
-
- uint64_t retry_token_expiration_;
-
- // Used to specify diagnostic packet loss probabilities
- double rx_loss_ = 0.0;
- double tx_loss_ = 0.0;
-
- QuicSocketListener* listener_;
- JSQuicSocketListener default_listener_;
- QuicSessionConfig server_session_config_;
- QlogMode qlog_ = QlogMode::kDisabled;
- BaseObjectPtr<crypto::SecureContext> server_secure_context_;
- std::string server_alpn_;
- QuicCID::Map<BaseObjectPtr<QuicSession>> sessions_;
- QuicCID::Map<QuicCID> dcid_to_scid_;
-
- uint8_t token_secret_[kTokenSecretLen];
- uint8_t reset_token_secret_[NGTCP2_STATELESS_RESET_TOKENLEN];
-
- struct SocketAddressInfo {
- size_t active_connections;
- size_t reset_count;
- size_t retry_count;
- bool validated;
- uint64_t timestamp;
- };
-
- struct SocketAddressInfoTraits {
- using Type = SocketAddressInfo;
-
- static bool CheckExpired(const SocketAddress& address, const Type& type);
- static void Touch(const SocketAddress& address, Type* type);
- };
-
- SocketAddressLRU<SocketAddressInfoTraits> addrLRU_;
-
- StatelessResetToken::Map<QuicSession> token_map_;
-
- class SendWrap : public ReqWrap<uv_udp_send_t> {
- public:
- SendWrap(QuicState* quic_state,
- v8::Local<v8::Object> req_wrap_obj,
- size_t total_length_);
-
- void set_packet(std::unique_ptr<QuicPacket> packet) {
- packet_ = std::move(packet);
- }
-
- QuicPacket* packet() { return packet_.get(); }
-
- void set_session(BaseObjectPtr<QuicSession> session) { session_ = session; }
-
- size_t total_length() const { return total_length_; }
-
- QuicState* quic_state() { return quic_state_.get(); }
-
- SET_SELF_SIZE(SendWrap);
- std::string MemoryInfoName() const override;
- void MemoryInfo(MemoryTracker* tracker) const override;
-
- private:
- BaseObjectPtr<QuicSession> session_;
- std::unique_ptr<QuicPacket> packet_;
- size_t total_length_;
- BaseObjectPtr<QuicState> quic_state_;
- };
-
- SendWrap* last_created_send_wrap_ = nullptr;
- BaseObjectPtr<QuicState> quic_state_;
-
- friend class QuicSocketListener;
-};
-
-} // namespace quic
-} // namespace node
-
-#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#endif // SRC_QUIC_NODE_QUIC_SOCKET_H_
diff --git a/src/quic/node_quic_state.h b/src/quic/node_quic_state.h
deleted file mode 100644
index 6eb76529f89..00000000000
--- a/src/quic/node_quic_state.h
+++ /dev/null
@@ -1,82 +0,0 @@
-#ifndef SRC_QUIC_NODE_QUIC_STATE_H_
-#define SRC_QUIC_NODE_QUIC_STATE_H_
-
-#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#include "aliased_buffer.h"
-
-namespace node {
-namespace quic {
-
-enum QuicSessionConfigIndex : int {
- IDX_QUIC_SESSION_ACTIVE_CONNECTION_ID_LIMIT,
- IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_LOCAL,
- IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_REMOTE,
- IDX_QUIC_SESSION_MAX_STREAM_DATA_UNI,
- IDX_QUIC_SESSION_MAX_DATA,
- IDX_QUIC_SESSION_MAX_STREAMS_BIDI,
- IDX_QUIC_SESSION_MAX_STREAMS_UNI,
- IDX_QUIC_SESSION_MAX_IDLE_TIMEOUT,
- IDX_QUIC_SESSION_MAX_UDP_PAYLOAD_SIZE,
- IDX_QUIC_SESSION_ACK_DELAY_EXPONENT,
- IDX_QUIC_SESSION_DISABLE_MIGRATION,
- IDX_QUIC_SESSION_MAX_ACK_DELAY,
- IDX_QUIC_SESSION_CC_ALGO,
- IDX_QUIC_SESSION_CONFIG_COUNT
-};
-
-enum Http3ConfigIndex : int {
- IDX_HTTP3_QPACK_MAX_TABLE_CAPACITY,
- IDX_HTTP3_QPACK_BLOCKED_STREAMS,
- IDX_HTTP3_MAX_HEADER_LIST_SIZE,
- IDX_HTTP3_MAX_PUSHES,
- IDX_HTTP3_MAX_HEADER_PAIRS,
- IDX_HTTP3_MAX_HEADER_LENGTH,
- IDX_HTTP3_CONFIG_COUNT
-};
-
-class QuicState : public BaseObject {
- public:
- explicit QuicState(Environment* env, v8::Local<v8::Object> obj)
- : BaseObject(env, obj),
- root_buffer(
- env->isolate(),
- sizeof(quic_state_internal)),
- quicsessionconfig_buffer(
- env->isolate(),
- offsetof(quic_state_internal, quicsessionconfig_buffer),
- IDX_QUIC_SESSION_CONFIG_COUNT + 1,
- root_buffer),
- http3config_buffer(
- env->isolate(),
- offsetof(quic_state_internal, http3config_buffer),
- IDX_HTTP3_CONFIG_COUNT + 1,
- root_buffer) {
- }
-
- AliasedUint8Array root_buffer;
- AliasedFloat64Array quicsessionconfig_buffer;
- AliasedFloat64Array http3config_buffer;
-
- bool warn_trace_tls = true;
-
- static constexpr FastStringKey binding_data_name { "quic" };
-
- void MemoryInfo(MemoryTracker* tracker) const override;
- SET_SELF_SIZE(QuicState)
- SET_MEMORY_INFO_NAME(QuicState)
-
- private:
- struct quic_state_internal {
- // doubles first so that they are always sizeof(double)-aligned
- double quicsessionconfig_buffer[IDX_QUIC_SESSION_CONFIG_COUNT + 1];
- double http3config_buffer[IDX_HTTP3_CONFIG_COUNT + 1];
- };
-};
-
-} // namespace quic
-} // namespace node
-
-#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#endif // SRC_QUIC_NODE_QUIC_STATE_H_
diff --git a/src/quic/node_quic_stream-inl.h b/src/quic/node_quic_stream-inl.h
deleted file mode 100644
index e79a326359c..00000000000
--- a/src/quic/node_quic_stream-inl.h
+++ /dev/null
@@ -1,180 +0,0 @@
-#ifndef SRC_QUIC_NODE_QUIC_STREAM_INL_H_
-#define SRC_QUIC_NODE_QUIC_STREAM_INL_H_
-
-#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#include "debug_utils-inl.h"
-#include "node_quic_session.h"
-#include "node_quic_stream.h"
-#include "node_quic_buffer-inl.h"
-
-namespace node {
-namespace quic {
-
-QuicStreamDirection QuicStream::direction() const {
- return stream_id_ & 0b10 ?
- QUIC_STREAM_UNIDIRECTIONAL :
- QUIC_STREAM_BIRECTIONAL;
-}
-
-QuicStreamOrigin QuicStream::origin() const {
- return stream_id_ & 0b01 ?
- QUIC_STREAM_SERVER :
- QUIC_STREAM_CLIENT;
-}
-
-void QuicStream::set_final_size(uint64_t final_size) {
- // Only set the final size once.
- if (state_->fin_received == 1) {
- CHECK_LE(final_size, GetStat(&QuicStreamStats::final_size));
- return;
- }
- state_->fin_received = 1;
- SetStat(&QuicStreamStats::final_size, final_size);
-}
-
-bool QuicStream::was_ever_writable() const {
- if (direction() == QUIC_STREAM_UNIDIRECTIONAL) {
- return session_->is_server() ?
- origin() == QUIC_STREAM_SERVER :
- origin() == QUIC_STREAM_CLIENT;
- }
- return true;
-}
-
-bool QuicStream::is_writable() const {
- return was_ever_writable() && !streambuf_.is_ended();
-}
-
-bool QuicStream::was_ever_readable() const {
- if (direction() == QUIC_STREAM_UNIDIRECTIONAL) {
- return session_->is_server() ?
- origin() == QUIC_STREAM_CLIENT :
- origin() == QUIC_STREAM_SERVER;
- }
-
- return true;
-}
-
-void QuicStream::set_fin_sent() {
- Debug(this, "final stream frame sent");
- state_->fin_sent = 1;
- if (shutdown_done_ != nullptr) {
- shutdown_done_(0);
- }
-}
-
-void QuicStream::set_destroyed() {
- destroyed_ = true;
-}
-
-bool QuicStream::is_readable() const {
- return was_ever_readable() && state_->read_ended == 0;
-}
-
-bool QuicStream::is_write_finished() const {
- return state_->fin_sent == 1 && streambuf_.length() == 0;
-}
-
-bool QuicStream::SubmitInformation(v8::Local<v8::Array> headers) {
- return session_->SubmitInformation(stream_id_, headers);
-}
-
-bool QuicStream::SubmitHeaders(v8::Local<v8::Array> headers, uint32_t flags) {
- return session_->SubmitHeaders(stream_id_, headers, flags);
-}
-
-bool QuicStream::SubmitTrailers(v8::Local<v8::Array> headers) {
- return session_->SubmitTrailers(stream_id_, headers);
-}
-
-BaseObjectPtr<QuicStream> QuicStream::SubmitPush(
- v8::Local<v8::Array> headers) {
- return session_->SubmitPush(stream_id_, headers);
-}
-
-void QuicStream::EndHeaders(int64_t push_id) {
- Debug(this, "End Headers");
- // Upon completion of a block of headers, convert the
- // vector of Header objects into an array of name+value
- // pairs, then call the on_stream_headers function.
- session()->application()->StreamHeaders(
- stream_id_,
- headers_kind_,
- headers_,
- push_id);
- headers_.clear();
-}
-
-void QuicStream::set_headers_kind(QuicStreamHeadersKind kind) {
- headers_kind_ = kind;
-}
-
-void QuicStream::BeginHeaders(QuicStreamHeadersKind kind) {
- Debug(this, "Beginning Headers");
- // Upon start of a new block of headers, ensure that any
- // previously collected ones are cleaned up.
- headers_.clear();
- set_headers_kind(kind);
-}
-
-void QuicStream::Commit(size_t amount) {
- CHECK(!is_destroyed());
- streambuf_.Seek(amount);
-}
-
-// ResetStream will cause ngtcp2 to queue a RESET_STREAM and STOP_SENDING
-// frame, as appropriate, for the given stream_id. For a locally-initiated
-// unidirectional stream, only a RESET_STREAM frame will be scheduled and
-// the stream will be immediately closed. For a bidirectional stream, a
-// STOP_SENDING frame will be sent.
-void QuicStream::ResetStream(uint64_t app_error_code) {
- QuicSession::SendSessionScope send_scope(session());
- session()->ShutdownStream(id(), app_error_code);
- state_->read_ended = 1;
- streambuf_.Cancel();
- streambuf_.End();
-}
-
-// StopSending will cause ngtcp2 to queue a STOP_SENDING frame if the
-// stream is still inbound readable.
-void QuicStream::StopSending(uint64_t app_error_code) {
- QuicSession::SendSessionScope send_scope(session());
- ngtcp2_conn_shutdown_stream_read(
- session()->connection(),
- stream_id_,
- app_error_code);
- state_->read_ended = 1;
-}
-
-void QuicStream::CancelPendingWrites() {
- // In case this stream is scheduled for sending data, remove it
- // from the schedule queue
- Unschedule();
-
- // If there is data currently buffered in the streambuf_,
- // then cancel will call out to invoke an arbitrary
- // JavaScript callback (the on write callback). Within
- // that callback, however, the QuicStream will no longer
- // be usable to send or receive data.
- streambuf_.End();
- streambuf_.Cancel();
- CHECK_EQ(streambuf_.length(), 0);
-}
-
-void QuicStream::Schedule(Queue* queue) {
- if (!stream_queue_.IsEmpty()) // Already scheduled?
- return;
- queue->PushBack(this);
-}
-
-void QuicStream::Unschedule() {
- stream_queue_.Remove();
-}
-
-} // namespace quic
-} // namespace node
-
-#endif // NODE_WANT_INTERNALS
-
-#endif // SRC_QUIC_NODE_QUIC_STREAM_INL_H_
diff --git a/src/quic/node_quic_stream.cc b/src/quic/node_quic_stream.cc
deleted file mode 100644
index 57976ae50d9..00000000000
--- a/src/quic/node_quic_stream.cc
+++ /dev/null
@@ -1,548 +0,0 @@
-#include "node_quic_stream-inl.h" // NOLINT(build/include)
-#include "aliased_struct-inl.h"
-#include "async_wrap-inl.h"
-#include "debug_utils-inl.h"
-#include "env-inl.h"
-#include "node.h"
-#include "node_buffer.h"
-#include "node_internals.h"
-#include "stream_base-inl.h"
-#include "node_sockaddr-inl.h"
-#include "node_http_common-inl.h"
-#include "node_quic_session-inl.h"
-#include "node_quic_socket-inl.h"
-#include "node_quic_util-inl.h"
-#include "v8.h"
-#include "uv.h"
-
-#include <algorithm>
-#include <memory>
-#include <string>
-#include <utility>
-
-namespace node {
-
-using v8::Array;
-using v8::Context;
-using v8::FunctionCallbackInfo;
-using v8::FunctionTemplate;
-using v8::Local;
-using v8::Object;
-using v8::ObjectTemplate;
-using v8::PropertyAttribute;
-using v8::Value;
-
-namespace quic {
-
-QuicStream::QuicStream(
- QuicSession* sess,
- Local<Object> wrap,
- int64_t stream_id,
- int64_t push_id)
- : AsyncWrap(sess->env(), wrap, AsyncWrap::PROVIDER_QUICSTREAM),
- StreamBase(sess->env()),
- StatsBase(sess->env(), wrap,
- HistogramOptions::ACK |
- HistogramOptions::RATE |
- HistogramOptions::SIZE),
- session_(sess),
- stream_id_(stream_id),
- push_id_(push_id),
- state_(sess->env()->isolate()),
- quic_state_(sess->quic_state()) {
- CHECK_NOT_NULL(sess);
- Debug(this, "Created");
- StreamBase::AttachToObject(GetObject());
-
- wrap->DefineOwnProperty(
- env()->context(),
- env()->state_string(),
- state_.GetArrayBuffer(),
- PropertyAttribute::ReadOnly).Check();
-
- ngtcp2_transport_params params;
- ngtcp2_conn_get_local_transport_params(session()->connection(), &params);
- IncrementStat(&QuicStreamStats::max_offset, params.initial_max_data);
-}
-
-QuicStream::~QuicStream() {
- DebugStats();
-}
-
-template <typename Fn>
-void QuicStreamStatsTraits::ToString(const QuicStream& ptr, Fn&& add_field) {
-#define V(_n, name, label) \
- add_field(label, ptr.GetStat(&QuicStreamStats::name));
- STREAM_STATS(V)
-#undef V
-}
-
-// Acknowledge is called when ngtcp2 has received an acknowledgement
-// for one or more stream frames for this QuicStream. This will cause
-// data stored in the streambuf_ outbound queue to be consumed and may
-// result in the JavaScript callback for the write to be invoked.
-void QuicStream::Acknowledge(uint64_t offset, size_t datalen) {
- if (is_destroyed())
- return;
-
- // ngtcp2 guarantees that offset must always be greater
- // than the previously received offset, but let's just
- // make sure that holds.
- CHECK_GE(offset, GetStat(&QuicStreamStats::max_offset_ack));
- SetStat(&QuicStreamStats::max_offset_ack, offset);
-
- Debug(this, "Acknowledging %d bytes", datalen);
-
- // Consumes the given number of bytes in the buffer. This may
- // have the side-effect of causing the onwrite callback to be
- // invoked if a complete chunk of buffered data has been acknowledged.
- streambuf_.Consume(datalen);
-
- RecordAck(&QuicStreamStats::acked_at);
-}
-
-// While not all QUIC applications will support headers, QuicStream
-// includes basic, generic support for storing them.
-bool QuicStream::AddHeader(std::unique_ptr<QuicHeader> header) {
- size_t len = header->length();
- QuicApplication* app = session()->application();
- // We cannot add the header if we've either reached
- // * the max number of header pairs or
- // * the max number of header bytes
- if (headers_.size() == app->max_header_pairs() ||
- current_headers_length_ + len > app->max_header_length()) {
- return false;
- }
-
- current_headers_length_ += header->length();
- Debug(this, "Header - %s", header.get());
- headers_.emplace_back(std::move(header));
- return true;
-}
-
-std::string QuicStream::diagnostic_name() const {
- return std::string("QuicStream ") + std::to_string(stream_id_) +
- " (" + std::to_string(static_cast<int64_t>(get_async_id())) +
- ", " + session_->diagnostic_name() + ")";
-}
-
-void QuicStream::Destroy(QuicError* error) {
- if (destroyed_)
- return;
- destroyed_ = true;
-
- if (is_writable() || is_readable())
- session()->ShutdownStream(id(), 0);
-
- CancelPendingWrites();
-
- session_->RemoveStream(stream_id_);
-}
-
-// Do shutdown is called when the JS stream writable side is closed.
-// If we're not within an ngtcp2 callback, this will trigger the
-// QuicSession to send any pending data. If a final stream frame
-// has not already been sent, it will be after this.
-int QuicStream::DoShutdown(ShutdownWrap* req_wrap) {
- if (is_destroyed())
- return UV_EPIPE;
-
- // If the fin bit has already been sent, we can return
- // immediately because there's nothing else to do. The
- // _final callback will be invoked immediately.
- if (state_->fin_sent || !is_writable()) {
- Debug(this, "Shutdown write immediately");
- return 1;
- }
- Debug(this, "Deferred shutdown. Waiting for fin sent");
-
- CHECK_NULL(shutdown_done_);
- CHECK_NOT_NULL(req_wrap);
- shutdown_done_ = [=](int status) {
- CHECK_NOT_NULL(req_wrap);
- shutdown_done_ = nullptr;
- req_wrap->Done(status);
- };
-
- QuicSession::SendSessionScope send_scope(session());
-
- Debug(this, "Shutdown writable side");
- RecordTimestamp(&QuicStreamStats::closing_at);
- state_->write_ended = 1;
- streambuf_.End();
- session()->ResumeStream(stream_id_);
-
- return 0;
-}
-
-int QuicStream::DoWrite(
- WriteWrap* req_wrap,
- uv_buf_t* bufs,
- size_t nbufs,
- uv_stream_t* send_handle) {
- CHECK_NULL(send_handle);
- CHECK(!streambuf_.is_ended());
-
- // A write should not have happened if we've been destroyed or
- // the QuicStream is no longer (or was never) writable.
- if (is_destroyed() || !is_writable()) {
- req_wrap->Done(UV_EPIPE);
- return 0;
- }
-
- // Nothing to write.
- size_t length = get_length(bufs, nbufs);
- if (length == 0) {
- req_wrap->Done(0);
- return 0;
- }
-
- QuicSession::SendSessionScope send_scope(session());
-
- Debug(this, "Queuing %" PRIu64 " bytes of data from %d buffers",
- length, nbufs);
- IncrementStat(&QuicStreamStats::bytes_sent, static_cast<uint64_t>(length));
-
- BaseObjectPtr<AsyncWrap> strong_ref{req_wrap->GetAsyncWrap()};
- // The list of buffers will be appended onto streambuf_ without
- // copying. Those will remain in the buffer until the serialized
- // stream frames are acknowledged.
- // This callback function will be invoked once this
- // complete batch of buffers has been acknowledged
- // by the peer. This will have the side effect of
- // blocking additional pending writes from the
- // javascript side, so writing data to the stream
- // will be throttled by how quickly the peer is
- // able to acknowledge stream packets. This is good
- // in the sense of providing back-pressure, but
- // also means that writes will be significantly
- // less performant unless written in batches.
- streambuf_.Push(
- bufs,
- nbufs,
- [req_wrap, strong_ref](int status) {
- req_wrap->Done(status);
- });
-
- // If end() was called on the JS side, the write_ended flag
- // will have been set. This allows us to know early if this
- // is the final chunk. But this is only only to be triggered
- // if end() was called with a final chunk of data to write.
- // Otherwise, we have to wait for DoShutdown to be called.
- if (state_->write_ended == 1) {
- RecordTimestamp(&QuicStreamStats::closing_at);
- streambuf_.End();
- }
-
- session()->ResumeStream(stream_id_);
-
- return 0;
-}
-
-bool QuicStream::IsAlive() {
- return !is_destroyed() && !IsClosing();
-}
-
-bool QuicStream::IsClosing() {
- return !is_writable() && !is_readable();
-}
-
-int QuicStream::ReadStart() {
- CHECK(!is_destroyed());
- CHECK(is_readable());
- state_->read_started = 1;
- state_->read_paused = 0;
- IncrementStat(
- &QuicStreamStats::max_offset,
- inbound_consumed_data_while_paused_);
- session_->ExtendStreamOffset(id(), inbound_consumed_data_while_paused_);
- return 0;
-}
-
-int QuicStream::ReadStop() {
- CHECK(!is_destroyed());
- CHECK(is_readable());
- state_->read_paused = 1;
- return 0;
-}
-
-void QuicStream::IncrementStats(size_t datalen) {
- uint64_t len = static_cast<uint64_t>(datalen);
- IncrementStat(&QuicStreamStats::bytes_received, len);
- RecordRate(&QuicStreamStats::received_at);
- RecordSize(len);
-}
-
-void QuicStream::MemoryInfo(MemoryTracker* tracker) const {
- tracker->TrackField("buffer", &streambuf_);
- StatsBase::StatsMemoryInfo(tracker);
- tracker->TrackField("headers", headers_);
-}
-
-BaseObjectPtr<QuicStream> QuicStream::New(
- QuicSession* session,
- int64_t stream_id,
- int64_t push_id) {
- Local<Object> obj;
- if (!session->env()
- ->quicserverstream_instance_template()
- ->NewInstance(session->env()->context()).ToLocal(&obj)) {
- return {};
- }
- BaseObjectPtr<QuicStream> stream =
- MakeDetachedBaseObject<QuicStream>(
- session,
- obj,
- stream_id,
- push_id);
- CHECK(stream);
- session->AddStream(stream);
- return stream;
-}
-
-// Passes chunks of data on to the JavaScript side as soon as they are
-// received but only if we're still readable. The caller of this must have a
-// HandleScope.
-//
-// Note that this is pushing data to the JS side regardless of whether
-// anything is listening. For flow-control, we only send window updates
-// to the sending peer if the stream is in flowing mode, so the sender
-// should not be sending too much data.
-void QuicStream::ReceiveData(
- uint32_t flags,
- const uint8_t* data,
- size_t datalen,
- uint64_t offset) {
- CHECK(!is_destroyed());
- Debug(this, "Receiving %d bytes. Final? %s. Readable? %s",
- datalen,
- flags & NGTCP2_STREAM_DATA_FLAG_FIN ? "yes" : "no",
- is_readable() ? "yes" : "no");
-
- // If the QuicStream is not (or was never) readable, just ignore the chunk.
- if (!is_readable())
- return;
-
- // ngtcp2 guarantees that datalen will only be 0 if fin is set.
- // Let's just make sure.
- CHECK(datalen > 0 || flags & NGTCP2_STREAM_DATA_FLAG_FIN);
-
- // ngtcp2 guarantees that offset is always greater than the previously
- // received offset. Let's just make sure.
- CHECK_GE(offset, GetStat(&QuicStreamStats::max_offset_received));
- SetStat(&QuicStreamStats::max_offset_received, offset);
-
- if (datalen > 0) {
- // IncrementStats will update the data_rx_rate_ and data_rx_size_
- // histograms. These will provide data necessary to detect and
- // prevent Slow Send DOS attacks specifically by allowing us to
- // see if a connection is sending very small chunks of data at very
- // slow speeds. It is important to emphasize, however, that slow send
- // rates may be perfectly legitimate so we cannot simply take blanket
- // action when slow rates are detected. Nor can we reliably define what
- // a slow rate even is! Will will need to determine some reasonable
- // default and allow user code to change the default as well as determine
- // what action to take. The current strategy will be to trigger an event
- // on the stream when data transfer rates are likely to be considered too
- // slow.
- IncrementStats(datalen);
-
- while (datalen > 0) {
- uv_buf_t buf = EmitAlloc(datalen);
- size_t avail = std::min(static_cast<size_t>(buf.len), datalen);
-
- // For now, we're allocating and copying. Once we determine if we can
- // safely switch to a non-allocated mode like we do with http2 streams,
- // we can make this branch more efficient by using the LIKELY
- // optimization. The way ngtcp2 currently works, however, we have
- // to memcpy here.
- if (UNLIKELY(buf.base == nullptr))
- buf.base = reinterpret_cast<char*>(const_cast<uint8_t*>(data));
- else
- memcpy(buf.base, data, avail);
- data += avail;
- datalen -= avail;
- // Capture read_paused before EmitRead in case user code callbacks
- // alter the state when EmitRead is called.
- bool read_paused = state_->read_paused == 1;
- EmitRead(avail, buf);
- // Reading can be paused while we are processing. If that's
- // the case, we still want to acknowledge the current bytes
- // so that pausing does not throw off our flow control.
- if (read_paused) {
- inbound_consumed_data_while_paused_ += avail;
- } else {
- IncrementStat(&QuicStreamStats::max_offset, avail);
- session_->ExtendStreamOffset(id(), avail);
- }
- }
- }
-
- // When fin != 0, we've received that last chunk of data for this
- // stream, indicating that the stream will no longer be readable.
- if (flags & NGTCP2_STREAM_DATA_FLAG_FIN) {
- set_final_size(offset + datalen);
- EmitRead(UV_EOF);
- }
-}
-
-int QuicStream::DoPull(
- bob::Next<ngtcp2_vec> next,
- int options,
- ngtcp2_vec* data,
- size_t count,
- size_t max_count_hint) {
- return streambuf_.Pull(
- std::move(next),
- options,
- data,
- count,
- max_count_hint);
-}
-
-// JavaScript API
-namespace {
-void QuicStreamGetID(const FunctionCallbackInfo<Value>& args) {
- QuicStream* stream;
- ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
- args.GetReturnValue().Set(static_cast<double>(stream->id()));
-}
-
-void OpenUnidirectionalStream(const FunctionCallbackInfo<Value>& args) {
- CHECK(!args.IsConstructCall());
- CHECK(args[0]->IsObject());
- QuicSession* session;
- ASSIGN_OR_RETURN_UNWRAP(&session, args[0].As<Object>());
-
- int64_t stream_id;
- if (!session->OpenUnidirectionalStream(&stream_id))
- return;
-
- BaseObjectPtr<QuicStream> stream = QuicStream::New(session, stream_id);
- args.GetReturnValue().Set(stream->object());
-}
-
-void OpenBidirectionalStream(const FunctionCallbackInfo<Value>& args) {
- CHECK(!args.IsConstructCall());
- CHECK(args[0]->IsObject());
- QuicSession* session;
- ASSIGN_OR_RETURN_UNWRAP(&session, args[0].As<Object>());
-
- int64_t stream_id;
- if (!session->OpenBidirectionalStream(&stream_id))
- return;
-
- BaseObjectPtr<QuicStream> stream = QuicStream::New(session, stream_id);
- args.GetReturnValue().Set(stream->object());
-}
-
-void QuicStreamDestroy(const FunctionCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
- QuicStream* stream;
- ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
- QuicError error(env, args[0], args[1], QUIC_ERROR_APPLICATION);
- stream->Destroy(&error);
-}
-
-void QuicStreamReset(const FunctionCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
- QuicStream* stream;
- ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
-
- QuicError error(env, args[0], args[1], QUIC_ERROR_APPLICATION);
-
- stream->ResetStream(
- error.family == QUIC_ERROR_APPLICATION ?
- error.code : static_cast<uint64_t>(NGTCP2_NO_ERROR));
-}
-
-void QuicStreamStopSending(const FunctionCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
- QuicStream* stream;
- ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
-
- QuicError error(env, args[0], args[1], QUIC_ERROR_APPLICATION);
-
- stream->StopSending(
- error.family == QUIC_ERROR_APPLICATION ?
- error.code : static_cast<uint64_t>(NGTCP2_NO_ERROR));
-}
-
-// Requests transmission of a block of informational headers. Not all
-// QUIC Applications will support headers. If headers are not supported,
-// This will set the return value to false, otherwise the return value
-// is set to true
-void QuicStreamSubmitInformation(const FunctionCallbackInfo<Value>& args) {
- QuicStream* stream;
- ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
- CHECK(args[0]->IsArray());
- args.GetReturnValue().Set(stream->SubmitInformation(args[0].As<Array>()));
-}
-
-// Requests transmission of a block of initial headers. Not all
-// QUIC Applications will support headers. If headers are not supported,
-// this will set the return value to false, otherwise the return value
-// is set to true. For http/3, these may be request or response headers.
-void QuicStreamSubmitHeaders(const FunctionCallbackInfo<Value>& args) {
- QuicStream* stream;
- ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
- CHECK(args[0]->IsArray());
- uint32_t flags = QUICSTREAM_HEADER_FLAGS_NONE;
- CHECK(args[1]->Uint32Value(stream->env()->context()).To(&flags));
- args.GetReturnValue().Set(stream->SubmitHeaders(args[0].As<Array>(), flags));
-}
-
-// Requests transmission of a block of trailing headers. Not all
-// QUIC Applications will support headers. If headers are not supported,
-// this will set the return value to false, otherwise the return value
-// is set to true.
-void QuicStreamSubmitTrailers(const FunctionCallbackInfo<Value>& args) {
- QuicStream* stream;
- ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
- CHECK(args[0]->IsArray());
- args.GetReturnValue().Set(stream->SubmitTrailers(args[0].As<Array>()));
-}
-
-// Requests creation of a push stream. Not all QUIC Applications will
-// support push streams. If pushes are not supported, the return value
-// will be undefined, otherwise the return value will be the created
-// QuicStream representing the push.
-void QuicStreamSubmitPush(const FunctionCallbackInfo<Value>& args) {
- QuicStream* stream;
- ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
- CHECK(args[0]->IsArray());
- BaseObjectPtr<QuicStream> push_stream =
- stream->SubmitPush(args[0].As<Array>());
- if (push_stream)
- args.GetReturnValue().Set(push_stream->object());
-}
-
-} // namespace
-
-void QuicStream::Initialize(
- Environment* env,
- Local<Object> target,
- Local<Context> context) {
- Local<FunctionTemplate> stream = FunctionTemplate::New(env->isolate());
- stream->Inherit(AsyncWrap::GetConstructorTemplate(env));
- StreamBase::AddMethods(env, stream);
- Local<ObjectTemplate> streamt = stream->InstanceTemplate();
- streamt->SetInternalFieldCount(StreamBase::kInternalFieldCount);
- streamt->Set(env->owner_symbol(), Null(env->isolate()));
- env->SetProtoMethod(stream, "destroy", QuicStreamDestroy);
- env->SetProtoMethod(stream, "resetStream", QuicStreamReset);
- env->SetProtoMethod(stream, "stopSending", QuicStreamStopSending);
- env->SetProtoMethod(stream, "id", QuicStreamGetID);
- env->SetProtoMethod(stream, "submitInformation", QuicStreamSubmitInformation);
- env->SetProtoMethod(stream, "submitHeaders", QuicStreamSubmitHeaders);
- env->SetProtoMethod(stream, "submitTrailers", QuicStreamSubmitTrailers);
- env->SetProtoMethod(stream, "submitPush", QuicStreamSubmitPush);
- env->set_quicserverstream_instance_template(streamt);
- env->SetConstructorFunction(target, "QuicStream", stream);
-
- env->SetMethod(target, "openBidirectionalStream", OpenBidirectionalStream);
- env->SetMethod(target, "openUnidirectionalStream", OpenUnidirectionalStream);
-}
-
-} // namespace quic
-} // namespace node
diff --git a/src/quic/node_quic_stream.h b/src/quic/node_quic_stream.h
deleted file mode 100644
index 6cbdef2416f..00000000000
--- a/src/quic/node_quic_stream.h
+++ /dev/null
@@ -1,409 +0,0 @@
-#ifndef SRC_QUIC_NODE_QUIC_STREAM_H_
-#define SRC_QUIC_NODE_QUIC_STREAM_H_
-
-#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#include "memory_tracker.h"
-#include "aliased_struct.h"
-#include "async_wrap.h"
-#include "env.h"
-#include "node_http_common.h"
-#include "node_quic_state.h"
-#include "node_quic_util.h"
-#include "stream_base-inl.h"
-#include "util-inl.h"
-#include "v8.h"
-
-#include <string>
-#include <vector>
-
-namespace node {
-namespace quic {
-
-class QuicSession;
-class QuicStream;
-class QuicApplication;
-
-using QuicHeader = NgHeaderBase<QuicApplication>;
-
-enum QuicStreamHeaderFlags : uint32_t {
- // No flags
- QUICSTREAM_HEADER_FLAGS_NONE = 0,
-
- // Set if the initial headers are considered
- // terminal (that is, the stream should be closed
- // after transmitting the headers). If headers are
- // not supported by the QUIC Application, flag is
- // ignored.
- QUICSTREAM_HEADER_FLAGS_TERMINAL = 1
-};
-
-enum QuicStreamHeadersKind : int {
- QUICSTREAM_HEADERS_KIND_NONE = 0,
- QUICSTREAM_HEADERS_KIND_INFORMATIONAL,
- QUICSTREAM_HEADERS_KIND_INITIAL,
- QUICSTREAM_HEADERS_KIND_TRAILING,
- QUICSTREAM_HEADERS_KIND_PUSH
-};
-
-#define STREAM_STATS(V) \
- V(CREATED_AT, created_at, "Created At") \
- V(RECEIVED_AT, received_at, "Last Received At") \
- V(ACKED_AT, acked_at, "Last Acknowledged At") \
- V(CLOSING_AT, closing_at, "Closing At") \
- V(DESTROYED_AT, destroyed_at, "Destroyed At") \
- V(BYTES_RECEIVED, bytes_received, "Bytes Received") \
- V(BYTES_SENT, bytes_sent, "Bytes Sent") \
- V(MAX_OFFSET, max_offset, "Max Offset") \
- V(MAX_OFFSET_ACK, max_offset_ack, "Max Acknowledged Offset") \
- V(MAX_OFFSET_RECV, max_offset_received, "Max Received Offset") \
- V(FINAL_SIZE, final_size, "Final Size")
-
-#define V(name, _, __) IDX_QUIC_STREAM_STATS_##name,
-enum QuicStreamStatsIdx : int {
- STREAM_STATS(V)
- IDX_QUIC_STREAM_STATS_COUNT
-};
-#undef V
-
-#define V(_, name, __) uint64_t name;
-struct QuicStreamStats {
- STREAM_STATS(V)
-};
-#undef V
-
-struct QuicStreamStatsTraits {
- using Stats = QuicStreamStats;
- using Base = QuicStream;
-
- template <typename Fn>
- static void ToString(const Base& ptr, Fn&& add_field);
-};
-
-#define QUICSTREAM_SHARED_STATE(V) \
- V(WRITE_ENDED, write_ended, uint8_t) \
- V(READ_STARTED, read_started, uint8_t) \
- V(READ_PAUSED, read_paused, uint8_t) \
- V(READ_ENDED, read_ended, uint8_t) \
- V(FIN_SENT, fin_sent, uint8_t) \
- V(FIN_RECEIVED, fin_received, uint8_t)
-
-#define V(_, name, type) type name;
-struct QuicStreamState {
- QUICSTREAM_SHARED_STATE(V);
-};
-#undef V
-
-#define V(id, name, _) \
- IDX_QUICSTREAM_STATE_##id = offsetof(QuicStreamState, name),
-enum QuicStreamStateFields {
- QUICSTREAM_SHARED_STATE(V)
- IDX_QUICSTREAM_STATE_END
-};
-#undef V
-
-enum QuicStreamDirection {
- // The QuicStream is readable and writable in both directions
- QUIC_STREAM_BIRECTIONAL,
-
- // The QuicStream is writable and readable in only one direction.
- // The direction depends on the QuicStreamOrigin.
- QUIC_STREAM_UNIDIRECTIONAL
-};
-
-enum QuicStreamOrigin {
- // The QuicStream was created by the server.
- QUIC_STREAM_SERVER,
-
- // The QuicStream was created by the client.
- QUIC_STREAM_CLIENT
-};
-
-// QuicStream's are simple data flows that, fortunately, do not
-// require much. They may be:
-//
-// * Bidirectional or Unidirectional
-// * Server or Client Initiated
-//
-// The flow direction and origin of the stream are important in
-// determining the write and read state (Open or Closed). Specifically:
-//
-// A Unidirectional stream originating with the Server is:
-//
-// * Server Writable (Open) but not Client Writable (Closed)
-// * Client Readable (Open) but not Server Readable (Closed)
-//
-// Likewise, a Unidirectional stream originating with the
-// Client is:
-//
-// * Client Writable (Open) but not Server Writable (Closed)
-// * Server Readable (Open) but not Client Readable (Closed)
-//
-// Bidirectional Stream States
-// +------------+--------------+--------------------+---------------------+
-// | | Initiated By | Initial Read State | Initial Write State |
-// +------------+--------------+--------------------+---------------------+
-// | On Server | Server | Open | Open |
-// +------------+--------------+--------------------+---------------------+
-// | On Server | Client | Open | Open |
-// +------------+--------------+--------------------+---------------------+
-// | On Client | Server | Open | Open |
-// +------------+--------------+--------------------+---------------------+
-// | On Client | Client | Open | Open |
-// +------------+--------------+--------------------+---------------------+
-//
-// Unidirectional Stream States
-// +------------+--------------+--------------------+---------------------+
-// | | Initiated By | Initial Read State | Initial Write State |
-// +------------+--------------+--------------------+---------------------+
-// | On Server | Server | Closed | Open |
-// +------------+--------------+--------------------+---------------------+
-// | On Server | Client | Open | Closed |
-// +------------+--------------+--------------------+---------------------+
-// | On Client | Server | Open | Closed |
-// +------------+--------------+--------------------+---------------------+
-// | On Client | Client | Closed | Open |
-// +------------+--------------+--------------------+---------------------+
-//
-// All data sent via the QuicStream is buffered internally until either
-// receipt is acknowledged from the peer or attempts to send are abandoned.
-//
-// A QuicStream may be in a fully Closed (Read and Write) state but still
-// have unacknowledged data in it's outbound queue.
-//
-// A QuicStream is gracefully closed when (a) both Read and Write states
-// are Closed, (b) all queued data has been acknowledged.
-//
-// The JavaScript Writable side of the QuicStream may be shutdown before
-// all pending queued data has been serialized to frames. During this state,
-// no additional data may be queued to send.
-//
-// The Write state of a QuicStream will not be closed while there is still
-// pending writes on the JavaScript side.
-//
-// The QuicStream may be forcefully closed immediately using destroy(err).
-// This causes all queued data and pending JavaScript writes to be
-// abandoned, and causes the QuicStream to be immediately closed at the
-// ngtcp2 level.
-class QuicStream : public AsyncWrap,
- public bob::SourceImpl<ngtcp2_vec>,
- public StreamBase,
- public StatsBase<QuicStreamStatsTraits> {
- public:
- static void Initialize(
- Environment* env,
- v8::Local<v8::Object> target,
- v8::Local<v8::Context> context);
-
- static BaseObjectPtr<QuicStream> New(
- QuicSession* session,
- int64_t stream_id,
- int64_t push_id = 0);
-
- QuicStream(
- QuicSession* session,
- v8::Local<v8::Object> target,
- int64_t stream_id,
- int64_t push_id = 0);
-
- ~QuicStream() override;
-
- std::string diagnostic_name() const override;
-
- // The numeric identifier of the QuicStream.
- int64_t id() const { return stream_id_; }
-
- // If the QuicStream is associated with a push promise,
- // the numeric identifier of the promise. Currently only
- // used by HTTP/3.
- int64_t push_id() const { return push_id_; }
-
- QuicSession* session() const { return session_.get(); }
-
- // A QuicStream can be either uni- or bi-directional.
- inline QuicStreamDirection direction() const;
-
- // A QuicStream can be initiated by either the client
- // or the server.
- inline QuicStreamOrigin origin() const;
-
- inline void set_fin_sent();
-
- inline bool is_destroyed() const { return destroyed_; }
-
- inline void set_destroyed();
-
- // A QuicStream will not be writable if:
- // - The streambuf_ is ended
- // - It is a Unidirectional stream originating from the peer
- inline bool is_writable() const;
-
- // A QuicStream will not be readable if:
- // - The read ended flag is set or
- // - It is a Unidirectional stream originating from the local peer.
- inline bool is_readable() const;
-
- // IsWriteFinished will return true if a final stream frame
- // has been sent and all data has been acknowledged (the
- // send buffer is empty).
- inline bool is_write_finished() const;
-
- // Specifies the kind of headers currently being processed.
- inline void set_headers_kind(QuicStreamHeadersKind kind);
-
- // Set the final size for the QuicStream. This only works
- // the first time it is called. Subsequent calls will be
- // ignored unless the subsequent size is greater than the
- // prior set size, in which case we have a bug and we'll
- // assert.
- inline void set_final_size(uint64_t final_size);
-
- // The final size is the maximum amount of data that has been
- // acknowleged to have been received for a QuicStream.
- uint64_t final_size() const {
- return GetStat(&QuicStreamStats::final_size);
- }
-
- // Marks the given data range as having been acknowledged.
- // This means that the data range may be released from
- // memory.
- void Acknowledge(uint64_t offset, size_t datalen);
-
- // Destroy the QuicStream and render it no longer usable.
- void Destroy(QuicError* error = nullptr);
-
- inline void CancelPendingWrites();
-
- // Buffers chunks of data to be written to the QUIC connection.
- int DoWrite(
- WriteWrap* req_wrap,
- uv_buf_t* bufs,
- size_t nbufs,
- uv_stream_t* send_handle) override;
-
- // Returns false if the header cannot be added. This will
- // typically only happen if a maximimum number of headers
- // has been reached.
- bool AddHeader(std::unique_ptr<QuicHeader> header);
-
- // Some QUIC applications support headers, others do not.
- // The following methods allow consistent handling of
- // headers at the QuicStream level regardless of the
- // protocol. For applications that do not support headers,
- // these are simply not used.
- inline void BeginHeaders(
- QuicStreamHeadersKind kind = QUICSTREAM_HEADERS_KIND_NONE);
-
- // Indicates an amount of unacknowledged data that has been
- // submitted to the QUIC connection.
- inline void Commit(size_t amount);
-
- inline void EndHeaders(int64_t push_id = 0);
-
- // Passes a chunk of data on to the QuicStream listener.
- void ReceiveData(
- uint32_t flags,
- const uint8_t* data,
- size_t datalen,
- uint64_t offset);
-
- // Resets the QUIC stream, sending a signal to the peer that
- // no additional data will be transmitted for this stream.
- inline void ResetStream(uint64_t app_error_code = NGTCP2_NO_ERROR);
-
- inline void StopSending(uint64_t app_error_code = NGTCP2_NO_ERROR);
-
- // Submits informational headers. Returns false if headers are not
- // supported on the underlying QuicApplication.
- inline bool SubmitInformation(v8::Local<v8::Array> headers);
-
- // Submits initial headers. Returns false if headers are not
- // supported on the underlying QuicApplication.
- inline bool SubmitHeaders(v8::Local<v8::Array> headers, uint32_t flags);
-
- // Submits trailing headers. Returns false if headers are not
- // supported on the underlying QuicApplication.
- inline bool SubmitTrailers(v8::Local<v8::Array> headers);
-
- inline BaseObjectPtr<QuicStream> SubmitPush(v8::Local<v8::Array> headers);
-
- // Required for StreamBase
- bool IsAlive() override;
-
- // Required for StreamBase
- bool IsClosing() override;
-
- // Required for StreamBase
- int ReadStart() override;
-
- // Required for StreamBase
- int ReadStop() override;
-
- // Required for StreamBase
- int DoShutdown(ShutdownWrap* req_wrap) override;
-
- AsyncWrap* GetAsyncWrap() override { return this; }
-
- QuicState* quic_state() { return quic_state_.get(); }
-
- // Required for MemoryRetainer
- void MemoryInfo(MemoryTracker* tracker) const override;
- SET_MEMORY_INFO_NAME(QuicStream)
- SET_SELF_SIZE(QuicStream)
-
- protected:
- int DoPull(
- bob::Next<ngtcp2_vec> next,
- int options,
- ngtcp2_vec* data,
- size_t count,
- size_t max_count_hint) override;
-
- private:
- // WasEverWritable returns true if it is a bidirectional stream,
- // or a Unidirectional stream originating from the local peer.
- // If was_ever_writable() is false, then no stream frames should
- // ever be sent from the local peer, including final stream frames.
- inline bool was_ever_writable() const;
-
- // WasEverReadable returns true if it is a bidirectional stream,
- // or a Unidirectional stream originating from the remote
- // peer.
- inline bool was_ever_readable() const;
-
- void IncrementStats(size_t datalen);
-
- BaseObjectWeakPtr<QuicSession> session_;
- QuicBuffer streambuf_;
-
- int64_t stream_id_ = 0;
- int64_t push_id_ = 0;
- bool destroyed_ = false;
- AliasedStruct<QuicStreamState> state_;
- DoneCB shutdown_done_ = nullptr;
-
- size_t inbound_consumed_data_while_paused_ = 0;
-
- std::vector<std::unique_ptr<QuicHeader>> headers_;
- QuicStreamHeadersKind headers_kind_;
- size_t current_headers_length_ = 0;
-
- ListNode<QuicStream> stream_queue_;
-
- BaseObjectPtr<QuicState> quic_state_;
-
- public:
- // Linked List of QuicStream objects
- using Queue = ListHead<QuicStream, &QuicStream::stream_queue_>;
-
- inline void Schedule(Queue* queue);
-
- inline void Unschedule();
-};
-
-} // namespace quic
-} // namespace node
-
-#endif // NODE_WANT_INTERNALS
-
-#endif // SRC_QUIC_NODE_QUIC_STREAM_H_
diff --git a/src/quic/node_quic_util-inl.h b/src/quic/node_quic_util-inl.h
deleted file mode 100644
index 65b2e08823d..00000000000
--- a/src/quic/node_quic_util-inl.h
+++ /dev/null
@@ -1,435 +0,0 @@
-#ifndef SRC_QUIC_NODE_QUIC_UTIL_INL_H_
-#define SRC_QUIC_NODE_QUIC_UTIL_INL_H_
-
-#include "debug_utils-inl.h"
-#include "node_internals.h"
-#include "node_quic_crypto.h"
-#include "node_quic_util.h"
-#include "memory_tracker-inl.h"
-#include "env-inl.h"
-#include "histogram-inl.h"
-#include "string_bytes.h"
-#include "util-inl.h"
-#include "uv.h"
-
-#include <string>
-
-namespace node {
-
-namespace quic {
-
-QuicPath::QuicPath(
- const SocketAddress& local,
- const SocketAddress& remote) {
- ngtcp2_addr_init(
- &this->local,
- local.data(),
- local.length(),
- const_cast<SocketAddress*>(&local));
- ngtcp2_addr_init(
- &this->remote,
- remote.data(),
- remote.length(),
- const_cast<SocketAddress*>(&remote));
-}
-
-size_t QuicCID::Hash::operator()(const QuicCID& token) const {
- size_t hash = 0;
- for (size_t n = 0; n < token->datalen; n++) {
- hash ^= std::hash<uint8_t>{}(token->data[n]) + 0x9e3779b9 +
- (hash << 6) + (hash >> 2);
- }
- return hash;
-}
-
-QuicCID& QuicCID::operator=(const QuicCID& cid) {
- if (this == &cid) return *this;
- this->~QuicCID();
- return *new(this) QuicCID(std::move(cid));
-}
-
-bool QuicCID::operator==(const QuicCID& other) const {
- return memcmp(cid()->data, other.cid()->data, cid()->datalen) == 0;
-}
-
-bool QuicCID::operator!=(const QuicCID& other) const {
- return !(*this == other);
-}
-
-std::string QuicCID::ToString() const {
- std::vector<char> dest(ptr_->datalen * 2 + 1);
- dest[dest.size() - 1] = '\0';
- size_t written = StringBytes::hex_encode(
- reinterpret_cast<const char*>(ptr_->data),
- ptr_->datalen,
- dest.data(),
- dest.size());
- return std::string(dest.data(), written);
-}
-
-size_t GetMaxPktLen(const SocketAddress& addr) {
- return addr.family() == AF_INET6 ?
- NGTCP2_MAX_PKTLEN_IPV6 :
- NGTCP2_MAX_PKTLEN_IPV4;
-}
-
-QuicError::QuicError(
- int32_t family_,
- uint64_t code_) :
- family(family_),
- code(code_) {}
-
-QuicError::QuicError(
- int32_t family_,
- int code_) :
- family(family_) {
- switch (family) {
- case QUIC_ERROR_CRYPTO:
- code_ |= NGTCP2_CRYPTO_ERROR;
- // Fall-through...
- case QUIC_ERROR_SESSION:
- code = ngtcp2_err_infer_quic_transport_error_code(code_);
- break;
- case QUIC_ERROR_APPLICATION:
- code = code_;
- break;
- default:
- UNREACHABLE();
- }
-}
-
-QuicError::QuicError(ngtcp2_connection_close_error_code ccec) :
- family(QUIC_ERROR_SESSION),
- code(ccec.error_code) {
- switch (ccec.type) {
- case NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION:
- family = QUIC_ERROR_APPLICATION;
- break;
- case NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT:
- if (code & NGTCP2_CRYPTO_ERROR)
- family = QUIC_ERROR_CRYPTO;
- break;
- default:
- UNREACHABLE();
- }
-}
-
-QuicError::QuicError(
- Environment* env,
- v8::Local<v8::Value> codeArg,
- v8::Local<v8::Value> familyArg,
- int32_t family_) :
- family(family_),
- code(NGTCP2_NO_ERROR) {
- if (codeArg->IsBigInt()) {
- code = codeArg.As<v8::BigInt>()->Int64Value();
- } else if (codeArg->IsNumber()) {
- double num = 0;
- CHECK(codeArg->NumberValue(env->context()).To(&num));
- code = static_cast<uint64_t>(num);
- }
- if (familyArg->IsNumber()) {
- CHECK(familyArg->Int32Value(env->context()).To(&family));
- }
-}
-
-const char* QuicError::family_name() {
- switch (family) {
- case QUIC_ERROR_SESSION:
- return "Session";
- case QUIC_ERROR_APPLICATION:
- return "Application";
- case QUIC_ERROR_CRYPTO:
- return "Crypto";
- default:
- UNREACHABLE();
- }
-}
-
-const ngtcp2_cid* PreferredAddress::cid() const {
- return &paddr_->cid;
-}
-
-const uint8_t* PreferredAddress::stateless_reset_token() const {
- return paddr_->stateless_reset_token;
-}
-
-std::string PreferredAddress::ipv6_address() const {
- char host[NI_MAXHOST];
- // Return an empty string if unable to convert...
- if (uv_inet_ntop(AF_INET6, paddr_->ipv6_addr, host, sizeof(host)) != 0)
- return std::string();
-
- return std::string(host);
-}
-std::string PreferredAddress::ipv4_address() const {
- char host[NI_MAXHOST];
- // Return an empty string if unable to convert...
- if (uv_inet_ntop(AF_INET, paddr_->ipv4_addr, host, sizeof(host)) != 0)
- return std::string();
-
- return std::string(host);
-}
-
-uint16_t PreferredAddress::ipv6_port() const {
- return paddr_->ipv6_port;
-}
-
-uint16_t PreferredAddress::ipv4_port() const {
- return paddr_->ipv4_port;
-}
-
-bool PreferredAddress::Use(int family) const {
- uv_getaddrinfo_t req;
-
- if (!ResolvePreferredAddress(family, &req))
- return false;
-
- dest_->addrlen = req.addrinfo->ai_addrlen;
- memcpy(dest_->addr, req.addrinfo->ai_addr, req.addrinfo->ai_addrlen);
- uv_freeaddrinfo(req.addrinfo);
- return true;
-}
-
-bool PreferredAddress::ResolvePreferredAddress(
- int local_address_family,
- uv_getaddrinfo_t* req) const {
- int af;
- const uint8_t* binaddr;
- uint16_t port;
- switch (local_address_family) {
- case AF_INET:
- if (paddr_->ipv4_port > 0) {
- af = AF_INET;
- binaddr = paddr_->ipv4_addr;
- port = paddr_->ipv4_port;
- break;
- }
- return false;
- case AF_INET6:
- if (paddr_->ipv6_port > 0) {
- af = AF_INET6;
- binaddr = paddr_->ipv6_addr;
- port = paddr_->ipv6_port;
- break;
- }
- return false;
- default:
- UNREACHABLE();
- }
-
- char host[NI_MAXHOST];
- if (uv_inet_ntop(af, binaddr, host, sizeof(host)) != 0)
- return false;
-
- addrinfo hints{};
- hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
- hints.ai_family = af;
- hints.ai_socktype = SOCK_DGRAM;
-
- // Unfortunately ngtcp2 requires the selection of the
- // preferred address to be synchronous, which means we
- // have to do a sync resolve using uv_getaddrinfo here.
- return
- uv_getaddrinfo(
- env_->event_loop(),
- req,
- nullptr,
- host,
- std::to_string(port).c_str(),
- &hints) == 0 &&
- req->addrinfo != nullptr;
-}
-
-StatelessResetToken::StatelessResetToken(
- uint8_t* token,
- const uint8_t* secret,
- const QuicCID& cid) {
- GenerateResetToken(token, secret, cid);
- memcpy(buf_, token, sizeof(buf_));
-}
-
-StatelessResetToken::StatelessResetToken(
- const uint8_t* secret,
- const QuicCID& cid) {
- GenerateResetToken(buf_, secret, cid);
-}
-
-StatelessResetToken::StatelessResetToken(
- const uint8_t* token) {
- memcpy(buf_, token, sizeof(buf_));
-}
-
-std::string StatelessResetToken::ToString() const {
- std::vector<char> dest(NGTCP2_STATELESS_RESET_TOKENLEN * 2 + 1);
- dest[dest.size() - 1] = '\0';
- size_t written = StringBytes::hex_encode(
- reinterpret_cast<const char*>(buf_),
- NGTCP2_STATELESS_RESET_TOKENLEN,
- dest.data(),
- dest.size());
- return std::string(dest.data(), written);
-}
-
-size_t StatelessResetToken::Hash::operator()(
- const StatelessResetToken& token) const {
- size_t hash = 0;
- for (size_t n = 0; n < NGTCP2_STATELESS_RESET_TOKENLEN; n++)
- hash ^= std::hash<uint8_t>{}(token.buf_[n]) + 0x9e3779b9 +
- (hash << 6) + (hash >> 2);
- return hash;
-}
-
-bool StatelessResetToken::operator==(const StatelessResetToken& other) const {
- return memcmp(data(), other.data(), NGTCP2_STATELESS_RESET_TOKENLEN) == 0;
-}
-
-bool StatelessResetToken::operator!=(const StatelessResetToken& other) const {
- return !(*this == other);
-}
-
-template <typename T>
-StatsBase<T>::StatsBase(
- Environment* env,
- v8::Local<v8::Object> wrap,
- int options) {
- static constexpr uint64_t kMax = std::numeric_limits<int64_t>::max();
-
- // Create the backing store for the statistics
- size_t size = sizeof(Stats);
- size_t count = size / sizeof(uint64_t);
- stats_store_ = v8::ArrayBuffer::NewBackingStore(env->isolate(), size);
- stats_ = new (stats_store_->Data()) Stats;
-
- DCHECK_NOT_NULL(stats_);
- stats_->created_at = uv_hrtime();
-
- // The stats buffer is exposed as a BigUint64Array on
- // the JavaScript side to allow statistics to be monitored.
- v8::Local<v8::ArrayBuffer> stats_buffer =
- v8::ArrayBuffer::New(env->isolate(), stats_store_);
- v8::Local<v8::BigUint64Array> stats_array =
- v8::BigUint64Array::New(stats_buffer, 0, count);
- USE(wrap->DefineOwnProperty(
- env->context(),
- env->stats_string(),
- stats_array,
- v8::PropertyAttribute::ReadOnly));
-
- if (options & HistogramOptions::ACK) {
- ack_ = HistogramBase::New(env, 1, kMax);
- wrap->DefineOwnProperty(
- env->context(),
- env->ack_string(),
- ack_->object(),
- v8::PropertyAttribute::ReadOnly).Check();
- }
-
- if (options & HistogramOptions::RATE) {
- rate_ = HistogramBase::New(env, 1, kMax);
- wrap->DefineOwnProperty(
- env->context(),
- env->rate_string(),
- rate_->object(),
- v8::PropertyAttribute::ReadOnly).Check();
- }
-
- if (options & HistogramOptions::SIZE) {
- size_ = HistogramBase::New(env, 1, kMax);
- wrap->DefineOwnProperty(
- env->context(),
- env->size_string(),
- size_->object(),
- v8::PropertyAttribute::ReadOnly).Check();
- }
-}
-
-template <typename T>
-void StatsBase<T>::IncrementStat(uint64_t Stats::*member, uint64_t amount) {
- static constexpr uint64_t kMax = std::numeric_limits<uint64_t>::max();
- stats_->*member += std::min(amount, kMax - stats_->*member);
-}
-
-template <typename T>
-void StatsBase<T>::SetStat(uint64_t Stats::*member, uint64_t value) {
- stats_->*member = value;
-}
-
-template <typename T>
-void StatsBase<T>::RecordTimestamp(uint64_t Stats::*member) {
- stats_->*member = uv_hrtime();
-}
-
-template <typename T>
-uint64_t StatsBase<T>::GetStat(uint64_t Stats::*member) const {
- return stats_->*member;
-}
-
-template <typename T>
-inline void StatsBase<T>::RecordRate(uint64_t Stats::*member) {
- CHECK(rate_);
- uint64_t received_at = GetStat(member);
- uint64_t now = uv_hrtime();
- if (received_at > 0)
- rate_->Record(now - received_at);
- SetStat(member, now);
-}
-
-template <typename T>
-inline void StatsBase<T>::RecordSize(uint64_t val) {
- CHECK(size_);
- size_->Record(val);
-}
-
-template <typename T>
-inline void StatsBase<T>::RecordAck(uint64_t Stats::*member) {
- CHECK(ack_);
- uint64_t acked_at = GetStat(member);
- uint64_t now = uv_hrtime();
- if (acked_at > 0)
- ack_->Record(now - acked_at);
- SetStat(member, now);
-}
-
-template <typename T>
-void StatsBase<T>::StatsMemoryInfo(MemoryTracker* tracker) const {
- tracker->TrackField("stats_store", stats_store_);
- tracker->TrackField("rate_histogram", rate_);
- tracker->TrackField("size_histogram", size_);
- tracker->TrackField("ack_histogram", ack_);
-}
-
-template <typename T>
-void StatsBase<T>::DebugStats() {
- StatsDebug stats_debug(static_cast<typename T::Base*>(this));
- Debug(static_cast<typename T::Base*>(this), "Destroyed. %s", stats_debug);
-}
-
-template <typename T>
-std::string StatsBase<T>::StatsDebug::ToString() const {
- std::string out = "Statistics:\n";
- auto add_field = [&out](const char* name, uint64_t val) {
- out += " ";
- out += std::string(name);
- out += ": ";
- out += std::to_string(val);
- out += "\n";
- };
- add_field("Duration", uv_hrtime() - ptr->GetStat(&Stats::created_at));
- T::ToString(*ptr, add_field);
- return out;
-}
-
-template <typename T>
-size_t get_length(const T* vec, size_t count) {
- CHECK_NOT_NULL(vec);
- size_t len = 0;
- for (size_t n = 0; n < count; n++)
- len += vec[n].len;
- return len;
-}
-
-} // namespace quic
-} // namespace node
-
-#endif // SRC_QUIC_NODE_QUIC_UTIL_INL_H_
diff --git a/src/quic/node_quic_util.h b/src/quic/node_quic_util.h
deleted file mode 100644
index b3afcb9483d..00000000000
--- a/src/quic/node_quic_util.h
+++ /dev/null
@@ -1,394 +0,0 @@
-#ifndef SRC_QUIC_NODE_QUIC_UTIL_H_
-#define SRC_QUIC_NODE_QUIC_UTIL_H_
-
-#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#include "node.h"
-#include "node_sockaddr.h"
-#include "uv.h"
-#include "v8.h"
-#include "histogram.h"
-#include "memory_tracker.h"
-
-#include <ngtcp2/ngtcp2.h>
-#include <openssl/ssl.h>
-
-#include <algorithm>
-#include <functional>
-#include <limits>
-#include <string>
-#include <unordered_map>
-
-namespace node {
-namespace quic {
-
-// k-constants are used internally, all-caps constants
-// are exposed to javascript as constants (see node_quic.cc)
-
-constexpr size_t kMaxSizeT = std::numeric_limits<size_t>::max();
-constexpr size_t kMaxValidateAddressLru = 10;
-constexpr size_t kMinInitialQuicPktSize = 1200;
-constexpr size_t kScidLen = NGTCP2_MAX_CIDLEN;
-constexpr size_t kTokenRandLen = 16;
-constexpr size_t kTokenSecretLen = 16;
-
-constexpr uint64_t DEFAULT_ACTIVE_CONNECTION_ID_LIMIT = 2;
-constexpr uint64_t DEFAULT_MAX_CONNECTIONS =
- std::min<uint64_t>(kMaxSizeT, kMaxSafeJsInteger);
-constexpr uint64_t DEFAULT_MAX_CONNECTIONS_PER_HOST = 100;
-constexpr uint64_t DEFAULT_MAX_STREAM_DATA_BIDI_LOCAL = 256 * 1024;
-constexpr uint64_t DEFAULT_MAX_STREAM_DATA_BIDI_REMOTE = 256 * 1024;
-constexpr uint64_t DEFAULT_MAX_STREAM_DATA_UNI = 256 * 1024;
-constexpr uint64_t DEFAULT_MAX_DATA = 1 * 1024 * 1024;
-constexpr uint64_t DEFAULT_MAX_STATELESS_RESETS_PER_HOST = 10;
-constexpr uint64_t DEFAULT_MAX_STREAMS_BIDI = 100;
-constexpr uint64_t DEFAULT_MAX_STREAMS_UNI = 3;
-constexpr uint64_t DEFAULT_MAX_IDLE_TIMEOUT = 10;
-constexpr uint64_t DEFAULT_RETRYTOKEN_EXPIRATION = 10;
-constexpr uint64_t MIN_RETRYTOKEN_EXPIRATION = 1;
-constexpr uint64_t MAX_RETRYTOKEN_EXPIRATION = 60;
-constexpr uint64_t NGTCP2_APP_NOERROR = 0xff00;
-
-constexpr int ERR_FAILED_TO_CREATE_SESSION = -1;
-
-// The preferred address policy determines how a client QuicSession
-// handles a server-advertised preferred address. As suggested, the
-// preferred address is the address the server would prefer the
-// client to use for subsequent communication for a QuicSession.
-// The client may choose to ignore the preference but really shouldn't
-// without good reason. We currently only support two options but
-// additional options may be added later.
-enum SelectPreferredAddressPolicy : int {
- // Ignore the server-provided preferred address
- QUIC_PREFERRED_ADDRESS_IGNORE,
-
- // Use the server-provided preferred address.
- // With this policy in effect, when a client
- // receives a preferred address from the server,
- // the client QuicSession will be automatically
- // switched to use the selected address if it
- // matches the current local address family.
- QUIC_PREFERRED_ADDRESS_USE
-};
-
-// QUIC error codes generally fall into two distinct namespaces:
-// Connection Errors and Application Errors. Connection errors
-// are further subdivided into Crypto and non-Crypto. Application
-// errors are entirely specific to the QUIC application being
-// used. An easy rule of thumb is that Application errors are
-// semantically associated with the ALPN identifier negotiated
-// for the QuicSession. So, if a connection is closed with
-// family: QUIC_ERROR_APPLICATION and code: 123, you have to
-// look at the ALPN identifier to determine exactly what it
-// means. Connection (Session) and Crypto errors, on the other
-// hand, share the same meaning regardless of the ALPN.
-enum QuicErrorFamily : int32_t {
- QUIC_ERROR_SESSION,
- QUIC_ERROR_CRYPTO,
- QUIC_ERROR_APPLICATION
-};
-
-
-template <typename T> class StatsBase;
-
-template <typename T, typename Q>
-struct StatsTraits {
- using Stats = T;
- using Base = Q;
-
- template <typename Fn>
- static void ToString(const Q& ptr, Fn&& add_field);
-};
-
-// StatsBase is a utility help for classes (like QuicSession)
-// that record performance statistics. The template takes a
-// single Traits argument (see QuicStreamStatsTraits in
-// node_quic_stream.h as an example). When the StatsBase
-// is deconstructed, collected statistics are output to
-// Debug automatically.
-template <typename T>
-class StatsBase {
- public:
- typedef typename T::Stats Stats;
-
- // A StatsBase instance may have one of three histogram
- // instances. One that records rate of data flow, one
- // that records size of data chunk, and one that records
- // rate of data acknowledgement. These may be used in
- // slightly different ways of different StatsBase
- // instances or may be turned off entirely.
- enum HistogramOptions {
- NONE = 0,
- RATE = 1,
- SIZE = 2,
- ACK = 4
- };
-
- inline StatsBase(
- Environment* env,
- v8::Local<v8::Object> wrap,
- int options = HistogramOptions::NONE);
-
- inline ~StatsBase() { if (stats_ != nullptr) stats_->~Stats(); }
-
- // The StatsDebug utility is used when StatsBase is destroyed
- // to output statistical information to Debug. It is designed
- // to only incur a performance cost constructing the debug
- // output when Debug output is enabled.
- struct StatsDebug {
- typename T::Base* ptr;
- explicit StatsDebug(typename T::Base* ptr_) : ptr(ptr_) {}
- std::string ToString() const;
- };
-
- // Increments the given stat field by the given amount or 1 if
- // no amount is specified.
- inline void IncrementStat(uint64_t Stats::*member, uint64_t amount = 1);
-
- // Sets an entirely new value for the given stat field
- inline void SetStat(uint64_t Stats::*member, uint64_t value);
-
- // Sets the given stat field to the current uv_hrtime()
- inline void RecordTimestamp(uint64_t Stats::*member);
-
- // Gets the current value of the given stat field
- inline uint64_t GetStat(uint64_t Stats::*member) const;
-
- // If the rate histogram is used, records the time elapsed
- // between now and the timestamp specified by the member
- // field.
- inline void RecordRate(uint64_t Stats::*member);
-
- // If the size histogram is used, records the given size.
- inline void RecordSize(uint64_t val);
-
- // If the ack rate histogram is used, records the time
- // elapsed between now and the timestamp specified by
- // the member field.
- inline void RecordAck(uint64_t Stats::*member);
-
- inline void StatsMemoryInfo(MemoryTracker* tracker) const;
-
- inline void DebugStats();
-
- private:
- BaseObjectPtr<HistogramBase> rate_;
- BaseObjectPtr<HistogramBase> size_;
- BaseObjectPtr<HistogramBase> ack_;
- std::shared_ptr<v8::BackingStore> stats_store_;
- Stats* stats_ = nullptr;
-};
-
-// PreferredAddress is a helper class used only when a client QuicSession
-// receives an advertised preferred address from a server. The helper provides
-// information about the preferred address. The Use() function is used to let
-// ngtcp2 know to use the preferred address for the given family.
-class PreferredAddress {
- public:
- PreferredAddress(
- Environment* env,
- ngtcp2_addr* dest,
- const ngtcp2_preferred_addr* paddr) :
- env_(env),
- dest_(dest),
- paddr_(paddr) {}
-
- // When a preferred address is advertised by a server, the
- // advertisement also includes a new CID and (optionally)
- // a stateless reset token. If the preferred address is
- // selected, then the client QuicSession will make use of
- // these new values. Access to the cid and reset token
- // are provided via the PreferredAddress class only as a
- // convenience.
- inline const ngtcp2_cid* cid() const;
-
- // The stateless reset token associated with the preferred
- // address CID
- inline const uint8_t* stateless_reset_token() const;
-
- // A preferred address advertisement may include both an
- // IPv4 and IPv6 address. Only one of which will be used.
-
- inline std::string ipv4_address() const;
-
- inline uint16_t ipv4_port() const;
-
- inline std::string ipv6_address() const;
-
- inline uint16_t ipv6_port() const;
-
- // Instructs the QuicSession to use the advertised
- // preferred address matching the given family. If
- // the advertisement does not include a matching
- // address, the preferred address is ignored.
- inline bool Use(int family = AF_INET) const;
-
- private:
- inline bool ResolvePreferredAddress(
- int local_address_family,
- uv_getaddrinfo_t* req) const;
-
- Environment* env_;
- mutable ngtcp2_addr* dest_;
- const ngtcp2_preferred_addr* paddr_;
-};
-
-// QuicError is a helper class used to encapsulate basic
-// details about a QUIC protocol error. There are three
-// basic types of errors (see QuicErrorFamily)
-struct QuicError {
- int32_t family;
- uint64_t code;
- inline QuicError(
- int32_t family_ = QUIC_ERROR_SESSION,
- int code_ = NGTCP2_NO_ERROR);
- inline QuicError(
- int32_t family_ = QUIC_ERROR_SESSION,
- uint64_t code_ = NGTCP2_NO_ERROR);
- explicit inline QuicError(ngtcp2_connection_close_error_code code);
- inline QuicError(
- Environment* env,
- v8::Local<v8::Value> codeArg,
- v8::Local<v8::Value> familyArg = v8::Local<v8::Object>(),
- int32_t family_ = QUIC_ERROR_SESSION);
- inline const char* family_name();
-};
-
-// Helper function that returns the maximum QUIC packet size for
-// the given socket address.
-inline size_t GetMaxPktLen(const SocketAddress& addr);
-
-// QuicPath is a utility class that wraps ngtcp2_path to adapt
-// it to work with SocketAddress
-struct QuicPath : public ngtcp2_path {
- inline QuicPath(const SocketAddress& local, const SocketAddress& remote);
-};
-
-struct QuicPathStorage : public ngtcp2_path_storage {
- QuicPathStorage() {
- ngtcp2_path_storage_zero(this);
- }
-};
-
-// Simple wrapper for ngtcp2_cid that handles hex encoding
-// CIDs are used to identify QuicSession instances and may
-// be between 0 and 20 bytes in length.
-class QuicCID : public MemoryRetainer {
- public:
- // Empty constructor
- QuicCID() : ptr_(&cid_) {}
-
- // Copy constructor
- QuicCID(const QuicCID& cid) : QuicCID(cid->data, cid->datalen) {}
-
- // Copy constructor
- explicit QuicCID(const ngtcp2_cid& cid) : QuicCID(cid.data, cid.datalen) {}
-
- // Wrap constructor
- explicit QuicCID(const ngtcp2_cid* cid) : ptr_(cid) {}
-
- QuicCID(const uint8_t* cid, size_t len) : QuicCID() {
- ngtcp2_cid* ptr = this->cid();
- ngtcp2_cid_init(ptr, cid, len);
- ptr_ = ptr;
- }
-
- struct Hash {
- inline size_t operator()(const QuicCID& cid) const;
- };
-
- inline bool operator==(const QuicCID& other) const;
- inline bool operator!=(const QuicCID& other) const;
- inline QuicCID& operator=(const QuicCID& cid);
- const ngtcp2_cid& operator*() const { return *ptr_; }
- const ngtcp2_cid* operator->() const { return ptr_; }
-
- inline std::string ToString() const;
-
- const ngtcp2_cid* cid() const { return ptr_; }
-
- const uint8_t* data() const { return ptr_->data; }
-
- operator bool() const { return ptr_->datalen > 0; }
-
- size_t length() const { return ptr_->datalen; }
-
- ngtcp2_cid* cid() {
- CHECK_EQ(ptr_, &cid_);
- return &cid_;
- }
-
- unsigned char* data() {
- return reinterpret_cast<unsigned char*>(cid()->data);
- }
-
- void set_length(size_t length) {
- cid()->datalen = length;
- }
-
- SET_NO_MEMORY_INFO()
- SET_MEMORY_INFO_NAME(QuicCID)
- SET_SELF_SIZE(QuicCID)
-
- template <typename T>
- using Map = std::unordered_map<QuicCID, T, QuicCID::Hash>;
-
- private:
- ngtcp2_cid cid_{};
- const ngtcp2_cid* ptr_;
-};
-
-// A Stateless Reset Token is a mechanism by which a QUIC
-// endpoint can discreetly signal to a peer that it has
-// lost all state associated with a connection. This
-// helper class is used to both store received tokens and
-// provide storage when creating new tokens to send.
-class StatelessResetToken : public MemoryRetainer {
- public:
- inline StatelessResetToken(
- uint8_t* token,
- const uint8_t* secret,
- const QuicCID& cid);
-
- inline StatelessResetToken(
- const uint8_t* secret,
- const QuicCID& cid);
-
- explicit inline StatelessResetToken(
- const uint8_t* token);
-
- inline std::string ToString() const;
-
- const uint8_t* data() const { return buf_; }
-
- struct Hash {
- inline size_t operator()(const StatelessResetToken& token) const;
- };
-
- inline bool operator==(const StatelessResetToken& other) const;
- inline bool operator!=(const StatelessResetToken& other) const;
-
- SET_NO_MEMORY_INFO()
- SET_MEMORY_INFO_NAME(StatelessResetToken)
- SET_SELF_SIZE(StatelessResetToken)
-
- template <typename T>
- using Map =
- std::unordered_map<
- StatelessResetToken,
- BaseObjectPtr<T>,
- StatelessResetToken::Hash>;
-
- private:
- uint8_t buf_[NGTCP2_STATELESS_RESET_TOKENLEN]{};
-};
-
-template <typename T>
-inline size_t get_length(const T*, size_t len);
-
-} // namespace quic
-} // namespace node
-
-#endif // NOE_WANT_INTERNALS
-
-#endif // SRC_QUIC_NODE_QUIC_UTIL_H_