diff --git a/lib/internal/quic/core.js b/lib/internal/quic/core.js index 1dde3a2c84..a4b5c8e661 100644 --- a/lib/internal/quic/core.js +++ b/lib/internal/quic/core.js @@ -262,8 +262,7 @@ function onSessionClose(code, family) { // some basic information about the ALPN, SNI, and Ciphers that are // being requested. It is only called if the 'clientHello' event is // listened for. -function onSessionClientHello(alpn, servername, ciphers, callback) { - callback = callback.bind(this); +function onSessionClientHello(alpn, servername, ciphers) { this[owner_symbol][kClientHello]( alpn, servername, @@ -274,7 +273,7 @@ function onSessionClientHello(alpn, servername, ciphers, callback) { return; } try { - callback(...args); + this.onClientHelloDone(...args); } catch (err) { this[owner_symbol].destroy(err); } @@ -283,10 +282,9 @@ function onSessionClientHello(alpn, servername, ciphers, callback) { // This callback is only ever invoked for QuicServerSession instances, // and is used to trigger OCSP request processing when needed. The -// user callback must invoke the callback function in order for the +// user callback must invoke .onCertDone() in order for the // TLS handshake to continue. -function onSessionCert(servername, callback) { - callback = callback.bind(this); +function onSessionCert(servername) { this[owner_symbol][kCert](servername, (err, context, ocspResponse) => { if (err) { this[owner_symbol].destroy(err); @@ -311,7 +309,7 @@ function onSessionCert(servername, callback) { } } try { - callback(context ? context.context : undefined, ocspResponse); + this.onCertDone(context ? context.context : undefined, ocspResponse); } catch (err) { this[owner_symbol].destroy(err); } diff --git a/node.gyp b/node.gyp index b1da9cbddb..2e6c6225c3 100644 --- a/node.gyp +++ b/node.gyp @@ -515,6 +515,7 @@ 'src/fs_event_wrap.cc', 'src/handle_wrap.cc', 'src/heap_utils.cc', + 'src/histogram.cc', 'src/js_native_api.h', 'src/js_native_api_types.h', 'src/js_native_api_v8.cc', @@ -621,6 +622,7 @@ 'src/node_internals.h', 'src/node_main_instance.h', 'src/node_mem.h', + 'src/node_mem-inl.h', 'src/node_messaging.h', 'src/node_metadata.h', 'src/node_mutex.h', diff --git a/src/histogram-inl.h b/src/histogram-inl.h index fb27c675d8..f341fab6d6 100644 --- a/src/histogram-inl.h +++ b/src/histogram-inl.h @@ -9,45 +9,46 @@ namespace node { -inline Histogram::Histogram(int64_t lowest, int64_t highest, int figures) { +Histogram::Histogram(int64_t lowest, int64_t highest, int figures) { CHECK_EQ(0, hdr_init(lowest, highest, figures, &histogram_)); } -inline Histogram::~Histogram() { +Histogram::~Histogram() { hdr_close(histogram_); } -inline void Histogram::Reset() { +void Histogram::Reset() { hdr_reset(histogram_); } -inline bool Histogram::Record(int64_t value) { +bool Histogram::Record(int64_t value) { return hdr_record_value(histogram_, value); } -inline int64_t Histogram::Min() { +int64_t Histogram::Min() { return hdr_min(histogram_); } -inline int64_t Histogram::Max() { +int64_t Histogram::Max() { return hdr_max(histogram_); } -inline double Histogram::Mean() { +double Histogram::Mean() { return hdr_mean(histogram_); } -inline double Histogram::Stddev() { +double Histogram::Stddev() { return hdr_stddev(histogram_); } -inline double Histogram::Percentile(double percentile) { +double Histogram::Percentile(double percentile) { CHECK_GT(percentile, 0); CHECK_LE(percentile, 100); return static_cast(hdr_value_at_percentile(histogram_, percentile)); } -inline void Histogram::Percentiles(std::function fn) { +template +void Histogram::Percentiles(Iterator&& fn) { hdr_iter iter; hdr_iter_percentile_init(&iter, histogram_, 1); while (hdr_iter_next(&iter)) { @@ -57,7 +58,7 @@ inline void Histogram::Percentiles(std::function fn) { } } -inline HistogramBase::HistogramBase( +HistogramBase::HistogramBase( Environment* env, v8::Local wrap, int64_t lowest, @@ -66,7 +67,7 @@ inline HistogramBase::HistogramBase( BaseObject(env, wrap), Histogram(lowest, highest, figures) {} -inline bool HistogramBase::RecordDelta() { +bool HistogramBase::RecordDelta() { uint64_t time = uv_hrtime(); bool ret = true; if (prev_ > 0) { @@ -85,110 +86,13 @@ inline bool HistogramBase::RecordDelta() { return ret; } -inline void HistogramBase::ResetState() { +void HistogramBase::ResetState() { Reset(); exceeds_ = 0; prev_ = 0; } -inline void HistogramBase::HistogramMin( - const v8::FunctionCallbackInfo& args) { - HistogramBase* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - double value = static_cast(histogram->Min()); - args.GetReturnValue().Set(value); -} - -inline void HistogramBase::HistogramMax( - const v8::FunctionCallbackInfo& args) { - HistogramBase* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - double value = static_cast(histogram->Max()); - args.GetReturnValue().Set(value); -} - -inline void HistogramBase::HistogramMean( - const v8::FunctionCallbackInfo& args) { - HistogramBase* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - args.GetReturnValue().Set(histogram->Mean()); -} - -inline void HistogramBase::HistogramExceeds( - const v8::FunctionCallbackInfo& args) { - HistogramBase* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - double value = static_cast(histogram->Exceeds()); - args.GetReturnValue().Set(value); -} - -inline void HistogramBase::HistogramStddev( - const v8::FunctionCallbackInfo& args) { - HistogramBase* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - args.GetReturnValue().Set(histogram->Stddev()); -} - -inline void HistogramBase::HistogramPercentile( - const v8::FunctionCallbackInfo& args) { - HistogramBase* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - CHECK(args[0]->IsNumber()); - double percentile = args[0].As()->Value(); - args.GetReturnValue().Set(histogram->Percentile(percentile)); -} - -inline void HistogramBase::HistogramPercentiles( - const v8::FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - HistogramBase* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - CHECK(args[0]->IsMap()); - v8::Local map = args[0].As(); - histogram->Percentiles([&](double key, double value) { - map->Set( - env->context(), - v8::Number::New(env->isolate(), key), - v8::Number::New(env->isolate(), value)).IsEmpty(); - }); -} - -inline void HistogramBase::HistogramReset( - const v8::FunctionCallbackInfo& args) { - HistogramBase* histogram; - ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - histogram->ResetState(); -} - -inline void HistogramBase::Initialize(Environment* env) { - // Guard against multiple initializations - if (!env->histogram_ctor_template().IsEmpty()) - return; - - v8::Local classname = - FIXED_ONE_BYTE_STRING(env->isolate(), "Histogram"); - - v8::Local histogram = - v8::FunctionTemplate::New(env->isolate()); - histogram->SetClassName(classname); - - v8::Local histogramt = - histogram->InstanceTemplate(); - - histogramt->SetInternalFieldCount(1); - env->SetProtoMethod(histogram, "exceeds", HistogramExceeds); - env->SetProtoMethod(histogram, "min", HistogramMin); - env->SetProtoMethod(histogram, "max", HistogramMax); - env->SetProtoMethod(histogram, "mean", HistogramMean); - env->SetProtoMethod(histogram, "stddev", HistogramStddev); - env->SetProtoMethod(histogram, "percentile", HistogramPercentile); - env->SetProtoMethod(histogram, "percentiles", HistogramPercentiles); - env->SetProtoMethod(histogram, "reset", HistogramReset); - - env->set_histogram_ctor_template(histogramt); -} - -inline HistogramBase* HistogramBase::New( +HistogramBase* HistogramBase::New( Environment* env, int64_t lowest, int64_t highest, diff --git a/src/histogram.cc b/src/histogram.cc new file mode 100644 index 0000000000..253fd58f82 --- /dev/null +++ b/src/histogram.cc @@ -0,0 +1,111 @@ +#include "histogram.h" // NOLINT(build/include_inline) +#include "histogram-inl.h" +#include "memory_tracker-inl.h" + +namespace node { + +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Local; +using v8::Map; +using v8::Number; +using v8::ObjectTemplate; +using v8::String; +using v8::Value; + +void HistogramBase::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackFieldWithSize("histogram", GetMemorySize()); +} + +void HistogramBase::HistogramMin(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast(histogram->Min()); + args.GetReturnValue().Set(value); +} + +void HistogramBase::HistogramMax(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast(histogram->Max()); + args.GetReturnValue().Set(value); +} + +void HistogramBase::HistogramMean(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set(histogram->Mean()); +} + +void HistogramBase::HistogramExceeds(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast(histogram->Exceeds()); + args.GetReturnValue().Set(value); +} + +void HistogramBase::HistogramStddev(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set(histogram->Stddev()); +} + +void HistogramBase::HistogramPercentile( + const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + CHECK(args[0]->IsNumber()); + double percentile = args[0].As()->Value(); + args.GetReturnValue().Set(histogram->Percentile(percentile)); +} + +void HistogramBase::HistogramPercentiles( + const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + CHECK(args[0]->IsMap()); + Local map = args[0].As(); + histogram->Percentiles([&](double key, double value) { + map->Set( + env->context(), + Number::New(env->isolate(), key), + Number::New(env->isolate(), value)).IsEmpty(); + }); +} + +void HistogramBase::HistogramReset(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + histogram->ResetState(); +} + +void HistogramBase::Initialize(Environment* env) { + // Guard against multiple initializations + if (!env->histogram_ctor_template().IsEmpty()) + return; + + Local classname = + FIXED_ONE_BYTE_STRING(env->isolate(), "Histogram"); + + Local histogram = + FunctionTemplate::New(env->isolate()); + histogram->SetClassName(classname); + + Local histogramt = + histogram->InstanceTemplate(); + + histogramt->SetInternalFieldCount(1); + env->SetProtoMethod(histogram, "exceeds", HistogramExceeds); + env->SetProtoMethod(histogram, "min", HistogramMin); + env->SetProtoMethod(histogram, "max", HistogramMax); + env->SetProtoMethod(histogram, "mean", HistogramMean); + env->SetProtoMethod(histogram, "stddev", HistogramStddev); + env->SetProtoMethod(histogram, "percentile", HistogramPercentile); + env->SetProtoMethod(histogram, "percentiles", HistogramPercentiles); + env->SetProtoMethod(histogram, "reset", HistogramReset); + + env->set_histogram_ctor_template(histogramt); +} + +} // namespace node diff --git a/src/histogram.h b/src/histogram.h index 7d8fd2140b..117b214e87 100644 --- a/src/histogram.h +++ b/src/histogram.h @@ -22,7 +22,11 @@ class Histogram { inline double Mean(); inline double Stddev(); inline double Percentile(double percentile); - inline void Percentiles(std::function fn); + + // Iterator is a function type that takes two doubles as argument, one for + // percentile and one for the value at that percentile. + template + inline void Percentiles(Iterator&& fn); size_t GetMemorySize() const { return hdr_get_memory_size(histogram_); @@ -34,57 +38,31 @@ class Histogram { class HistogramBase : public BaseObject, public Histogram { public: - inline HistogramBase( - Environment* env, - v8::Local wrap, - int64_t lowest, - int64_t highest, - int figures = 3); - - inline virtual ~HistogramBase() {} + virtual ~HistogramBase() = default; inline virtual void TraceDelta(int64_t delta) {} - inline virtual void TraceExceeds(int64_t delta) {} inline bool RecordDelta(); - inline void ResetState(); - inline int64_t Exceeds() { return exceeds_; } - - inline void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackFieldWithSize("histogram", GetMemorySize()); - } + int64_t Exceeds() { return exceeds_; } + void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(HistogramBase) SET_SELF_SIZE(HistogramBase) - static inline void HistogramMin( - const v8::FunctionCallbackInfo& args); - - static inline void HistogramMax( - const v8::FunctionCallbackInfo& args); - - static inline void HistogramMean( + static void HistogramMin(const v8::FunctionCallbackInfo& args); + static void HistogramMax(const v8::FunctionCallbackInfo& args); + static void HistogramMean(const v8::FunctionCallbackInfo& args); + static void HistogramExceeds(const v8::FunctionCallbackInfo& args); + static void HistogramStddev(const v8::FunctionCallbackInfo& args); + static void HistogramPercentile( const v8::FunctionCallbackInfo& args); - - static inline void HistogramExceeds( + static void HistogramPercentiles( const v8::FunctionCallbackInfo& args); - - static inline void HistogramStddev( - const v8::FunctionCallbackInfo& args); - - static inline void HistogramPercentile( - const v8::FunctionCallbackInfo& args); - - static inline void HistogramPercentiles( - const v8::FunctionCallbackInfo& args); - - static inline void HistogramReset( - const v8::FunctionCallbackInfo& args); - - static inline void Initialize(Environment* env); + static void HistogramReset(const v8::FunctionCallbackInfo& args); + static void Initialize(Environment* env); static inline HistogramBase* New( Environment* env, @@ -93,6 +71,13 @@ class HistogramBase : public BaseObject, public Histogram { int figures = 3); private: + inline HistogramBase( + Environment* env, + v8::Local wrap, + int64_t lowest, + int64_t highest, + int figures = 3); + int64_t exceeds_ = 0; uint64_t prev_ = 0; }; diff --git a/src/node_http2.cc b/src/node_http2.cc index 1065a940f5..7728165b48 100644 --- a/src/node_http2.cc +++ b/src/node_http2.cc @@ -5,6 +5,7 @@ #include "node_buffer.h" #include "node_http2.h" #include "node_http2_state.h" +#include "node_mem-inl.h" #include "node_perf.h" #include "node_revert.h" #include "util-inl.h" @@ -499,101 +500,20 @@ Http2Session::Callbacks::~Callbacks() { nghttp2_session_callbacks_del(callbacks); } -// Track memory allocated by nghttp2 using a custom allocator. -class Http2Session::MemoryAllocatorInfo { - public: - explicit MemoryAllocatorInfo(Http2Session* session) - : info({ session, H2Malloc, H2Free, H2Calloc, H2Realloc }) {} - - static void* H2Malloc(size_t size, void* user_data) { - return H2Realloc(nullptr, size, user_data); - } - - static void* H2Calloc(size_t nmemb, size_t size, void* user_data) { - size_t real_size = MultiplyWithOverflowCheck(nmemb, size); - void* mem = H2Malloc(real_size, user_data); - if (mem != nullptr) - memset(mem, 0, real_size); - return mem; - } - - static void H2Free(void* ptr, void* user_data) { - if (ptr == nullptr) return; // free(null); happens quite often. - void* result = H2Realloc(ptr, 0, user_data); - CHECK_NULL(result); - } - - static void* H2Realloc(void* ptr, size_t size, void* user_data) { - Http2Session* session = static_cast(user_data); - size_t previous_size = 0; - char* original_ptr = nullptr; - - // We prepend each allocated buffer with a size_t containing the full - // size of the allocation. - if (size > 0) size += sizeof(size_t); - - if (ptr != nullptr) { - // We are free()ing or re-allocating. - original_ptr = static_cast(ptr) - sizeof(size_t); - previous_size = *reinterpret_cast(original_ptr); - // This means we called StopTracking() on this pointer before. - if (previous_size == 0) { - // Fall back to the standard Realloc() function. - char* ret = UncheckedRealloc(original_ptr, size); - if (ret != nullptr) - ret += sizeof(size_t); - return ret; - } - } - CHECK_GE(session->current_nghttp2_memory_, previous_size); - - // TODO(addaleax): Add the following, and handle NGHTTP2_ERR_NOMEM properly - // everywhere: - // - // if (size > previous_size && - // !session->IsAvailableSessionMemory(size - previous_size)) { - // return nullptr; - //} - - char* mem = UncheckedRealloc(original_ptr, size); - - if (mem != nullptr) { - // Adjust the memory info counter. - // TODO(addaleax): Avoid the double bookkeeping we do with - // current_nghttp2_memory_ + AdjustAmountOfExternalAllocatedMemory - // and provide versions of our memory allocation utilities that take an - // Environment*/Isolate* parameter and call the V8 method transparently. - const int64_t new_size = size - previous_size; - session->current_nghttp2_memory_ += new_size; - session->env()->isolate()->AdjustAmountOfExternalAllocatedMemory( - new_size); - *reinterpret_cast(mem) = size; - mem += sizeof(size_t); - } else if (size == 0) { - session->current_nghttp2_memory_ -= previous_size; - session->env()->isolate()->AdjustAmountOfExternalAllocatedMemory( - -static_cast(previous_size)); - } - - return mem; - } - - static void StopTracking(Http2Session* session, void* ptr) { - size_t* original_ptr = reinterpret_cast( - static_cast(ptr) - sizeof(size_t)); - session->current_nghttp2_memory_ -= *original_ptr; - session->env()->isolate()->AdjustAmountOfExternalAllocatedMemory( - -static_cast(*original_ptr)); - *original_ptr = 0; - } +void Http2Session::StopTrackingRcbuf(nghttp2_rcbuf* buf) { + StopTrackingMemory(buf); +} - inline nghttp2_mem* operator*() { return &info; } +void Http2Session::CheckAllocatedSize(size_t previous_size) const { + CHECK_GE(current_nghttp2_memory_, previous_size); +} - nghttp2_mem info; -}; +void Http2Session::IncreaseAllocatedSize(size_t size) { + current_nghttp2_memory_ += size; +} -void Http2Session::StopTrackingRcbuf(nghttp2_rcbuf* buf) { - MemoryAllocatorInfo::StopTracking(this, buf); +void Http2Session::DecreaseAllocatedSize(size_t size) { + current_nghttp2_memory_ -= size; } Http2Session::Http2Session(Environment* env, @@ -630,14 +550,14 @@ Http2Session::Http2Session(Environment* env, nghttp2_session_server_new3 : nghttp2_session_client_new3; - MemoryAllocatorInfo allocator_info(this); + nghttp2_mem alloc_info = MakeAllocator(); // This should fail only if the system is out of memory, which // is going to cause lots of other problems anyway, or if any // of the options are out of acceptable range, which we should // be catching before it gets this far. Either way, crash if this // fails. - CHECK_EQ(fn(&session_, callbacks, this, *opts, *allocator_info), 0); + CHECK_EQ(fn(&session_, callbacks, this, *opts, &alloc_info), 0); outgoing_storage_.reserve(1024); outgoing_buffers_.reserve(32); diff --git a/src/node_http2.h b/src/node_http2.h index db85dc6e5a..c27e3dba4b 100644 --- a/src/node_http2.h +++ b/src/node_http2.h @@ -8,6 +8,7 @@ #include "nghttp2/nghttp2.h" #include "node_http2_state.h" +#include "node_mem.h" #include "node_perf.h" #include "stream_base-inl.h" #include "string_bytes.h" @@ -690,7 +691,9 @@ enum SessionBitfieldFlags { kSessionHasAltsvcListeners }; -class Http2Session : public AsyncWrap, public StreamListener { +class Http2Session : public AsyncWrap, + public StreamListener, + public mem::NgLibMemoryManager { public: Http2Session(Environment* env, Local wrap, @@ -699,7 +702,6 @@ class Http2Session : public AsyncWrap, public StreamListener { class Http2Ping; class Http2Settings; - class MemoryAllocatorInfo; void EmitStatistics(); @@ -800,6 +802,11 @@ class Http2Session : public AsyncWrap, public StreamListener { void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override; void OnStreamAfterWrite(WriteWrap* w, int status) override; + // Implementation for mem::NgLibMemoryManager + void CheckAllocatedSize(size_t previous_size) const; + void IncreaseAllocatedSize(size_t size); + void DecreaseAllocatedSize(size_t size); + // The JavaScript API static void New(const FunctionCallbackInfo& args); static void Consume(const FunctionCallbackInfo& args); diff --git a/src/node_mem-inl.h b/src/node_mem-inl.h new file mode 100644 index 0000000000..ad6fc45b36 --- /dev/null +++ b/src/node_mem-inl.h @@ -0,0 +1,112 @@ +#ifndef SRC_NODE_MEM_INL_H_ +#define SRC_NODE_MEM_INL_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node_mem.h" +#include "node_internals.h" + +namespace node { +namespace mem { + +template +AllocatorStruct NgLibMemoryManager::MakeAllocator() { + return AllocatorStruct { + static_cast(static_cast(this)), + MallocImpl, + FreeImpl, + CallocImpl, + ReallocImpl + }; +} + +template +void* NgLibMemoryManager::ReallocImpl(void* ptr, + size_t size, + void* user_data) { + Class* manager = static_cast(user_data); + + size_t previous_size = 0; + char* original_ptr = nullptr; + + // We prepend each allocated buffer with a size_t containing the full + // size of the allocation. + if (size > 0) size += sizeof(size_t); + + if (ptr != nullptr) { + // We are free()ing or re-allocating. + original_ptr = static_cast(ptr) - sizeof(size_t); + previous_size = *reinterpret_cast(original_ptr); + // This means we called StopTracking() on this pointer before. + if (previous_size == 0) { + // Fall back to the standard Realloc() function. + char* ret = UncheckedRealloc(original_ptr, size); + if (ret != nullptr) + ret += sizeof(size_t); + return ret; + } + } + + manager->CheckAllocatedSize(previous_size); + + char* mem = UncheckedRealloc(original_ptr, size); + + if (mem != nullptr) { + // Adjust the memory info counter. + // TODO(addaleax): Avoid the double bookkeeping we do with + // current_nghttp2_memory_ + AdjustAmountOfExternalAllocatedMemory + // and provide versions of our memory allocation utilities that take an + // Environment*/Isolate* parameter and call the V8 method transparently. + const int64_t new_size = size - previous_size; + manager->IncreaseAllocatedSize(new_size); + manager->env()->isolate()->AdjustAmountOfExternalAllocatedMemory( + new_size); + *reinterpret_cast(mem) = size; + mem += sizeof(size_t); + } else if (size == 0) { + manager->DecreaseAllocatedSize(previous_size); + manager->env()->isolate()->AdjustAmountOfExternalAllocatedMemory( + -static_cast(previous_size)); + } + return mem; +} + +template +void* NgLibMemoryManager::MallocImpl(size_t size, void* user_data) { + return ReallocImpl(nullptr, size, user_data); +} + +template +void NgLibMemoryManager::FreeImpl(void* ptr, void* user_data) { + if (ptr == nullptr) return; + CHECK_NULL(ReallocImpl(ptr, 0, user_data)); +} + +template +void* NgLibMemoryManager::CallocImpl(size_t nmemb, + size_t size, + void* user_data) { + size_t real_size = MultiplyWithOverflowCheck(nmemb, size); + void* mem = MallocImpl(real_size, user_data); + if (mem != nullptr) + memset(mem, 0, real_size); + return mem; +} + +template +void NgLibMemoryManager::StopTrackingMemory(void* ptr) { + size_t* original_ptr = reinterpret_cast( + static_cast(ptr) - sizeof(size_t)); + Class* manager = static_cast(this); + manager->DecreaseAllocatedSize(*original_ptr); + manager->env()->isolate()->AdjustAmountOfExternalAllocatedMemory( + -static_cast(*original_ptr)); + *original_ptr = 0; +} + +} // namespace mem +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_MEM_INL_H_ diff --git a/src/node_mem.h b/src/node_mem.h index a73fa1998f..1c55f04a31 100644 --- a/src/node_mem.h +++ b/src/node_mem.h @@ -3,7 +3,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include "node_internals.h" +#include namespace node { namespace mem { @@ -11,97 +11,26 @@ namespace mem { // Both ngtcp2 and nghttp2 allow custom allocators that // follow exactly the same structure and behavior, but // use different struct names. To allow for code re-use, -// the Allocator template class can be used for both. +// the NgLibMemoryManager template class can be used for both. -struct Tracker { - virtual void CheckAllocatedSize(size_t previous_size) = 0; - virtual void IncrementAllocatedSize(size_t size) = 0; - virtual void DecrementAllocatedSize(size_t size) = 0; -}; - -inline static void* MemRealloc( - void* ptr, - size_t size, - void* user_data) { - - Tracker* tracker = static_cast(user_data); - - size_t previous_size = 0; - char* original_ptr = nullptr; - - if (size > 0) size += sizeof(size_t); - - if (ptr != nullptr) { - original_ptr = static_cast(ptr) - sizeof(size_t); - previous_size = *reinterpret_cast(original_ptr); - if (previous_size == 0) { - char* ret = UncheckedRealloc(original_ptr, size); - if (ret != nullptr) - ret += sizeof(size_t); - return ret; - } - } - - tracker->CheckAllocatedSize(previous_size); - - char* mem = UncheckedRealloc(original_ptr, size); - - if (mem != nullptr) { - tracker->IncrementAllocatedSize(size - previous_size); - *reinterpret_cast(mem) = size; - mem += sizeof(size_t); - } else if (size == 0) { - tracker->DecrementAllocatedSize(previous_size); - } - return mem; -} - -inline static void* MemMalloc( - size_t size, - void* user_data) { - return MemRealloc(nullptr, size, user_data); -} - -inline static void MemFree( - void* ptr, - void* user_data) { - if (ptr == nullptr) return; - CHECK_NULL(MemRealloc(ptr, 0, user_data)); -} - -inline static void* MemCalloc( - size_t nmemb, - size_t size, - void* user_data) { - size_t real_size = MultiplyWithOverflowCheck(nmemb, size); - void* mem = MemMalloc(real_size, user_data); - if (mem != nullptr) - memset(mem, 0, real_size); - return mem; -} +template +class NgLibMemoryManager { + public: + // Class needs to provide these methods: + // void CheckAllocatedSize(size_t previous_size) const; + // void IncreaseAllocatedSize(size_t size); + // void DecreaseAllocatedSize(size_t size); + // Environment* env() const; -inline static void MemStopTracking(Tracker* tracker, void* ptr) { - size_t* original_ptr = reinterpret_cast( - static_cast(ptr) - sizeof(size_t)); - tracker->DecrementAllocatedSize(*original_ptr); - *original_ptr = 0; -} + AllocatorStructName MakeAllocator(); -template -class Allocator { - public: - explicit inline Allocator(Tracker* user_data) : - info_({ - user_data, - MemMalloc, - MemFree, - MemCalloc, - MemRealloc - }) {} + void StopTrackingMemory(void* ptr); - inline T* operator*() { return &info_; } private: - T info_; + static void* ReallocImpl(void* ptr, size_t size, void* user_data); + static void* MallocImpl(size_t size, void* user_data); + static void FreeImpl(void* ptr, void* user_data); + static void* CallocImpl(size_t nmemb, size_t size, void* user_data); }; } // namespace mem diff --git a/src/node_quic_buffer.h b/src/node_quic_buffer.h index db51277d28..ec29699c8a 100644 --- a/src/node_quic_buffer.h +++ b/src/node_quic_buffer.h @@ -24,12 +24,14 @@ namespace quic { // For QUIC, the data is not consumed until an explicit ack // is received or we know that we do not need the data. -typedef std::function done_cb; +typedef std::function done_cb; // Default non-op done handler. -inline void default_quic_buffer_chunk_done(int status, void* user_data) {} +inline void default_quic_buffer_chunk_done(int status) {} -#define EMPTY_BUF(buf) (buf.len == 0 || buf.base == nullptr) +inline bool IsEmptyBuffer(const uv_buf_t& buf) { + return buf.len == 0 || buf.base == nullptr; +} // A quic_buffer_chunk contains the actual buffered data // along with a callback, and optional V8 object that @@ -40,49 +42,40 @@ struct quic_buffer_chunk : public MemoryRetainer { done_cb done = default_quic_buffer_chunk_done; size_t offset = 0; size_t roffset = 0; - void* user_data = nullptr; bool done_called = false; v8::Global keep_alive; std::unique_ptr next; - inline quic_buffer_chunk( - MallocedBuffer&& buf_, - done_cb done_, - void* user_data_, - v8::Local keep_alive_) : - data_buf(std::move(buf_)), - buf(uv_buf_init( - reinterpret_cast(data_buf.data), - data_buf.size)), - done(done_), - user_data(user_data_) { - if (!keep_alive.IsEmpty()) - keep_alive.Reset(keep_alive_->GetIsolate(), keep_alive_); + quic_buffer_chunk( + MallocedBuffer&& buf_, + done_cb done_, + v8::Local keep_alive_) + : quic_buffer_chunk(uv_buf_init(reinterpret_cast(buf_.data), + buf_.size), + done_, + keep_alive_) { + data_buf = std::move(buf_); } - inline explicit quic_buffer_chunk( - uv_buf_t buf_) : - buf(buf_) {} - - inline quic_buffer_chunk( - uv_buf_t buf_, - done_cb done_, - void* user_data_, - v8::Local keep_alive_) : - buf(buf_), - done(done_), - user_data(user_data_) { + explicit quic_buffer_chunk(uv_buf_t buf_) : buf(buf_) {} + + quic_buffer_chunk( + uv_buf_t buf_, + done_cb done_, + v8::Local keep_alive_) + : quic_buffer_chunk(buf_) { + done = std::move(done_); if (!keep_alive.IsEmpty()) keep_alive.Reset(keep_alive_->GetIsolate(), keep_alive_); } - inline ~quic_buffer_chunk() override { + ~quic_buffer_chunk() override { CHECK(done_called); } void Done(int status) { done_called = true; - done(status, user_data); + std::move(done)(status); } void MemoryInfo(MemoryTracker* tracker) const override { @@ -130,21 +123,21 @@ struct quic_buffer_chunk : public MemoryRetainer { // Will append the contents of buf1 to buf2, then reset buf1 class QuicBuffer : public MemoryRetainer { public: - inline QuicBuffer() : - head_(nullptr), - tail_(nullptr), - size_(0), - count_(0), - length_(0), - rlength_(0) {} - - inline QuicBuffer(QuicBuffer&& src) noexcept : - head_(src.head_), - tail_(src.tail_), - size_(src.size_), - count_(src.count_), - length_(src.length_), - rlength_(src.rlength_) { + QuicBuffer() + : head_(nullptr), + tail_(nullptr), + size_(0), + count_(0), + length_(0), + rlength_(0) {} + + QuicBuffer(QuicBuffer&& src) noexcept + : head_(src.head_), + tail_(src.tail_), + size_(src.size_), + count_(src.count_), + length_(src.length_), + rlength_(src.rlength_) { root_ = std::move(src.root_); src.head_ = nullptr; src.tail_ = nullptr; @@ -154,44 +147,42 @@ class QuicBuffer : public MemoryRetainer { } QuicBuffer& operator=(QuicBuffer&& src) noexcept { + if (this == &src) return *this; this->~QuicBuffer(); return *new(this) QuicBuffer(std::move(src)); } QuicBuffer& operator+=(QuicBuffer&& src) noexcept { - if (!tail_) { + if (tail_ == nullptr) { // If this thing is empty, just do a move... - this->~QuicBuffer(); - return *new(this) QuicBuffer(std::move(src)); - } else { - tail_->next = std::move(src.root_); - // If head_ is null, then it had been read to the - // end, set the new head_ equal to the appended - // root. - if (head_ == nullptr) - head_ = tail_->next.get(); - tail_ = src.tail_; - length_ += src.length_; - rlength_ += src.length_; - size_ += src.size_; - count_ += src.size_; - src.head_ = nullptr; - src.tail_ = nullptr; - src.size_ = 0; - src.length_ = 0; - src.rlength_ = 0; - return *this; + return *this = std::move(src); } + + tail_->next = std::move(src.root_); + // If head_ is null, then it had been read to the + // end, set the new head_ equal to the appended + // root. + if (head_ == nullptr) + head_ = tail_->next.get(); + tail_ = src.tail_; + length_ += src.length_; + rlength_ += src.length_; + size_ += src.size_; + count_ += src.size_; + src.head_ = nullptr; + src.tail_ = nullptr; + src.size_ = 0; + src.length_ = 0; + src.rlength_ = 0; + return *this; } - inline ~QuicBuffer() override { + ~QuicBuffer() override { Cancel(); // Cancel the remaining data CHECK_EQ(length_, 0); } - inline size_t Copy( - uv_buf_t* bufs, - size_t nbufs) { + size_t Copy(uv_buf_t* bufs, size_t nbufs) { size_t total = 0; for (size_t n = 0; n < nbufs; n++) { MallocedBuffer data(bufs[n].len); @@ -205,26 +196,22 @@ class QuicBuffer : public MemoryRetainer { // Push one or more uv_buf_t instances into the buffer. // the done_cb callback will be invoked when the last // uv_buf_t in the bufs array is consumed and popped out - // of the internal linked list. The user_data is passed in to - // the done_cb. The keep_alive allows a reference to a + // of the internal linked list. The keep_alive allows a reference to a // JS object to be kept around until the final uv_buf_t // is consumed. - inline size_t Push( + size_t Push( uv_buf_t* bufs, size_t nbufs, done_cb done = default_quic_buffer_chunk_done, - void* user_data = nullptr, v8::Local keep_alive = v8::Local()) { size_t len = 0; - if (nbufs == 0 || - bufs == nullptr || - EMPTY_BUF(bufs[0])) { - done(0, user_data); + if (nbufs == 0 || bufs == nullptr || IsEmptyBuffer(bufs[0])) { + done(0); return 0; } size_t n = 0; while (nbufs > 1) { - if (!EMPTY_BUF(bufs[n])) { + if (!IsEmptyBuffer(bufs[n])) { Push(bufs[n]); length_ += bufs[n].len; rlength_ += bufs[n].len; @@ -236,112 +223,78 @@ class QuicBuffer : public MemoryRetainer { length_ += bufs[n].len; rlength_ += bufs[n].len; len += bufs[n].len; - Push(bufs[n], done, user_data, keep_alive); + Push(bufs[n], done, keep_alive); return len; } // Push a single malloc buf into the buffer. // The done_cb will be invoked when the buf is consumed - // and popped out of the internal linked list. The user_data - // is passed into the done_cb. The keep_alive allows a + // and popped out of the internal linked list. The keep_alive allows a // reference to a JS object to be kept around until the // final uv_buf_t is consumed. - inline size_t Push( + size_t Push( MallocedBuffer&& buffer, done_cb done = default_quic_buffer_chunk_done, - void* user_data = nullptr, v8::Local keep_alive = v8::Local()) { if (buffer.size == 0) { - done(0, user_data); + done(0); return 0; } length_ += buffer.size; rlength_ += buffer.size; - Push(new quic_buffer_chunk(std::move(buffer), done, user_data, keep_alive)); + Push(new quic_buffer_chunk(std::move(buffer), done, keep_alive)); return buffer.size; } // Consume the given number of bytes within the buffer. If amount is // negative, all buffered bytes that are available to be consumed are // consumed. - inline void Consume(ssize_t amount = -1) { Consume(0, amount); } + void Consume(ssize_t amount = -1) { Consume(0, amount); } // Cancels the remaining bytes within the buffer - inline size_t Cancel(int status = UV_ECANCELED) { + size_t Cancel(int status = UV_ECANCELED) { size_t remaining = Length(); Consume(status, -1); return remaining; } // The total buffered bytes - inline size_t Length() { + size_t Length() { return length_; } - inline size_t RemainingLength() { + size_t RemainingLength() { return rlength_; } // The total number of buffers - inline size_t Size() { + size_t Size() { return size_; } // The number of buffers remaining to be read - inline size_t ReadRemaining() { + size_t ReadRemaining() { return count_; } // Drain the remaining buffers into the given vector. // The function will return the number of positions the // read head_ can be advanced. - inline size_t DrainInto( - std::vector* list, - size_t* length = nullptr) { - size_t len = 0; - bool seen_head = false; - quic_buffer_chunk* pos = head_; - if (pos == nullptr) - return 0; - if (length != nullptr) *length = 0; - while (pos != nullptr) { - size_t datalen = pos->buf.len - pos->roffset; - if (length != nullptr) *length += datalen; - list->push_back( - uv_buf_init(pos->buf.base + pos->roffset, datalen)); - if (pos == head_) seen_head = true; - if (seen_head) len++; - pos = pos->next.get(); - } - return len; + size_t DrainInto(std::vector* list, size_t* length = nullptr) { + return DrainInto([&](uv_buf_t buf) { list->push_back(buf); }, length); } - inline size_t DrainInto( - std::vector* list, - size_t* length = nullptr) { - size_t len = 0; - bool seen_head = false; - quic_buffer_chunk* pos = head_; - if (pos == nullptr) - return 0; - if (length != nullptr) *length = 0; - while (pos != nullptr) { - size_t datalen = pos->buf.len - pos->roffset; - if (length != nullptr) *length += datalen; - list->push_back(ngtcp2_vec{ - reinterpret_cast(pos->buf.base) + pos->roffset, - datalen}); - if (pos == head_) seen_head = true; - if (seen_head) len++; - pos = pos->next.get(); - } - return len; + size_t DrainInto(std::vector* list, size_t* length = nullptr) { + return DrainInto([&](uv_buf_t buf) { + list->push_back(ngtcp2_vec { + reinterpret_cast(buf.base), buf.len }); + }, length); } // Returns the current read head or an empty buffer if // we're empty - inline uv_buf_t Head() { - if (!head_) + uv_buf_t Head() { + if (head_ == nullptr) return uv_buf_init(nullptr, 0); return uv_buf_init( head_->buf.base + head_->roffset, @@ -352,20 +305,20 @@ class QuicBuffer : public MemoryRetainer { // number of buffers. If amount is greater than // the number of buffers remaining, move to the // end, and return the actual number advanced. - inline size_t SeekHead(size_t amount = 1) { + size_t SeekHead(size_t amount = 1) { size_t n = 0; size_t amt = amount; - while (head_ && amt > 0) { + while (head_ != nullptr && amt > 0) { head_ = head_->next.get(); n++; amt--; count_--; - rlength_ -= !head_ ? 0 : head_->buf.len; + rlength_ -= head_ == nullptr ? 0 : head_->buf.len; } return n; } - inline void SeekHeadOffset(ssize_t amount) { + void SeekHeadOffset(ssize_t amount) { if (amount < 0) return; size_t amt = std::min(amount < 0 ? length_ : amount, length_); @@ -393,7 +346,27 @@ class QuicBuffer : public MemoryRetainer { SET_SELF_SIZE(QuicBuffer); private: - inline void Push(quic_buffer_chunk* chunk) { + template + size_t DrainInto(Fn&& add_to_list, size_t* length) { + size_t len = 0; + bool seen_head = false; + quic_buffer_chunk* pos = head_; + if (pos == nullptr) + return 0; + if (length != nullptr) *length = 0; + while (pos != nullptr) { + size_t datalen = pos->buf.len - pos->roffset; + if (length != nullptr) *length += datalen; + add_to_list(uv_buf_init(pos->buf.base + pos->roffset, datalen)); + if (pos == head_) seen_head = true; + if (seen_head) len++; + pos = pos->next.get(); + } + return len; + } + + + void Push(quic_buffer_chunk* chunk) { size_++; count_++; if (!tail_) { @@ -407,19 +380,15 @@ class QuicBuffer : public MemoryRetainer { } } - inline void Push(uv_buf_t buf) { + void Push(uv_buf_t buf) { Push(new quic_buffer_chunk(buf)); } - inline void Push( - uv_buf_t buf, - done_cb done, - void* user_data, - v8::Local keep_alive) { - Push(new quic_buffer_chunk(buf, done, user_data, keep_alive)); + void Push(uv_buf_t buf, done_cb done, v8::Local keep_alive) { + Push(new quic_buffer_chunk(buf, done, keep_alive)); } - inline bool Pop(int status = 0) { + bool Pop(int status = 0) { if (!root_) return false; std::unique_ptr root(std::move(root_)); @@ -435,7 +404,7 @@ class QuicBuffer : public MemoryRetainer { return true; } - inline void Consume(int status, ssize_t amount) { + void Consume(int status, ssize_t amount) { size_t amt = std::min(amount < 0 ? length_ : amount, length_); while (root_ && amt > 0) { auto root = root_.get(); diff --git a/src/node_quic_crypto.h b/src/node_quic_crypto.h index 8862bd284a..8d0b096a6f 100644 --- a/src/node_quic_crypto.h +++ b/src/node_quic_crypto.h @@ -59,7 +59,7 @@ struct CryptoContext { }; // TODO(@jasnell): Remove once we move to ngtcp2_crypto -typedef enum ngtcp2_crypto_side { +enum ngtcp2_crypto_side { /** * ``NGTCP2_CRYPTO_SIDE_CLIENT`` indicates that the application is * client. @@ -70,7 +70,7 @@ typedef enum ngtcp2_crypto_side { * server. */ NGTCP2_CRYPTO_SIDE_SERVER -} ngtcp2_crypto_side; +}; BIO_METHOD* CreateBIOMethod(); diff --git a/src/node_quic_session-inl.h b/src/node_quic_session-inl.h index 858b56522a..4a90a76f42 100644 --- a/src/node_quic_session-inl.h +++ b/src/node_quic_session-inl.h @@ -3,665 +3,77 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include "aliased_buffer.h" -#include "debug_utils.h" -#include "env-inl.h" -#include "node_crypto.h" #include "node_quic_session.h" -#include "ngtcp2/ngtcp2.h" #include namespace node { -using crypto::EntropySource; - namespace quic { -inline void SetConfig(Environment* env, int idx, uint64_t* val) { - AliasedFloat64Array& buffer = env->quic_state()->quicsessionconfig_buffer; - uint64_t flags = static_cast(buffer[IDX_QUIC_SESSION_CONFIG_COUNT]); - if (flags & (1ULL << idx)) - *val = static_cast(buffer[idx]); -} - -// Forwards detailed(verbose) debugging information from ngtcp2. Enabled using -// the NODE_DEBUG_NATIVE=NGTCP2_DEBUG category. -inline void DebugLog(void* user_data, const char* fmt, ...) { - QuicSession* session = static_cast(user_data); - va_list ap; - va_start(ap, fmt); - Debug(session->env(), DebugCategory::NGTCP2_DEBUG, fmt, ap); - va_end(ap); -} - -inline void QuicSessionConfig::ResetToDefaults() { - ngtcp2_settings_default(&settings_); - settings_.initial_ts = uv_hrtime(); - settings_.log_printf = DebugLog; - settings_.active_connection_id_limit = DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; - settings_.max_stream_data_bidi_local = DEFAULT_MAX_STREAM_DATA_BIDI_LOCAL; - settings_.max_stream_data_bidi_remote = DEFAULT_MAX_STREAM_DATA_BIDI_REMOTE; - settings_.max_stream_data_uni = DEFAULT_MAX_STREAM_DATA_UNI; - settings_.max_data = DEFAULT_MAX_DATA; - settings_.max_streams_bidi = DEFAULT_MAX_STREAMS_BIDI; - settings_.max_streams_uni = DEFAULT_MAX_STREAMS_UNI; - settings_.idle_timeout = DEFAULT_IDLE_TIMEOUT; - settings_.max_packet_size = NGTCP2_MAX_PKT_SIZE; - settings_.max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY; - settings_.disable_migration = 0; - settings_.preferred_address_present = 0; - settings_.stateless_reset_token_present = 0; - max_crypto_buffer_ = DEFAULT_MAX_CRYPTO_BUFFER; -} - -// Sets the QuicSessionConfig using an AliasedBuffer for efficiency. -inline void QuicSessionConfig::Set( - Environment* env, - const sockaddr* preferred_addr) { - ResetToDefaults(); - - SetConfig(env, IDX_QUIC_SESSION_ACTIVE_CONNECTION_ID_LIMIT, - &settings_.active_connection_id_limit); - SetConfig(env, IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_LOCAL, - &settings_.max_stream_data_bidi_local); - SetConfig(env, IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_REMOTE, - &settings_.max_stream_data_bidi_remote); - SetConfig(env, IDX_QUIC_SESSION_MAX_STREAM_DATA_UNI, - &settings_.max_stream_data_uni); - SetConfig(env, IDX_QUIC_SESSION_MAX_DATA, - &settings_.max_data); - SetConfig(env, IDX_QUIC_SESSION_MAX_STREAMS_BIDI, - &settings_.max_streams_bidi); - SetConfig(env, IDX_QUIC_SESSION_MAX_STREAMS_UNI, - &settings_.max_streams_uni); - SetConfig(env, IDX_QUIC_SESSION_IDLE_TIMEOUT, - &settings_.idle_timeout); - SetConfig(env, IDX_QUIC_SESSION_MAX_PACKET_SIZE, - &settings_.max_packet_size); - SetConfig(env, IDX_QUIC_SESSION_MAX_ACK_DELAY, - &settings_.max_ack_delay); - - SetConfig(env, IDX_QUIC_SESSION_MAX_CRYPTO_BUFFER, - &max_crypto_buffer_); - max_crypto_buffer_ = std::max(max_crypto_buffer_, MIN_MAX_CRYPTO_BUFFER); - - if (preferred_addr != nullptr) { - settings_.preferred_address_present = 1; - switch (preferred_addr->sa_family) { - case AF_INET: { - auto& dest = settings_.preferred_address.ipv4_addr; - memcpy( - &dest, - &(reinterpret_cast(preferred_addr)->sin_addr), - sizeof(dest)); - settings_.preferred_address.ipv4_port = - SocketAddress::GetPort(preferred_addr); - break; - } - case AF_INET6: { - auto& dest = settings_.preferred_address.ipv6_addr; - memcpy( - &dest, - &(reinterpret_cast(preferred_addr)->sin6_addr), - sizeof(dest)); - settings_.preferred_address.ipv6_port = - SocketAddress::GetPort(preferred_addr); - break; - } - default: - UNREACHABLE(); - } - } -} - -inline void QuicSessionConfig::GenerateStatelessResetToken() { - settings_.stateless_reset_token_present = 1; - EntropySource( - settings_.stateless_reset_token, - arraysize(settings_.stateless_reset_token)); -} - -inline void QuicSessionConfig::GeneratePreferredAddressToken( - ngtcp2_cid* pscid) { - if (!settings_.preferred_address_present) - return; - EntropySource( - settings_.preferred_address.stateless_reset_token, - arraysize(settings_.preferred_address.stateless_reset_token)); - - pscid->datalen = NGTCP2_SV_SCIDLEN; - EntropySource(pscid->data, pscid->datalen); - settings_.preferred_address.cid = *pscid; -} - - -inline void QuicSession::CheckAllocatedSize(size_t previous_size) { +void QuicSession::CheckAllocatedSize(size_t previous_size) const { CHECK_GE(current_ngtcp2_memory_, previous_size); } -inline void QuicSession::IncrementAllocatedSize(size_t size) { +void QuicSession::IncreaseAllocatedSize(size_t size) { current_ngtcp2_memory_ += size; } -inline void QuicSession::DecrementAllocatedSize(size_t size) { +void QuicSession::DecreaseAllocatedSize(size_t size) { current_ngtcp2_memory_ -= size; } - -inline void QuicSession::OnIdleTimeout(uv_timer_t* timer) { - QuicSession* session = static_cast(timer->data); - CHECK_NOT_NULL(session); - session->OnIdleTimeout(); -} - -// 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 upon creation of a new client connection -// to initiate the TLS handshake. -inline int QuicSession::OnClientInitial( - ngtcp2_conn* conn, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - return session->TLSHandshake() == 0 ? 0 : NGTCP2_ERR_CALLBACK_FAILURE; -} - -// Called by ngtcp2 for a new server connection when the initial -// crypto handshake from the client has been received. -inline int QuicSession::OnReceiveClientInitial( - ngtcp2_conn* conn, - const ngtcp2_cid* dcid, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - return session->ReceiveClientInitial(dcid) ? - 0 : NGTCP2_ERR_CALLBACK_FAILURE; -} - -// Called by ngtcp2 for both client and server connections when -// TLS handshake data has been received. -inline 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(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - return static_cast( - session->ReceiveCryptoData(crypto_level, offset, data, datalen)); -} - -// Called by ngtcp2 for a client connection when the server has -// sent a retry packet. -inline int QuicSession::OnReceiveRetry( - ngtcp2_conn* conn, - const ngtcp2_pkt_hd* hd, - const ngtcp2_pkt_retry* retry, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - return session->ReceiveRetry() ? 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. -inline int QuicSession::OnExtendMaxStreamsBidi( - ngtcp2_conn* conn, - uint64_t max_streams, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope 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 -inline int QuicSession::OnExtendMaxStreamsUni( - ngtcp2_conn* conn, - uint64_t max_streams, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - session->ExtendMaxStreamsUni(max_streams); - return 0; -} - -inline 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(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - session->ExtendMaxStreamData(stream_id, max_data); - return 0; -} - -// Called by ngtcp2 for both client and server connections -// when ngtcp2 has determined that the TLS handshake has -// been completed. -inline int QuicSession::OnHandshakeCompleted( - ngtcp2_conn* conn, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - session->HandshakeCompleted(); - return 0; -} - -// Called by ngtcp2 when TLS handshake data needs to be -// encrypted prior to sending. -inline ssize_t QuicSession::OnDoHSEncrypt( - ngtcp2_conn* conn, - uint8_t* dest, - size_t destlen, - const uint8_t* plaintext, - size_t plaintextlen, - const uint8_t* key, - size_t keylen, - const uint8_t* nonce, - size_t noncelen, - const uint8_t* ad, - size_t adlen, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - return session->DoHSEncrypt( - dest, destlen, - plaintext, plaintextlen, - key, keylen, - nonce, noncelen, - ad, adlen); -} - -// Called by ngtcp2 when encrypted TLS handshake data has -// been received. -inline ssize_t QuicSession::OnDoHSDecrypt( - ngtcp2_conn* conn, - uint8_t* dest, - size_t destlen, - const uint8_t* ciphertext, - size_t ciphertextlen, - const uint8_t* key, - size_t keylen, - const uint8_t* nonce, - size_t noncelen, - const uint8_t* ad, - size_t adlen, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - return session->DoHSDecrypt( - dest, destlen, - ciphertext, ciphertextlen, - key, keylen, - nonce, noncelen, - ad, adlen); -} - -// Called by ngtcp2 when non-TLS handshake data needs to be -// encrypted prior to sending. -inline ssize_t QuicSession::OnDoEncrypt( - ngtcp2_conn* conn, - uint8_t* dest, - size_t destlen, - const uint8_t* plaintext, - size_t plaintextlen, - const uint8_t* key, - size_t keylen, - const uint8_t* nonce, - size_t noncelen, - const uint8_t* ad, - size_t adlen, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - return session->DoEncrypt( - dest, destlen, - plaintext, plaintextlen, - key, keylen, - nonce, noncelen, - ad, adlen); -} - -// Called by ngtcp2 when encrypted non-TLS handshake data -// has been received. -inline ssize_t QuicSession::OnDoDecrypt( - ngtcp2_conn* conn, - uint8_t* dest, - size_t destlen, - const uint8_t* ciphertext, - size_t ciphertextlen, - const uint8_t* key, - size_t keylen, - const uint8_t* nonce, - size_t noncelen, - const uint8_t* ad, - size_t adlen, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - return session->DoDecrypt( - dest, destlen, - ciphertext, ciphertextlen, - key, keylen, - nonce, noncelen, - ad, adlen); -} - -inline ssize_t QuicSession::OnDoInHPMask( - ngtcp2_conn* conn, - uint8_t* dest, - size_t destlen, - const uint8_t* key, - size_t keylen, - const uint8_t* sample, - size_t samplelen, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - return session->DoInHPMask( - dest, destlen, - key, keylen, - sample, samplelen); -} - -inline ssize_t QuicSession::OnDoHPMask( - ngtcp2_conn* conn, - uint8_t* dest, - size_t destlen, - const uint8_t* key, - size_t keylen, - const uint8_t* sample, - size_t samplelen, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - return session->DoHPMask( - dest, destlen, - key, keylen, - sample, samplelen); -} - -// Called by ngtcp2 when a chunk of stream data has been received. -inline int QuicSession::OnReceiveStreamData( - ngtcp2_conn* conn, - int64_t stream_id, - int fin, - uint64_t offset, - const uint8_t* data, - size_t datalen, - void* user_data, - void* stream_user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - session->ReceiveStreamData(stream_id, fin, data, datalen, offset); - return 0; -} - -// Called by ngtcp2 when a new stream has been opened -inline int QuicSession::OnStreamOpen( - ngtcp2_conn* conn, - int64_t stream_id, - void* user_data) { - QuicSession* session = static_cast(user_data); - session->StreamOpen(stream_id); - return 0; -} - -// Called by ngtcp2 when an acknowledgement for a chunk of -// TLS handshake data has been received. -inline int QuicSession::OnAckedCryptoOffset( - ngtcp2_conn* conn, - ngtcp2_crypto_level crypto_level, - uint64_t offset, - size_t datalen, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - session->AckedCryptoOffset(datalen); - return 0; -} - -// Called by ngtcp2 when an acknowledgement for a chunk of -// stream data has been received. -inline int QuicSession::OnAckedStreamDataOffset( - ngtcp2_conn* conn, - int64_t stream_id, - uint64_t offset, - size_t datalen, - void* user_data, - void* stream_user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope 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. -inline int QuicSession::OnSelectPreferredAddress( - ngtcp2_conn* conn, - ngtcp2_addr* dest, - const ngtcp2_preferred_addr* paddr, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - return session->SelectPreferredAddress(dest, paddr) ? - 0 : NGTCP2_ERR_CALLBACK_FAILURE; -} - -// Called by ngtcp2 when a stream has been closed for any reason. -inline 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(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - session->StreamClose(stream_id, app_error_code); - return 0; -} - -inline 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(user_data); - QuicSession::Ngtcp2CallbackScope 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 -inline int QuicSession::OnRand( - ngtcp2_conn* conn, - uint8_t* dest, - size_t destlen, - ngtcp2_rand_ctx ctx, - void* user_data) { - 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. -inline int QuicSession::OnGetNewConnectionID( - ngtcp2_conn* conn, - ngtcp2_cid* cid, - uint8_t* token, - size_t cidlen, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - session->GetNewConnectionID(cid, token, cidlen); - return 0; -} - -// Called by ngtcp2 to trigger a key update for the connection. -inline int QuicSession::OnUpdateKey( - ngtcp2_conn* conn, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - return session->UpdateKey() ? 0 : NGTCP2_ERR_CALLBACK_FAILURE; -} - -// When a connection is closed, ngtcp2 will call this multiple -// times to remove connection IDs. -inline int QuicSession::OnRemoveConnectionID( - ngtcp2_conn* conn, - const ngtcp2_cid* cid, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - session->RemoveConnectionID(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. -inline int QuicSession::OnPathValidation( - ngtcp2_conn* conn, - const ngtcp2_path* path, - ngtcp2_path_validation_result res, - void* user_data) { - QuicSession* session = static_cast(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - session->PathValidation(path, res); - return 0; -} - -inline 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(user_data); - QuicSession::Ngtcp2CallbackScope callback_scope(session); - session->VersionNegotiation(hd, sv, nsv); - return 0; -} - -inline void QuicSession::OnKeylog(const SSL* ssl, const char* line) { - QuicSession* session = static_cast(SSL_get_app_data(ssl)); - session->Keylog(line); -} - -inline int QuicSession::OnStatelessReset( - ngtcp2_conn* conn, - const ngtcp2_pkt_stateless_reset* sr, - void* user_data) { - QuicSession* session = static_cast(user_data); - session->SilentClose(true); - return 0; -} - -inline void QuicSession::SetTLSAlert(int err) { +void QuicSession::SetTLSAlert(int err) { SetLastError(InitQuicError(QUIC_ERROR_CRYPTO, err)); } -inline void QuicSession::SetLastError(QuicError error) { +void QuicSession::SetLastError(QuicError error) { last_error_ = error; } -inline void QuicSession::SetLastError(QuicErrorFamily family, uint64_t code) { - last_error_.family = family; - last_error_.code = code; +void QuicSession::SetLastError(QuicErrorFamily family, uint64_t code) { + SetLastError({ family, code }); } -inline void QuicSession::SetLastError(QuicErrorFamily family, int code) { +void QuicSession::SetLastError(QuicErrorFamily family, int code) { SetLastError(family, ngtcp2_err_infer_quic_transport_error_code(code)); } -inline bool QuicSession::IsInClosingPeriod() { +bool QuicSession::IsInClosingPeriod() { return ngtcp2_conn_is_in_closing_period(Connection()); } -inline bool QuicSession::IsInDrainingPeriod() { +bool QuicSession::IsInDrainingPeriod() { return ngtcp2_conn_is_in_draining_period(Connection()); } // Locate the QuicStream with the given id or return nullptr -inline QuicStream* QuicSession::FindStream(int64_t id) { +QuicStream* QuicSession::FindStream(int64_t id) { auto it = streams_.find(id); if (it == std::end(streams_)) return nullptr; - return (*it).second.get(); + return it->second.get(); } -inline bool QuicSession::HasStream(int64_t id) { +bool QuicSession::HasStream(int64_t id) { return streams_.find(id) != std::end(streams_); } -inline QuicError QuicSession::GetLastError() { return last_error_; } +QuicError QuicSession::GetLastError() const { return last_error_; } -inline bool QuicSession::IsGracefullyClosing() { +bool QuicSession::IsGracefullyClosing() const { return IsFlagSet(QUICSESSION_FLAG_GRACEFUL_CLOSING); } -inline bool QuicSession::IsDestroyed() { +bool QuicSession::IsDestroyed() const { return IsFlagSet(QUICSESSION_FLAG_DESTROYED); } -inline void QuicSession::StartGracefulClose() { +void QuicSession::StartGracefulClose() { SetFlag(QUICSESSION_FLAG_GRACEFUL_CLOSING); session_stats_.closing_at = uv_hrtime(); } -inline 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; -} - -inline int Empty(const ngtcp2_vec* vec, size_t cnt) { - size_t i; - for (i = 0; i < cnt && vec[i].len == 0; ++i) {} - return i == cnt; -} - -inline void QuicSession::OnIdleTimeoutCB(void* data) { - QuicSession* session = static_cast(data); - session->OnIdleTimeout(); -} - -inline void QuicSession::OnRetransmitTimeoutCB(void* data) { - QuicSession* session = static_cast(data); - session->MaybeTimeout(); -} - } // namespace quic } // namespace node diff --git a/src/node_quic_session.cc b/src/node_quic_session.cc index 4830678cc3..e542bac765 100644 --- a/src/node_quic_session.cc +++ b/src/node_quic_session.cc @@ -6,6 +6,7 @@ #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.h" // NOLINT(build/include_inline) #include "node_quic_session-inl.h" @@ -47,6 +48,121 @@ using v8::Value; namespace quic { +// Forwards detailed(verbose) debugging information from ngtcp2. Enabled using +// the NODE_DEBUG_NATIVE=NGTCP2_DEBUG category. +static void Ngtcp2DebugLog(void* user_data, const char* fmt, ...) { + QuicSession* session = static_cast(user_data); + va_list ap; + va_start(ap, fmt); + Debug(session->env(), DebugCategory::NGTCP2_DEBUG, fmt, ap); + va_end(ap); +} + +inline void SetConfig(Environment* env, int idx, uint64_t* val) { + AliasedFloat64Array& buffer = env->quic_state()->quicsessionconfig_buffer; + uint64_t flags = static_cast(buffer[IDX_QUIC_SESSION_CONFIG_COUNT]); + if (flags & (1ULL << idx)) + *val = static_cast(buffer[idx]); +} + +void QuicSessionConfig::ResetToDefaults() { + ngtcp2_settings_default(&settings_); + settings_.initial_ts = uv_hrtime(); + settings_.log_printf = Ngtcp2DebugLog; + settings_.active_connection_id_limit = DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + settings_.max_stream_data_bidi_local = DEFAULT_MAX_STREAM_DATA_BIDI_LOCAL; + settings_.max_stream_data_bidi_remote = DEFAULT_MAX_STREAM_DATA_BIDI_REMOTE; + settings_.max_stream_data_uni = DEFAULT_MAX_STREAM_DATA_UNI; + settings_.max_data = DEFAULT_MAX_DATA; + settings_.max_streams_bidi = DEFAULT_MAX_STREAMS_BIDI; + settings_.max_streams_uni = DEFAULT_MAX_STREAMS_UNI; + settings_.idle_timeout = DEFAULT_IDLE_TIMEOUT; + settings_.max_packet_size = NGTCP2_MAX_PKT_SIZE; + settings_.max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY; + settings_.disable_migration = 0; + settings_.preferred_address_present = 0; + settings_.stateless_reset_token_present = 0; + max_crypto_buffer_ = DEFAULT_MAX_CRYPTO_BUFFER; +} + +// Sets the QuicSessionConfig using an AliasedBuffer for efficiency. +void QuicSessionConfig::Set(Environment* env, + const sockaddr* preferred_addr) { + ResetToDefaults(); + + SetConfig(env, IDX_QUIC_SESSION_ACTIVE_CONNECTION_ID_LIMIT, + &settings_.active_connection_id_limit); + SetConfig(env, IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_LOCAL, + &settings_.max_stream_data_bidi_local); + SetConfig(env, IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_REMOTE, + &settings_.max_stream_data_bidi_remote); + SetConfig(env, IDX_QUIC_SESSION_MAX_STREAM_DATA_UNI, + &settings_.max_stream_data_uni); + SetConfig(env, IDX_QUIC_SESSION_MAX_DATA, + &settings_.max_data); + SetConfig(env, IDX_QUIC_SESSION_MAX_STREAMS_BIDI, + &settings_.max_streams_bidi); + SetConfig(env, IDX_QUIC_SESSION_MAX_STREAMS_UNI, + &settings_.max_streams_uni); + SetConfig(env, IDX_QUIC_SESSION_IDLE_TIMEOUT, + &settings_.idle_timeout); + SetConfig(env, IDX_QUIC_SESSION_MAX_PACKET_SIZE, + &settings_.max_packet_size); + SetConfig(env, IDX_QUIC_SESSION_MAX_ACK_DELAY, + &settings_.max_ack_delay); + + SetConfig(env, IDX_QUIC_SESSION_MAX_CRYPTO_BUFFER, + &max_crypto_buffer_); + max_crypto_buffer_ = std::max(max_crypto_buffer_, MIN_MAX_CRYPTO_BUFFER); + + if (preferred_addr != nullptr) { + settings_.preferred_address_present = 1; + switch (preferred_addr->sa_family) { + case AF_INET: { + auto& dest = settings_.preferred_address.ipv4_addr; + memcpy( + &dest, + &(reinterpret_cast(preferred_addr)->sin_addr), + sizeof(dest)); + settings_.preferred_address.ipv4_port = + SocketAddress::GetPort(preferred_addr); + break; + } + case AF_INET6: { + auto& dest = settings_.preferred_address.ipv6_addr; + memcpy( + &dest, + &(reinterpret_cast(preferred_addr)->sin6_addr), + sizeof(dest)); + settings_.preferred_address.ipv6_port = + SocketAddress::GetPort(preferred_addr); + break; + } + default: + UNREACHABLE(); + } + } +} + +void QuicSessionConfig::GenerateStatelessResetToken() { + settings_.stateless_reset_token_present = 1; + EntropySource( + settings_.stateless_reset_token, + arraysize(settings_.stateless_reset_token)); +} + +void QuicSessionConfig::GeneratePreferredAddressToken(ngtcp2_cid* pscid) { + if (!settings_.preferred_address_present) + return; + EntropySource( + settings_.preferred_address.stateless_reset_token, + arraysize(settings_.preferred_address.stateless_reset_token)); + + pscid->datalen = NGTCP2_SV_SCIDLEN; + EntropySource(pscid->data, pscid->datalen); + settings_.preferred_address.cid = *pscid; +} + // QuicSession is an abstract base class that defines the code used by both // server and client sessions. QuicSession::QuicSession( @@ -57,68 +173,68 @@ QuicSession::QuicSession( AsyncWrap::ProviderType provider_type, const std::string& alpn, uint32_t options, - uint64_t initial_connection_close) : - AsyncWrap(socket->env(), wrap, provider_type), + uint64_t initial_connection_close) + : AsyncWrap(socket->env(), wrap, provider_type), side_(side), socket_(socket), alpn_(alpn), options_(options), initial_connection_close_(initial_connection_close), - idle_(new Timer(socket->env(), OnIdleTimeoutCB, this)), - retransmit_(new Timer(socket->env(), OnRetransmitTimeoutCB, this)), + idle_(new Timer(socket->env(), [this]() { OnIdleTimeout(); })), + retransmit_(new Timer(socket->env(), [this]() { MaybeTimeout(); })), state_(env()->isolate(), IDX_QUIC_SESSION_STATE_COUNT), - allocator_(this), + alloc_info_(MakeAllocator()), crypto_rx_ack_( - HistogramBase::New( - socket->env(), - 1, std::numeric_limits::max())), + HistogramBase::New( + socket->env(), + 1, std::numeric_limits::max())), crypto_handshake_rate_( - HistogramBase::New( - socket->env(), - 1, std::numeric_limits::max())), + HistogramBase::New( + socket->env(), + 1, std::numeric_limits::max())), stats_buffer_( - socket->env()->isolate(), - sizeof(session_stats_) / sizeof(uint64_t), - reinterpret_cast(&session_stats_)), + socket->env()->isolate(), + sizeof(session_stats_) / sizeof(uint64_t), + reinterpret_cast(&session_stats_)), recovery_stats_buffer_( - socket->env()->isolate(), - sizeof(recovery_stats_) / sizeof(double), - reinterpret_cast(&recovery_stats_)) { + socket->env()->isolate(), + sizeof(recovery_stats_) / sizeof(double), + reinterpret_cast(&recovery_stats_)) { ssl_.reset(SSL_new(ctx->ctx_.get())); SSL_CTX_set_keylog_callback(ctx->ctx_.get(), OnKeylog); CHECK(ssl_); session_stats_.created_at = uv_hrtime(); - USE(wrap->DefineOwnProperty( - env()->context(), - env()->state_string(), - state_.GetJSArray(), - PropertyAttribute::ReadOnly)); - - USE(wrap->DefineOwnProperty( - env()->context(), - env()->stats_string(), - stats_buffer_.GetJSArray(), - PropertyAttribute::ReadOnly)); - - USE(wrap->DefineOwnProperty( - env()->context(), - env()->recovery_stats_string(), - recovery_stats_buffer_.GetJSArray(), - PropertyAttribute::ReadOnly)); - - USE(wrap->DefineOwnProperty( - env()->context(), - FIXED_ONE_BYTE_STRING(env()->isolate(), "crypto_rx_ack"), - crypto_rx_ack_->object(), - PropertyAttribute::ReadOnly)); - - USE(wrap->DefineOwnProperty( - env()->context(), - FIXED_ONE_BYTE_STRING(env()->isolate(), "crypto_handshake_rate"), - crypto_handshake_rate_->object(), - PropertyAttribute::ReadOnly)); + if (wrap->DefineOwnProperty( + env()->context(), + env()->state_string(), + state_.GetJSArray(), + PropertyAttribute::ReadOnly).IsNothing()) return; + + if (wrap->DefineOwnProperty( + env()->context(), + env()->stats_string(), + stats_buffer_.GetJSArray(), + PropertyAttribute::ReadOnly).IsNothing()) return; + + if (wrap->DefineOwnProperty( + env()->context(), + env()->recovery_stats_string(), + recovery_stats_buffer_.GetJSArray(), + PropertyAttribute::ReadOnly).IsNothing()) return; + + if (wrap->DefineOwnProperty( + env()->context(), + FIXED_ONE_BYTE_STRING(env()->isolate(), "crypto_rx_ack"), + crypto_rx_ack_->object(), + PropertyAttribute::ReadOnly).IsNothing()) return; + + if (wrap->DefineOwnProperty( + env()->context(), + FIXED_ONE_BYTE_STRING(env()->isolate(), "crypto_handshake_rate"), + crypto_handshake_rate_->object(), + PropertyAttribute::ReadOnly).IsNothing()) return; // TODO(@jasnell): memory accounting // env_->isolate()->AdjustAmountOfExternalAllocatedMemory(kExternalSize); @@ -285,7 +401,7 @@ void QuicSession::ImmediateClose() { // Grab a shared pointer to this to prevent the QuicSession // from being freed while the MakeCallback is running. - std::shared_ptr ptr(this->shared_from_this()); + std::shared_ptr ptr(shared_from_this()); MakeCallback(env()->quic_on_session_close_function(), arraysize(argv), argv); } @@ -305,7 +421,7 @@ QuicStream* QuicSession::CreateStream(int64_t stream_id) { // Grab a shared pointer to this to prevent the QuicSession // from being freed while the MakeCallback is running. - std::shared_ptr ptr(this->shared_from_this()); + std::shared_ptr ptr(shared_from_this()); MakeCallback(env()->quic_on_stream_ready_function(), arraysize(argv), argv); return stream; } @@ -611,7 +727,7 @@ void QuicSession::HandshakeCompleted() { // Grab a shared pointer to this to prevent the QuicSession // from being freed while the MakeCallback is running. - std::shared_ptr ptr(this->shared_from_this()); + std::shared_ptr ptr(shared_from_this()); MakeCallback(env()->quic_on_session_handshake_function(), arraysize(argv), argv); @@ -674,7 +790,7 @@ void QuicSession::Keylog(const char* line) { // Grab a shared pointer to this to prevent the QuicSession // from being freed while the MakeCallback is running. - std::shared_ptr ptr(this->shared_from_this()); + std::shared_ptr ptr(shared_from_this()); MakeCallback(env()->quic_on_session_keylog_function(), 1, &line_bf); } @@ -851,7 +967,7 @@ void QuicSession::PathValidation( }; // Grab a shared pointer to this to prevent the QuicSession // from being freed while the MakeCallback is running. - std::shared_ptr ptr(this->shared_from_this()); + std::shared_ptr ptr(shared_from_this()); MakeCallback( env()->quic_on_session_path_validation_function(), arraysize(argv), @@ -1178,6 +1294,31 @@ void QuicSession::UpdateRetransmitTimer(uint64_t timeout) { retransmit_->Update(timeout); } +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 + // Sends buffered stream data. bool QuicSession::SendStreamData(QuicStream* stream) { // Because SendStreamData calls ngtcp2_conn_writev_streams, @@ -1297,7 +1438,7 @@ bool QuicSession::SendStreamData(QuicStream* stream) { if (!SendPacket("stream data")) return false; - if (Empty(v, c)) { + if (IsEmpty(v, c)) { // fin will have been set if all of the data has been // encoded in the packet and IsWritable() returns false. if (!stream->IsWritable()) { @@ -1332,7 +1473,7 @@ bool QuicSession::SendPacket(const char* diagnostic_label) { int err = Socket()->SendPacket( *remote_address_, &txbuf_, - this->shared_from_this(), + shared_from_this(), diagnostic_label); if (err != 0) { SetLastError(QUIC_ERROR_SESSION, err); @@ -1457,7 +1598,7 @@ void QuicSession::SilentClose(bool stateless_reset) { // Grab a shared pointer to this to prevent the QuicSession // from being freed while the MakeCallback is running. - std::shared_ptr ptr(this->shared_from_this()); + std::shared_ptr ptr(shared_from_this()); MakeCallback( env()->quic_on_session_silent_close_function(), arraysize(argv), argv); } @@ -1485,7 +1626,7 @@ void QuicSession::StreamClose(int64_t stream_id, uint64_t app_error_code) { // Grab a shared pointer to this to prevent the QuicSession // from being freed while the MakeCallback is running. - std::shared_ptr ptr(this->shared_from_this()); + std::shared_ptr ptr(shared_from_this()); MakeCallback(env()->quic_on_stream_close_function(), arraysize(argv), argv); } @@ -1559,7 +1700,7 @@ void QuicSession::StreamReset( }; // Grab a shared pointer to this to prevent the QuicSession // from being freed while the MakeCallback is running. - std::shared_ptr ptr(this->shared_from_this()); + std::shared_ptr ptr(shared_from_this()); MakeCallback(env()->quic_on_stream_reset_function(), arraysize(argv), argv); } @@ -1795,26 +1936,24 @@ std::shared_ptr QuicServerSession::New( const std::string& alpn, uint32_t options, uint64_t initial_connection_close) { - std::shared_ptr session; Local obj; if (!socket->env() ->quicserversession_constructor_template() ->NewInstance(socket->env()->context()).ToLocal(&obj)) { - return session; + return {}; } - session.reset( - new QuicServerSession( - socket, - config, - obj, - rcid, - addr, - dcid, - ocid, - version, - alpn, - options, - initial_connection_close)); + std::shared_ptr session { new QuicServerSession( + socket, + config, + obj, + rcid, + addr, + dcid, + ocid, + version, + alpn, + options, + initial_connection_close) }; session->AddToSocket(socket); return session; @@ -1847,8 +1986,8 @@ void QuicServerSession::Init( CHECK_NULL(connection_); - this->ExtendMaxStreamsBidi(config->max_streams_bidi()); - this->ExtendMaxStreamsUni(config->max_streams_uni()); + ExtendMaxStreamsBidi(config->max_streams_bidi()); + ExtendMaxStreamsUni(config->max_streams_uni()); remote_address_.Copy(addr); max_pktlen_ = SocketAddress::GetMaxPktLen(addr); @@ -1857,7 +1996,7 @@ void QuicServerSession::Init( QuicSessionConfig cfg = *config; cfg.GenerateStatelessResetToken(); - cfg.GeneratePreferredAddressToken(this->pscid()); + cfg.GeneratePreferredAddressToken(pscid()); max_crypto_buffer_ = cfg.GetMaxCryptoBuffer(); EntropySource(scid_.data, NGTCP2_SV_SCIDLEN); @@ -1873,9 +2012,9 @@ void QuicServerSession::Init( &scid_, *path, version, - &callbacks_, + &callbacks, *cfg, - *allocator_, + &alloc_info_, static_cast(this)), 0); if (ocid) @@ -1896,13 +2035,11 @@ void QuicServerSession::InitTLS_Post() { } } -namespace { -void OnServerClientHelloCB(const FunctionCallbackInfo& args) { +void QuicSessionOnClientHelloDone(const FunctionCallbackInfo& args) { QuicSession* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); session->OnClientHelloDone(); } -} // namespace void QuicServerSession::OnClientHelloDone() { // Continue the TLS handshake when this function exits @@ -1967,13 +2104,7 @@ int QuicServerSession::OnClientHello() { Local argv[] = { Undefined(env()->isolate()), Undefined(env()->isolate()), - GetClientHelloCiphers(env(), ssl()), - Function::New( - env()->context(), - OnServerClientHelloCB, - object(), 0, - v8::ConstructorBehavior::kThrow, - v8::SideEffectType::kHasNoSideEffect).ToLocalChecked() + GetClientHelloCiphers(env(), ssl()) }; if (alpn != nullptr) { @@ -1991,7 +2122,7 @@ int QuicServerSession::OnClientHello() { // Grab a shared pointer to this to prevent the QuicSession // from being freed while the MakeCallback is running. - std::shared_ptr ptr(this->shared_from_this()); + std::shared_ptr ptr(shared_from_this()); MakeCallback( env()->quic_on_session_client_hello_function(), arraysize(argv), argv); @@ -1999,13 +2130,12 @@ int QuicServerSession::OnClientHello() { return IsFlagSet(QUICSESSION_FLAG_CLIENT_HELLO_CB_RUNNING) ? -1 : 0; } -namespace { // This callback is invoked by user code after completing handling // of the 'OCSPRequest' event. The callback is invoked with two // possible arguments, both of which are optional // 1. A replacement SecureContext // 2. An OCSP response -void OnServerCertCB(const FunctionCallbackInfo& args) { +void QuicSessionOnCertDone(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); QuicServerSession* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); @@ -2013,12 +2143,11 @@ void OnServerCertCB(const FunctionCallbackInfo& args) { Local cons = env->secure_context_constructor_template(); crypto::SecureContext* context = nullptr; if (args[0]->IsObject() && cons->HasInstance(args[0])) - ASSIGN_OR_RETURN_UNWRAP(&context, args[0].As()); + context = Unwrap(args[0].As()); session->OnCertDone(context, args[1]); } -} // namespace -// The OnCertDone function is called by the OnServerCertCB +// The OnCertDone function is called by the QuicSessionOnCertDone // function when usercode is done handling the OCSPRequest event. void QuicServerSession::OnCertDone( crypto::SecureContext* context, @@ -2090,18 +2219,12 @@ int QuicServerSession::OnCert() { OneByteString( env()->isolate(), servername, - strlen(servername)), - Function::New( - env()->context(), - OnServerCertCB, - object(), 0, - v8::ConstructorBehavior::kThrow, - v8::SideEffectType::kHasNoSideEffect).ToLocalChecked() + strlen(servername)) }; // Grab a shared pointer to this to prevent the QuicSession // from being freed while the MakeCallback is running. - std::shared_ptr ptr(this->shared_from_this()); + std::shared_ptr ptr(shared_from_this()); MakeCallback(env()->quic_on_session_cert_function(), arraysize(argv), argv); return IsFlagSet(QUICSESSION_FLAG_CERT_CB_RUNNING) ? -1 : 1; @@ -2293,29 +2416,27 @@ std::shared_ptr QuicClientSession::New( SelectPreferredAddressPolicy select_preferred_address_policy, const std::string& alpn, uint32_t options) { - std::shared_ptr session; Local obj; if (!socket->env() ->quicclientsession_constructor_template() ->NewInstance(socket->env()->context()).ToLocal(&obj)) { - return session; + return {}; } - session = - std::make_shared( - socket, - obj, - addr, - version, - context, - hostname, - port, - early_transport_params, - session_ticket, - dcid, - select_preferred_address_policy, - alpn, - options); + std::shared_ptr session { new QuicClientSession( + socket, + obj, + addr, + version, + context, + hostname, + port, + early_transport_params, + session_ticket, + dcid, + select_preferred_address_policy, + alpn, + options) }; session->AddToSocket(socket); session->TLSHandshake(); @@ -2364,7 +2485,7 @@ void QuicClientSession::VersionNegotiation( // Grab a shared pointer to this to prevent the QuicSession // from being freed while the MakeCallback is running. - std::shared_ptr ptr(this->shared_from_this()); + std::shared_ptr ptr(shared_from_this()); MakeCallback( env()->quic_on_session_version_negotiation_function(), arraysize(argv), argv); @@ -2392,8 +2513,8 @@ bool QuicClientSession::Init( QuicSessionConfig config(env()); max_crypto_buffer_ = config.GetMaxCryptoBuffer(); - this->ExtendMaxStreamsBidi(config.max_streams_bidi()); - this->ExtendMaxStreamsUni(config.max_streams_uni()); + ExtendMaxStreamsBidi(config.max_streams_bidi()); + ExtendMaxStreamsUni(config.max_streams_uni()); scid_.datalen = NGTCP2_MAX_CIDLEN; EntropySource(scid_.data, scid_.datalen); @@ -2421,9 +2542,9 @@ bool QuicClientSession::Init( &scid_, *path, version, - &callbacks_, + &callbacks, *config, - *allocator_, + &alloc_info_, static_cast(this)), 0); connection_.reset(conn); @@ -2505,13 +2626,13 @@ int QuicClientSession::SetSession(SSL_SESSION* session) { v8::Undefined(env()->isolate()) }; - AllocatedBuffer sessionTicket = env()->AllocateManaged(size); + AllocatedBuffer session_ticket = env()->AllocateManaged(size); unsigned char* session_data = - reinterpret_cast(sessionTicket.data()); + reinterpret_cast(session_ticket.data()); memset(session_data, 0, size); i2d_SSL_SESSION(session, &session_data); - if (!sessionTicket.empty()) - argv[1] = sessionTicket.ToBuffer().ToLocalChecked(); + if (!session_ticket.empty()) + argv[1] = session_ticket.ToBuffer().ToLocalChecked(); if (transportParams_.length() > 0) { argv[2] = Buffer::New( @@ -2522,7 +2643,7 @@ int QuicClientSession::SetSession(SSL_SESSION* session) { } // Grab a shared pointer to this to prevent the QuicSession // from being freed while the MakeCallback is running. - std::shared_ptr ptr(this->shared_from_this()); + std::shared_ptr ptr(shared_from_this()); MakeCallback(env()->quic_on_session_ticket_function(), arraysize(argv), argv); return 1; @@ -2617,7 +2738,7 @@ int QuicClientSession::OnTLSStatus() { } // Grab a shared pointer to this to prevent the QuicSession // from being freed while the MakeCallback is running. - std::shared_ptr ptr(this->shared_from_this()); + std::shared_ptr ptr(shared_from_this()); MakeCallback(env()->quic_on_session_status_function(), 1, &arg); return 1; } @@ -2689,7 +2810,7 @@ bool QuicClientSession::SetSession(Local buffer) { ArrayBufferViewContents sbuf(buffer.As()); const unsigned char* p = sbuf.data(); crypto::SSLSessionPointer s(d2i_SSL_SESSION(nullptr, &p, sbuf.length())); - return (s != nullptr && SSL_set_session(ssl_.get(), s.get()) == 1); + return s != nullptr && SSL_set_session(ssl_.get(), s.get()) == 1; } // The TLS handshake kicks off when the QuicClientSession is created. @@ -2759,6 +2880,495 @@ int QuicClientSession::VerifyPeerIdentity(const char* hostname) { return 0; } +// 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 upon creation of a new client connection +// to initiate the TLS handshake. +int QuicSession::OnClientInitial( + ngtcp2_conn* conn, + void* user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + return session->TLSHandshake() == 0 ? 0 : NGTCP2_ERR_CALLBACK_FAILURE; +} + +// Called by ngtcp2 for a new server connection when the initial +// crypto handshake from the client has been received. +int QuicSession::OnReceiveClientInitial( + ngtcp2_conn* conn, + const ngtcp2_cid* dcid, + void* user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + return session->ReceiveClientInitial(dcid) ? + 0 : NGTCP2_ERR_CALLBACK_FAILURE; +} + +// Called by ngtcp2 for both client and server connections when +// TLS handshake data has been received. +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(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + return static_cast( + session->ReceiveCryptoData(crypto_level, offset, data, datalen)); +} + +// Called by ngtcp2 for a client connection when the server has +// sent a retry packet. +int QuicSession::OnReceiveRetry( + ngtcp2_conn* conn, + const ngtcp2_pkt_hd* hd, + const ngtcp2_pkt_retry* retry, + void* user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + return session->ReceiveRetry() ? 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(user_data); + QuicSession::Ngtcp2CallbackScope 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(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + session->ExtendMaxStreamsUni(max_streams); + return 0; +} + +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(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + session->ExtendMaxStreamData(stream_id, max_data); + return 0; +} + +// Called by ngtcp2 for both client and server connections +// when ngtcp2 has determined that the TLS handshake has +// been completed. +int QuicSession::OnHandshakeCompleted( + ngtcp2_conn* conn, + void* user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + session->HandshakeCompleted(); + return 0; +} + +// Called by ngtcp2 when TLS handshake data needs to be +// encrypted prior to sending. +ssize_t QuicSession::OnDoHSEncrypt( + ngtcp2_conn* conn, + uint8_t* dest, + size_t destlen, + const uint8_t* plaintext, + size_t plaintextlen, + const uint8_t* key, + size_t keylen, + const uint8_t* nonce, + size_t noncelen, + const uint8_t* ad, + size_t adlen, + void* user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + return session->DoHSEncrypt( + dest, destlen, + plaintext, plaintextlen, + key, keylen, + nonce, noncelen, + ad, adlen); +} + +// Called by ngtcp2 when encrypted TLS handshake data has +// been received. +ssize_t QuicSession::OnDoHSDecrypt( + ngtcp2_conn* conn, + uint8_t* dest, + size_t destlen, + const uint8_t* ciphertext, + size_t ciphertextlen, + const uint8_t* key, + size_t keylen, + const uint8_t* nonce, + size_t noncelen, + const uint8_t* ad, + size_t adlen, + void* user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + return session->DoHSDecrypt( + dest, destlen, + ciphertext, ciphertextlen, + key, keylen, + nonce, noncelen, + ad, adlen); +} + +// Called by ngtcp2 when non-TLS handshake data needs to be +// encrypted prior to sending. +ssize_t QuicSession::OnDoEncrypt( + ngtcp2_conn* conn, + uint8_t* dest, + size_t destlen, + const uint8_t* plaintext, + size_t plaintextlen, + const uint8_t* key, + size_t keylen, + const uint8_t* nonce, + size_t noncelen, + const uint8_t* ad, + size_t adlen, + void* user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + return session->DoEncrypt( + dest, destlen, + plaintext, plaintextlen, + key, keylen, + nonce, noncelen, + ad, adlen); +} + +// Called by ngtcp2 when encrypted non-TLS handshake data +// has been received. +ssize_t QuicSession::OnDoDecrypt( + ngtcp2_conn* conn, + uint8_t* dest, + size_t destlen, + const uint8_t* ciphertext, + size_t ciphertextlen, + const uint8_t* key, + size_t keylen, + const uint8_t* nonce, + size_t noncelen, + const uint8_t* ad, + size_t adlen, + void* user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + return session->DoDecrypt( + dest, destlen, + ciphertext, ciphertextlen, + key, keylen, + nonce, noncelen, + ad, adlen); +} + +ssize_t QuicSession::OnDoInHPMask( + ngtcp2_conn* conn, + uint8_t* dest, + size_t destlen, + const uint8_t* key, + size_t keylen, + const uint8_t* sample, + size_t samplelen, + void* user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + return session->DoInHPMask( + dest, destlen, + key, keylen, + sample, samplelen); +} + +ssize_t QuicSession::OnDoHPMask( + ngtcp2_conn* conn, + uint8_t* dest, + size_t destlen, + const uint8_t* key, + size_t keylen, + const uint8_t* sample, + size_t samplelen, + void* user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + return session->DoHPMask( + dest, destlen, + key, keylen, + sample, samplelen); +} + +// Called by ngtcp2 when a chunk of stream data has been received. +int QuicSession::OnReceiveStreamData( + ngtcp2_conn* conn, + int64_t stream_id, + int fin, + uint64_t offset, + const uint8_t* data, + size_t datalen, + void* user_data, + void* stream_user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + session->ReceiveStreamData(stream_id, fin, data, datalen, offset); + return 0; +} + +// 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(user_data); + session->StreamOpen(stream_id); + return 0; +} + +// Called by ngtcp2 when an acknowledgement for a chunk of +// TLS handshake data has been received. +int QuicSession::OnAckedCryptoOffset( + ngtcp2_conn* conn, + ngtcp2_crypto_level crypto_level, + uint64_t offset, + size_t datalen, + void* user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + session->AckedCryptoOffset(datalen); + return 0; +} + +// Called by ngtcp2 when an acknowledgement for a chunk of +// stream data has been received. +int QuicSession::OnAckedStreamDataOffset( + ngtcp2_conn* conn, + int64_t stream_id, + uint64_t offset, + size_t datalen, + void* user_data, + void* stream_user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope 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(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + return session->SelectPreferredAddress(dest, paddr) ? + 0 : NGTCP2_ERR_CALLBACK_FAILURE; +} + +// Called by ngtcp2 when a stream has been closed for any reason. +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(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + session->StreamClose(stream_id, app_error_code); + return 0; +} + +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(user_data); + QuicSession::Ngtcp2CallbackScope 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 +int QuicSession::OnRand( + ngtcp2_conn* conn, + uint8_t* dest, + size_t destlen, + ngtcp2_rand_ctx ctx, + void* user_data) { + 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(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + session->GetNewConnectionID(cid, token, cidlen); + return 0; +} + +// Called by ngtcp2 to trigger a key update for the connection. +int QuicSession::OnUpdateKey( + ngtcp2_conn* conn, + void* user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + return session->UpdateKey() ? 0 : NGTCP2_ERR_CALLBACK_FAILURE; +} + +// When a connection is closed, ngtcp2 will call this multiple +// times to remove connection IDs. +int QuicSession::OnRemoveConnectionID( + ngtcp2_conn* conn, + const ngtcp2_cid* cid, + void* user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + session->RemoveConnectionID(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. +int QuicSession::OnPathValidation( + ngtcp2_conn* conn, + const ngtcp2_path* path, + ngtcp2_path_validation_result res, + void* user_data) { + QuicSession* session = static_cast(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + session->PathValidation(path, res); + return 0; +} + +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(user_data); + QuicSession::Ngtcp2CallbackScope callback_scope(session); + session->VersionNegotiation(hd, sv, nsv); + return 0; +} + +void QuicSession::OnKeylog(const SSL* ssl, const char* line) { + QuicSession* session = static_cast(SSL_get_app_data(ssl)); + session->Keylog(line); +} + +int QuicSession::OnStatelessReset( + ngtcp2_conn* conn, + const ngtcp2_pkt_stateless_reset* sr, + void* user_data) { + QuicSession* session = static_cast(user_data); + session->SilentClose(true); + return 0; +} + +const ngtcp2_conn_callbacks QuicServerSession::callbacks = { + nullptr, + OnReceiveClientInitial, + OnReceiveCryptoData, + OnHandshakeCompleted, + nullptr, // recv_version_negotiation + OnDoHSEncrypt, + OnDoHSDecrypt, + OnDoEncrypt, + OnDoDecrypt, + OnDoInHPMask, + OnDoHPMask, + OnReceiveStreamData, + OnAckedCryptoOffset, + OnAckedStreamDataOffset, + OnStreamOpen, + OnStreamClose, + OnStatelessReset, + nullptr, // recv_retry + nullptr, // extend_max_streams_bidi + nullptr, // extend_max_streams_uni + OnRand, + OnGetNewConnectionID, + OnRemoveConnectionID, + OnUpdateKey, + OnPathValidation, + nullptr, // select_preferred_addr + OnStreamReset, + OnExtendMaxStreamsBidi, + OnExtendMaxStreamsUni, + OnExtendMaxStreamData +}; + +const ngtcp2_conn_callbacks QuicClientSession::callbacks = { + OnClientInitial, + nullptr, + OnReceiveCryptoData, + OnHandshakeCompleted, + OnVersionNegotiation, + OnDoHSEncrypt, + OnDoHSDecrypt, + OnDoEncrypt, + OnDoDecrypt, + OnDoInHPMask, + OnDoHPMask, + OnReceiveStreamData, + OnAckedCryptoOffset, + OnAckedStreamDataOffset, + OnStreamOpen, + OnStreamClose, + OnStatelessReset, + OnReceiveRetry, + OnExtendMaxStreamsBidi, + OnExtendMaxStreamsUni, + OnRand, + OnGetNewConnectionID, + OnRemoveConnectionID, + OnUpdateKey, + OnPathValidation, + OnSelectPreferredAddress, + OnStreamReset, + OnExtendMaxStreamsBidi, + OnExtendMaxStreamsUni, + OnExtendMaxStreamData +}; + + // JavaScript API namespace { @@ -2786,7 +3396,7 @@ void QuicSessionClose(const FunctionCallbackInfo& args) { ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); int family = QUIC_ERROR_SESSION; uint64_t code = ExtractErrorCode(env, args[0]); - USE(args[1]->Int32Value(env->context()).To(&family)); + if (!args[1]->Int32Value(env->context()).To(&family)) return; session->SetLastError(static_cast(family), code); session->SendConnectionClose(); } @@ -2809,8 +3419,8 @@ void QuicSessionDestroy(const FunctionCallbackInfo& args) { ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); int code = 0; int family = QUIC_ERROR_SESSION; - USE(args[0]->Int32Value(env->context()).To(&code)); - USE(args[1]->Int32Value(env->context()).To(&family)); + if (!args[0]->Int32Value(env->context()).To(&code)) return; + if (!args[1]->Int32Value(env->context()).To(&family)) return; session->SetLastError(static_cast(family), code); session->Destroy(); } @@ -2992,8 +3602,8 @@ void NewQuicClientSession(const FunctionCallbackInfo& args) { return args.GetReturnValue().Set(err); int select_preferred_address_policy = QUIC_PREFERRED_ADDRESS_IGNORE; - USE(args[10]->Int32Value( - env->context()).To(&select_preferred_address_policy)); + if (!args[10]->Int32Value(env->context()) + .To(&select_preferred_address_policy)) return; std::string alpn(NGTCP2_ALPN_H3); if (args[11]->IsString()) { @@ -3003,7 +3613,7 @@ void NewQuicClientSession(const FunctionCallbackInfo& args) { } uint32_t options = QUICCLIENTSESSION_OPTION_VERIFY_HOSTNAME_IDENTITY; - USE(args[12]->Uint32Value(env->context()).To(&options)); + if (!args[12]->Uint32Value(env->context()).To(&options)) return; socket->ReceiveStart(); @@ -3034,12 +3644,14 @@ void AddMethods(Environment* env, Local session) { env->SetProtoMethod(session, "destroy", QuicSessionDestroy); env->SetProtoMethod(session, "getRemoteAddress", QuicSessionGetRemoteAddress); env->SetProtoMethod(session, "getCertificate", QuicSessionGetCertificate); - env->SetProtoMethod(session, - "getPeerCertificate", + env->SetProtoMethod(session, "getPeerCertificate", QuicSessionGetPeerCertificate); env->SetProtoMethod(session, "gracefulClose", QuicSessionGracefulClose); env->SetProtoMethod(session, "updateKey", QuicSessionUpdateKey); env->SetProtoMethod(session, "ping", QuicSessionPing); + env->SetProtoMethod(session, "onClientHelloDone", + QuicSessionOnClientHelloDone); + env->SetProtoMethod(session, "onCertDone", QuicSessionOnCertDone); } } // namespace diff --git a/src/node_quic_session.h b/src/node_quic_session.h index e064df18d8..b5d763ae85 100644 --- a/src/node_quic_session.h +++ b/src/node_quic_session.h @@ -41,44 +41,42 @@ class QuicStream; // in non-numeric settings (e.g. preferred_addr). class QuicSessionConfig { public: - inline QuicSessionConfig() { + QuicSessionConfig() { ResetToDefaults(); } - inline explicit QuicSessionConfig(Environment* env) { - ResetToDefaults(); + explicit QuicSessionConfig(Environment* env) : QuicSessionConfig() { Set(env); } - inline QuicSessionConfig(const QuicSessionConfig& config) { - memcpy(&settings_, &config.settings_, sizeof(ngtcp2_settings)); + QuicSessionConfig(const QuicSessionConfig& config) { + settings_ = config.settings_; max_crypto_buffer_ = config.max_crypto_buffer_; settings_.initial_ts = uv_hrtime(); } - inline uint64_t max_streams_bidi() const { + uint64_t max_streams_bidi() const { return settings_.max_streams_bidi; } - inline uint64_t max_streams_uni() const { + uint64_t max_streams_uni() const { return settings_.max_streams_uni; } - inline void ResetToDefaults(); + void ResetToDefaults(); // 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 - inline void Set( - Environment* env, - const struct sockaddr* preferred_addr = nullptr); + void Set(Environment* env, + const struct sockaddr* preferred_addr = nullptr); // Generates the stateless reset token for the settings_ - inline void GenerateStatelessResetToken(); + void GenerateStatelessResetToken(); // If the preferred address is set, generates the associated tokens - inline void GeneratePreferredAddressToken(ngtcp2_cid* pscid); + void GeneratePreferredAddressToken(ngtcp2_cid* pscid); uint64_t GetMaxCryptoBuffer() const { return max_crypto_buffer_; } @@ -93,7 +91,7 @@ class QuicSessionConfig { // QuicServerSession. These are set on the QuicSocket when // the listen() function is called and are passed to the // constructor of the QuicServerSession. -typedef enum QuicServerSessionOptions : uint32_t { +enum QuicServerSessionOptions : uint32_t { // When set, instructs the QuicServerSession to reject // client authentication certs that cannot be verified. QUICSERVERSESSION_OPTION_REJECT_UNAUTHORIZED = 0x1, @@ -101,12 +99,12 @@ typedef enum QuicServerSessionOptions : uint32_t { // When set, instructs the QuicServerSession to request // a client authentication cert QUICSERVERSESSION_OPTION_REQUEST_CERT = 0x2 -} QuicServerSessionOptions; +}; // Options to alter the behavior of various functions on the // QuicClientSession. These are set on the QuicClientSession // constructor. -typedef enum QuicClientSessionOptions : uint32_t { +enum QuicClientSessionOptions : uint32_t { // When set, instructs the QuicClientSession to include an // OCSP request in the initial TLS handshake QUICCLIENTSESSION_OPTION_REQUEST_OCSP = 0x1, @@ -120,14 +118,14 @@ typedef enum QuicClientSessionOptions : uint32_t { // When set, instructs the QuicClientSession to perform // additional checks on TLS session resumption. QUICCLIENTSESSION_OPTION_RESUME = 0x4 -} QuicClientSessionOptions; +}; // The QuicSessionState enums are used with the QuicSession's // private state_ array. This is exposed to JavaScript via an // aliased buffer and is used to communicate various types of // state efficiently across the native/JS boundary. -typedef enum QuicSessionState : int { +enum QuicSessionState : int { // Communicates whether a 'keylog' event listener has been // registered on the JavaScript QuicSession object. The // value will be either 1 or 0. When set to 1, the native @@ -165,7 +163,7 @@ typedef enum QuicSessionState : int { // Just the number of session state enums for use when // creating the AliasedBuffer. IDX_QUIC_SESSION_STATE_COUNT -} QuicSessionState; +}; // The QuicSession class is an virtual class that serves as // the basis for both QuicServerSession and QuicClientSession. @@ -185,7 +183,7 @@ typedef enum QuicSessionState : int { // a QuicSession object. class QuicSession : public AsyncWrap, public std::enable_shared_from_this, - public mem::Tracker { + public mem::NgLibMemoryManager { public: static const int kInitialClientBufferLength = 4096; @@ -212,16 +210,16 @@ class QuicSession : public AsyncWrap, std::string diagnostic_name() const override; - inline QuicError GetLastError(); + inline QuicError GetLastError() const; inline void SetTLSAlert(int err); // Returns true if StartGracefulClose() has been called and the // QuicSession is currently in the process of a graceful close. - inline bool IsGracefullyClosing(); + inline bool IsGracefullyClosing() const; // Returns true if Destroy() has been called and the // QuicSession is no longer usable. - inline bool IsDestroyed(); + inline bool IsDestroyed() const; // Starting a GracefulClose disables the ability to open or accept // new streams for this session. Existing streams are allowed to @@ -340,10 +338,10 @@ class QuicSession : public AsyncWrap, virtual bool SendConnectionClose() = 0; virtual int TLSHandshake_Initial() = 0; - // Implementation for mem::Tracker - inline void CheckAllocatedSize(size_t previous_size) override; - inline void IncrementAllocatedSize(size_t size) override; - inline void DecrementAllocatedSize(size_t size) override; + // Implementation for mem::NgLibMemoryManager + inline void CheckAllocatedSize(size_t previous_size) const; + inline void IncreaseAllocatedSize(size_t size); + inline void DecreaseAllocatedSize(size_t size); // Tracks whether or not we are currently within an ngtcp2 callback // function. Certain ngtcp2 APIs are not supposed to be called when @@ -387,7 +385,7 @@ class QuicSession : public AsyncWrap, inline QuicStream* FindStream(int64_t id); inline bool HasStream(int64_t id); - bool IsHandshakeSuspended() { + bool IsHandshakeSuspended() const { return IsFlagSet(QUICSESSION_FLAG_CERT_CB_RUNNING) || IsFlagSet(QUICSESSION_FLAG_CLIENT_HELLO_CB_RUNNING); } @@ -548,24 +546,24 @@ class QuicSession : public AsyncWrap, virtual int VerifyPeerIdentity(const char* hostname) = 0; // static ngtcp2 callbacks - static inline int OnClientInitial( + static int OnClientInitial( ngtcp2_conn* conn, void* user_data); - static inline int OnReceiveClientInitial( + static int OnReceiveClientInitial( ngtcp2_conn* conn, const ngtcp2_cid* dcid, void* user_data); - static inline int OnReceiveCryptoData( + 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 inline int OnHandshakeCompleted( + static int OnHandshakeCompleted( ngtcp2_conn* conn, void* user_data); - static inline ssize_t OnDoHSEncrypt( + static ssize_t OnDoHSEncrypt( ngtcp2_conn* conn, uint8_t* dest, size_t destlen, @@ -578,7 +576,7 @@ class QuicSession : public AsyncWrap, const uint8_t* ad, size_t adlen, void* user_data); - static inline ssize_t OnDoHSDecrypt( + static ssize_t OnDoHSDecrypt( ngtcp2_conn* conn, uint8_t* dest, size_t destlen, @@ -591,7 +589,7 @@ class QuicSession : public AsyncWrap, const uint8_t* ad, size_t adlen, void* user_data); - static inline ssize_t OnDoEncrypt( + static ssize_t OnDoEncrypt( ngtcp2_conn* conn, uint8_t* dest, size_t destlen, @@ -604,7 +602,7 @@ class QuicSession : public AsyncWrap, const uint8_t* ad, size_t adlen, void* user_data); - static inline ssize_t OnDoDecrypt( + static ssize_t OnDoDecrypt( ngtcp2_conn* conn, uint8_t* dest, size_t destlen, @@ -617,7 +615,7 @@ class QuicSession : public AsyncWrap, const uint8_t* ad, size_t adlen, void* user_data); - static inline ssize_t OnDoInHPMask( + static ssize_t OnDoInHPMask( ngtcp2_conn* conn, uint8_t* dest, size_t destlen, @@ -626,7 +624,7 @@ class QuicSession : public AsyncWrap, const uint8_t* sample, size_t samplelen, void* user_data); - static inline ssize_t OnDoHPMask( + static ssize_t OnDoHPMask( ngtcp2_conn* conn, uint8_t* dest, size_t destlen, @@ -635,7 +633,7 @@ class QuicSession : public AsyncWrap, const uint8_t* sample, size_t samplelen, void* user_data); - static inline int OnReceiveStreamData( + static int OnReceiveStreamData( ngtcp2_conn* conn, int64_t stream_id, int fin, @@ -644,107 +642,102 @@ class QuicSession : public AsyncWrap, size_t datalen, void* user_data, void* stream_user_data); - static inline int OnReceiveRetry( + static int OnReceiveRetry( ngtcp2_conn* conn, const ngtcp2_pkt_hd* hd, const ngtcp2_pkt_retry* retry, void* user_data); - static inline int OnAckedCryptoOffset( + static int OnAckedCryptoOffset( ngtcp2_conn* conn, ngtcp2_crypto_level crypto_level, uint64_t offset, size_t datalen, void* user_data); - static inline int OnAckedStreamDataOffset( + static int OnAckedStreamDataOffset( ngtcp2_conn* conn, int64_t stream_id, uint64_t offset, size_t datalen, void* user_data, void* stream_user_data); - static inline int OnSelectPreferredAddress( + static int OnSelectPreferredAddress( ngtcp2_conn* conn, ngtcp2_addr* dest, const ngtcp2_preferred_addr* paddr, void* user_data); - static inline int OnStreamClose( + static int OnStreamClose( ngtcp2_conn* conn, int64_t stream_id, uint64_t app_error_code, void* user_data, void* stream_user_data); - static inline int OnStreamOpen( + static int OnStreamOpen( ngtcp2_conn* conn, int64_t stream_id, void* user_data); - static inline int OnStreamReset( + 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 inline int OnRand( + static int OnRand( ngtcp2_conn* conn, uint8_t* dest, size_t destlen, ngtcp2_rand_ctx ctx, void* user_data); - static inline int OnGetNewConnectionID( + static int OnGetNewConnectionID( ngtcp2_conn* conn, ngtcp2_cid* cid, uint8_t* token, size_t cidlen, void* user_data); - static inline int OnRemoveConnectionID( + static int OnRemoveConnectionID( ngtcp2_conn* conn, const ngtcp2_cid* cid, void* user_data); - static inline int OnUpdateKey( + static int OnUpdateKey( ngtcp2_conn* conn, void* user_data); - static inline int OnPathValidation( + static int OnPathValidation( ngtcp2_conn* conn, const ngtcp2_path* path, ngtcp2_path_validation_result res, void* user_data); - static inline void OnIdleTimeout( - uv_timer_t* timer); - static inline int OnExtendMaxStreamsUni( + static int OnExtendMaxStreamsUni( ngtcp2_conn* conn, uint64_t max_streams, void* user_data); - static inline int OnExtendMaxStreamsBidi( + static int OnExtendMaxStreamsBidi( ngtcp2_conn* conn, uint64_t max_streams, void* user_data); - static inline int OnExtendMaxStreamData( + static int OnExtendMaxStreamData( ngtcp2_conn* conn, int64_t stream_id, uint64_t max_data, void* user_data, void* stream_user_data); - static inline int OnVersionNegotiation( + static int OnVersionNegotiation( ngtcp2_conn* conn, const ngtcp2_pkt_hd* hd, const uint32_t* sv, size_t nsv, void* user_data); - static inline void OnKeylog(const SSL* ssl, const char* line); - static inline int OnStatelessReset( + static void OnKeylog(const SSL* ssl, const char* line); + static int OnStatelessReset( ngtcp2_conn* conn, const ngtcp2_pkt_stateless_reset* sr, void* user_data); - static inline void OnIdleTimeoutCB(void* data); - static inline void OnRetransmitTimeoutCB(void* data); - void UpdateIdleTimer(); void UpdateRetransmitTimer(uint64_t timeout); void StopRetransmitTimer(); void StopIdleTimer(); - typedef enum QuicSessionFlags : uint32_t { + enum QuicSessionFlags : uint32_t { // Initial state when a QuicSession is created but nothing yet done. QUICSESSION_FLAG_INITIAL = 0x1, @@ -777,7 +770,7 @@ class QuicSession : public AsyncWrap, // Set if the QuicSession is in the middle of a silent close // (that is, a CONNECTION_CLOSE should not be sent) QUICSESSION_FLAG_SILENT_CLOSE = 0x200 - } QuicSessionFlags; + }; void SetFlag(QuicSessionFlags flag, bool on = true) { if (on) @@ -786,7 +779,7 @@ class QuicSession : public AsyncWrap, flags_ &= ~flag; } - bool IsFlagSet(QuicSessionFlags flag) { + bool IsFlagSet(QuicSessionFlags flag) const { return flags_ & flag; } @@ -797,7 +790,7 @@ class QuicSession : public AsyncWrap, options_ &= ~option; } - bool IsOptionSet(uint32_t option) { + bool IsOptionSet(uint32_t option) const { return options_ & option; } @@ -883,7 +876,7 @@ class QuicSession : public AsyncWrap, AliasedFloat64Array state_; - mem::Allocator allocator_; + ngtcp2_mem alloc_info_; struct session_stats { // The timestamp at which the session was created @@ -1022,11 +1015,11 @@ class QuicSession : public AsyncWrap, class QuicServerSession : public QuicSession { public: - typedef enum InitialPacketResult : int { + enum InitialPacketResult : int { PACKET_OK, PACKET_IGNORE, PACKET_VERSION - } InitialPacketResult; + }; static InitialPacketResult Accept( ngtcp2_pkt_hd* hd, @@ -1124,38 +1117,7 @@ class QuicServerSession : public QuicSession { MallocedBuffer conn_closebuf_; v8::Global ocsp_response_; - const ngtcp2_conn_callbacks callbacks_ = { - nullptr, - OnReceiveClientInitial, - OnReceiveCryptoData, - OnHandshakeCompleted, - nullptr, // recv_version_negotiation - OnDoHSEncrypt, - OnDoHSDecrypt, - OnDoEncrypt, - OnDoDecrypt, - OnDoInHPMask, - OnDoHPMask, - OnReceiveStreamData, - OnAckedCryptoOffset, - OnAckedStreamDataOffset, - OnStreamOpen, - OnStreamClose, - OnStatelessReset, - nullptr, // recv_retry - nullptr, // extend_max_streams_bidi - nullptr, // extend_max_streams_uni - OnRand, - OnGetNewConnectionID, - OnRemoveConnectionID, - OnUpdateKey, - OnPathValidation, - nullptr, // select_preferred_addr - OnStreamReset, - OnExtendMaxStreamsBidi, - OnExtendMaxStreamsUni, - OnExtendMaxStreamData - }; + static const ngtcp2_conn_callbacks callbacks; friend class QuicSession; }; @@ -1263,38 +1225,7 @@ class QuicClientSession : public QuicSession { MaybeStackBuffer transportParams_; - const ngtcp2_conn_callbacks callbacks_ = { - OnClientInitial, - nullptr, - OnReceiveCryptoData, - OnHandshakeCompleted, - OnVersionNegotiation, - OnDoHSEncrypt, - OnDoHSDecrypt, - OnDoEncrypt, - OnDoDecrypt, - OnDoInHPMask, - OnDoHPMask, - OnReceiveStreamData, - OnAckedCryptoOffset, - OnAckedStreamDataOffset, - OnStreamOpen, - OnStreamClose, - OnStatelessReset, - OnReceiveRetry, - OnExtendMaxStreamsBidi, - OnExtendMaxStreamsUni, - OnRand, - OnGetNewConnectionID, - OnRemoveConnectionID, - OnUpdateKey, - OnPathValidation, - OnSelectPreferredAddress, - OnStreamReset, - OnExtendMaxStreamsBidi, - OnExtendMaxStreamsUni, - OnExtendMaxStreamData - }; + static const ngtcp2_conn_callbacks callbacks; friend class QuicSession; }; diff --git a/src/node_quic_socket.cc b/src/node_quic_socket.cc index b6778360e1..14adba2fb6 100644 --- a/src/node_quic_socket.cc +++ b/src/node_quic_socket.cc @@ -5,6 +5,7 @@ #include "node.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_socket.h" @@ -70,20 +71,15 @@ QuicSocket::QuicSocket( HandleWrap(env, wrap, reinterpret_cast(&handle_), AsyncWrap::PROVIDER_QUICSOCKET), - flags_(QUICSOCKET_FLAGS_NONE), options_(options), - pending_callbacks_(0), max_connections_per_host_(max_connections_per_host), - current_ngtcp2_memory_(0), retry_token_expiration_(retry_token_expiration), - rx_loss_(0.0), - tx_loss_(0.0), - server_secure_context_(nullptr), server_alpn_(NGTCP2_ALPN_H3), stats_buffer_( - env->isolate(), - sizeof(socket_stats_) / sizeof(uint64_t), - reinterpret_cast(&socket_stats_)) { + env->isolate(), + sizeof(socket_stats_) / sizeof(uint64_t), + reinterpret_cast(&socket_stats_)), + alloc_info_(MakeAllocator()) { CHECK_EQ(uv_udp_init(env->event_loop(), &handle_), 0); Debug(this, "New QuicSocket created."); @@ -280,23 +276,19 @@ void QuicSocket::OnAlloc( uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { - buf->base = node::Malloc(suggested_size); - buf->len = suggested_size; + QuicSocket* socket = + ContainerOf(&QuicSocket::handle_, reinterpret_cast(handle)); + *buf = socket->env()->AllocateManaged(suggested_size).release(); } void QuicSocket::OnRecv( uv_udp_t* handle, ssize_t nread, - const uv_buf_t* buf, + const uv_buf_t* buf_, const struct sockaddr* addr, unsigned int flags) { - OnScopeLeave on_scope_leave([&]() { - if (buf->base != nullptr) - free(buf->base); - }); - - QuicSocket* socket = static_cast(handle->data); - CHECK_NOT_NULL(socket); + QuicSocket* socket = ContainerOf(&QuicSocket::handle_, handle); + AllocatedBuffer buf(socket->env(), *buf_); if (nread == 0) return; @@ -311,12 +303,12 @@ void QuicSocket::OnRecv( return; } - socket->Receive(nread, buf, addr, flags); + socket->Receive(nread, std::move(buf), addr, flags); } void QuicSocket::Receive( ssize_t nread, - const uv_buf_t* buf, + AllocatedBuffer buf, const struct sockaddr* addr, unsigned int flags) { Debug(this, "Receiving %d bytes from the UDP socket.", nread); @@ -330,7 +322,7 @@ void QuicSocket::Receive( IncrementSocketStat(nread, &socket_stats_, &socket_stats::bytes_received); - const uint8_t* data = reinterpret_cast(buf->base); + const uint8_t* data = reinterpret_cast(buf.data()); uint32_t pversion; const uint8_t* pdcid; @@ -363,8 +355,8 @@ void QuicSocket::Receive( QuicCID dcid(pdcid, pdcidlen); QuicCID scid(pscid, pscidlen); - auto dcid_hex = dcid.ToHex(); - auto dcid_str = dcid.ToStr(); + std::string dcid_hex = dcid.ToHex(); + std::string dcid_str = dcid.ToStr(); Debug(this, "Received a QUIC packet for dcid %s", dcid_hex.c_str()); // Grabbing a shared pointer to prevent the QuicSession from @@ -486,8 +478,6 @@ void QuicSocket::SendInitialConnectionClose( ngtcp2_settings settings; ngtcp2_settings_default(&settings); - mem::Allocator allocator(this); - ngtcp2_conn* conn; ngtcp2_conn_server_new( &conn, @@ -497,7 +487,7 @@ void QuicSocket::SendInitialConnectionClose( version, &callbacks, &settings, - *allocator, + &alloc_info_, nullptr); SendWrapStack* req = @@ -625,11 +615,11 @@ void QuicSocket::SetValidatedAddress(const sockaddr* addr) { } } -bool QuicSocket::IsValidatedAddress(const sockaddr* addr) { +bool QuicSocket::IsValidatedAddress(const sockaddr* addr) const { if (IsOptionSet(QUICSOCKET_OPTIONS_VALIDATE_ADDRESS_LRU)) { auto res = std::find(std::begin(validated_addrs_), std::end(validated_addrs_), - addr_hash((addr))); + addr_hash(addr)); return res != std::end(validated_addrs_); } return false; @@ -643,7 +633,6 @@ std::shared_ptr QuicSocket::AcceptInitialPacket( const uint8_t* data, const struct sockaddr* addr, unsigned int flags) { - std::shared_ptr session; HandleScope handle_scope(env()->isolate()); Context::Scope context_scope(env()->context()); ngtcp2_pkt_hd hd; @@ -653,7 +642,7 @@ std::shared_ptr QuicSocket::AcceptInitialPacket( if (!IsFlagSet(QUICSOCKET_FLAGS_SERVER_LISTENING)) { Debug(this, "QuicSocket is not listening"); - return session; + return {}; } // Perform some initial checks on the packet to see if it is an @@ -663,9 +652,8 @@ std::shared_ptr QuicSocket::AcceptInitialPacket( SendVersionNegotiation(version, dcid, scid, addr); // Fall-through to ignore packet case QuicServerSession::InitialPacketResult::PACKET_IGNORE: - return session; + return {}; case QuicServerSession::InitialPacketResult::PACKET_OK: - // Fall-through break; } @@ -711,7 +699,7 @@ std::shared_ptr QuicSocket::AcceptInitialPacket( retry_token_expiration_)) { Debug(this, "A valid retry token was not found. Sending retry."); SendRetry(version, dcid, scid, addr); - return session; + return {}; } Debug(this, "A valid retry token was found. Continuing."); SetValidatedAddress(addr); @@ -721,7 +709,7 @@ std::shared_ptr QuicSocket::AcceptInitialPacket( } } - session = + std::shared_ptr session { QuicServerSession::New( this, &server_session_config_, @@ -732,7 +720,7 @@ std::shared_ptr QuicSocket::AcceptInitialPacket( version, server_alpn_, server_options_, - initial_connection_close); + initial_connection_close) }; Local arg = session->object(); MakeCallback(env()->quic_on_session_ready_function(), 1, &arg); @@ -762,7 +750,7 @@ size_t QuicSocket::GetCurrentSocketAddressCounter(const sockaddr* addr) { auto it = addr_counts_.find(addr); if (it == std::end(addr_counts_)) return 0; - return (*it).second; + return it->second; } void QuicSocket::SetServerBusy(bool on) { @@ -841,13 +829,13 @@ void QuicSocket::OnSend( size_t length, const char* diagnostic_label) { IncrementSocketStat( - length, - &socket_stats_, - &socket_stats::bytes_sent); + length, + &socket_stats_, + &socket_stats::bytes_sent); IncrementSocketStat( - 1, - &socket_stats_, - &socket_stats::packets_sent); + 1, + &socket_stats_, + &socket_stats::packets_sent); Debug(this, "Packet sent status: %d (label: %s)", status, @@ -928,18 +916,16 @@ QuicSocket::SendWrap::SendWrap( SocketAddress* dest, QuicBuffer* buffer, std::shared_ptr session, - const char* diagnostic_label) : - SendWrapBase(socket, **dest, diagnostic_label), - buffer_(buffer), - session_(session) {} + const char* diagnostic_label) + : SendWrap(socket, **dest, buffer, session, diagnostic_label) {} QuicSocket::SendWrap::SendWrap( QuicSocket* socket, const sockaddr* dest, QuicBuffer* buffer, std::shared_ptr session, - const char* diagnostic_label) : - SendWrapBase(socket, dest, diagnostic_label), + const char* diagnostic_label) + : SendWrapBase(socket, dest, diagnostic_label), buffer_(buffer), session_(session) {} @@ -1011,31 +997,33 @@ void QuicSocket::SetDiagnosticPacketLoss(double rx, double tx) { tx_loss_ = tx; } -inline void QuicSocket::CheckAllocatedSize(size_t previous_size) { +void QuicSocket::CheckAllocatedSize(size_t previous_size) const { CHECK_GE(current_ngtcp2_memory_, previous_size); } -inline void QuicSocket::IncrementAllocatedSize(size_t size) { +void QuicSocket::IncreaseAllocatedSize(size_t size) { current_ngtcp2_memory_ += size; } -inline void QuicSocket::DecrementAllocatedSize(size_t size) { +void QuicSocket::DecreaseAllocatedSize(size_t size) { current_ngtcp2_memory_ -= size; } - // JavaScript API namespace { void NewQuicSocket(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(args.IsConstructCall()); - uint32_t options = 0; - USE(args[0]->Uint32Value(env->context()).To(&options)); - uint32_t retry_token_expiration = DEFAULT_RETRYTOKEN_EXPIRATION; - uint32_t max_connections_per_host = DEFAULT_MAX_CONNECTIONS_PER_HOST; - USE(args[1]->Uint32Value(env->context()).To(&retry_token_expiration)); - USE(args[2]->Uint32Value(env->context()).To(&max_connections_per_host)); + uint32_t options; + uint32_t retry_token_expiration; + uint32_t max_connections_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_per_host)) { + return; + } CHECK_GE(retry_token_expiration, MIN_RETRYTOKEN_EXPIRATION); CHECK_LE(retry_token_expiration, MAX_RETRYTOKEN_EXPIRATION); @@ -1055,14 +1043,13 @@ void NewQuicSocket(const FunctionCallbackInfo& args) { // arguments to a value between 0.0 and 1.0. Setting both values to 0.0 // disables the mechanism. void QuicSocketSetDiagnosticPacketLoss( - const FunctionCallbackInfo& args) { + const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); QuicSocket* socket; ASSIGN_OR_RETURN_UNWRAP(&socket, args.Holder()); - double rx = 0.0f; - double tx = 0.0f; - USE(args[0]->NumberValue(env->context()).To(&rx)); - USE(args[1]->NumberValue(env->context()).To(&tx)); + 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); @@ -1129,7 +1116,8 @@ void QuicSocketListen(const FunctionCallbackInfo& args) { QuicSocket* socket; ASSIGN_OR_RETURN_UNWRAP(&socket, args.Holder(), args.GetReturnValue().Set(UV_EBADF)); - CHECK(args[0]->IsObject()); // Secure Context + CHECK(args[0]->IsObject() && + env->secure_context_constructor_template()->HasInstance(args[0])); SecureContext* sc; ASSIGN_OR_RETURN_UNWRAP(&sc, args[0].As(), args.GetReturnValue().Set(UV_EBADF)); @@ -1141,9 +1129,10 @@ void QuicSocketListen(const FunctionCallbackInfo& args) { 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) && - SocketAddress::ToSockAddr( + 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, @@ -1161,7 +1150,7 @@ void QuicSocketListen(const FunctionCallbackInfo& args) { } uint32_t options = 0; - USE(args[5]->Uint32Value(env->context()).To(&options)); + if (!args[5]->Uint32Value(env->context()).To(&options)) return; socket->Listen(sc, preferred_address, alpn, options); } diff --git a/src/node_quic_socket.h b/src/node_quic_socket.h index 852378f0b8..f31010eaef 100644 --- a/src/node_quic_socket.h +++ b/src/node_quic_socket.h @@ -31,7 +31,7 @@ namespace quic { static constexpr size_t MAX_VALIDATE_ADDRESS_LRU = 10; -typedef enum QuicSocketOptions : uint32_t { +enum QuicSocketOptions : uint32_t { // When enabled the QuicSocket will validate the address // using a RETRY packet to the peer. QUICSOCKET_OPTIONS_VALIDATE_ADDRESS = 0x1, @@ -41,10 +41,10 @@ typedef enum QuicSocketOptions : uint32_t { // validated addresses. Address validation will be skipped // if the address is currently in the cache. QUICSOCKET_OPTIONS_VALIDATE_ADDRESS_LRU = 0x2, -} QuicSocketOptions; +}; class QuicSocket : public HandleWrap, - public mem::Tracker { + public mem::NgLibMemoryManager { public: static void Initialize( Environment* env, @@ -126,10 +126,10 @@ class QuicSocket : public HandleWrap, SET_MEMORY_INFO_NAME(QuicSocket) SET_SELF_SIZE(QuicSocket) - // Implementation for mem::Tracker - inline void CheckAllocatedSize(size_t previous_size) override; - inline void IncrementAllocatedSize(size_t size) override; - inline void DecrementAllocatedSize(size_t size) override; + // Implementation for mem::NgLibMemoryManager + void CheckAllocatedSize(size_t previous_size) const; + void IncreaseAllocatedSize(size_t size); + void DecreaseAllocatedSize(size_t size); private: static void OnAlloc( @@ -146,7 +146,7 @@ class QuicSocket : public HandleWrap, void Receive( ssize_t nread, - const uv_buf_t* buf, + AllocatedBuffer buf, const struct sockaddr* addr, unsigned int flags); @@ -168,8 +168,7 @@ class QuicSocket : public HandleWrap, const char* diagnostic_label); void SetValidatedAddress(const sockaddr* addr); - - bool IsValidatedAddress(const sockaddr* addr); + bool IsValidatedAddress(const sockaddr* addr) const; std::shared_ptr AcceptInitialPacket( uint32_t version, @@ -205,7 +204,7 @@ class QuicSocket : public HandleWrap, // Fields and TypeDefs typedef uv_udp_t HandleType; - typedef enum QuicSocketFlags : uint32_t { + enum QuicSocketFlags : uint32_t { QUICSOCKET_FLAGS_NONE = 0x0, // Indicates that the QuicSocket has entered a graceful @@ -214,7 +213,7 @@ class QuicSocket : public HandleWrap, QUICSOCKET_FLAGS_PENDING_CLOSE = 0x2, QUICSOCKET_FLAGS_SERVER_LISTENING = 0x4, QUICSOCKET_FLAGS_SERVER_BUSY = 0x8, - } QuicSocketFlags; + }; void SetFlag(QuicSocketFlags flag, bool on = true) { if (on) @@ -223,7 +222,7 @@ class QuicSocket : public HandleWrap, flags_ &= ~flag; } - bool IsFlagSet(QuicSocketFlags flag) { + bool IsFlagSet(QuicSocketFlags flag) const { return flags_ & flag; } @@ -234,28 +233,28 @@ class QuicSocket : public HandleWrap, options_ &= ~option; } - bool IsOptionSet(QuicSocketOptions option) { + bool IsOptionSet(QuicSocketOptions option) const { return options_ & option; } uv_udp_t handle_; - uint32_t flags_; + uint32_t flags_ = QUICSOCKET_FLAGS_NONE; uint32_t options_; uint32_t server_options_; - size_t pending_callbacks_; + size_t pending_callbacks_ = 0; size_t max_connections_per_host_; - size_t current_ngtcp2_memory_; + size_t current_ngtcp2_memory_ = 0; uint64_t retry_token_expiration_; // Used to specify diagnostic packet loss probabilities - double rx_loss_; - double tx_loss_; + double rx_loss_ = 0.0; + double tx_loss_ = 0.0; SocketAddress local_address_; QuicSessionConfig server_session_config_; - crypto::SecureContext* server_secure_context_; + crypto::SecureContext* server_secure_context_ = nullptr; std::string server_alpn_; std::unordered_map> sessions_; std::unordered_map dcid_to_scid_; @@ -270,7 +269,7 @@ class QuicSocket : public HandleWrap, // attempts to create new connections will be ignored // until the value falls back below the limit. std::unordered_map - addr_counts_; + addr_counts_; // The validated_addrs_ vector is used as an LRU cache for // validated addresses only when the VALIDATE_ADDRESS_LRU @@ -319,6 +318,8 @@ class QuicSocket : public HandleWrap, AliasedBigUint64Array stats_buffer_; + ngtcp2_mem alloc_info_; + template void IncrementSocketStat( uint64_t amount, diff --git a/src/node_quic_state.h b/src/node_quic_state.h index d191df45ad..2772c763ec 100644 --- a/src/node_quic_state.h +++ b/src/node_quic_state.h @@ -7,7 +7,7 @@ namespace node { -typedef enum QuicSessionConfigIndex : int { +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, @@ -22,7 +22,7 @@ typedef enum QuicSessionConfigIndex : int { IDX_QUIC_SESSION_MAX_ACK_DELAY, IDX_QUIC_SESSION_MAX_CRYPTO_BUFFER, IDX_QUIC_SESSION_CONFIG_COUNT -} QuicSessionConfigIndex; +}; class QuicState { public: diff --git a/src/node_quic_stream.cc b/src/node_quic_stream.cc index 97f148f7bf..00a029922d 100644 --- a/src/node_quic_stream.cc +++ b/src/node_quic_stream.cc @@ -29,85 +29,59 @@ using v8::Value; namespace quic { -uv_buf_t QuicStreamListener::OnStreamAlloc(size_t size) { - Environment* env = static_cast(stream_)->env(); - return env->AllocateManaged(size).release(); -} - -void QuicStreamListener::OnStreamRead(ssize_t nread, const uv_buf_t& buf) { - QuicStream* stream = static_cast(stream_); - Environment* env = stream->env(); - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); - - if (nread < 0) { - PassReadErrorToPreviousListener(nread); - return; - } - - AllocatedBuffer buffer(stream->env(), buf); - stream->CallJSOnreadMethod(nread, buffer.ToArrayBuffer()); -} - QuicStream::QuicStream( QuicSession* session, Local wrap, - int64_t stream_id) : - AsyncWrap(session->env(), wrap, AsyncWrap::PROVIDER_QUICSTREAM), + int64_t stream_id) + : AsyncWrap(session->env(), wrap, AsyncWrap::PROVIDER_QUICSTREAM), StreamBase(session->env()), session_(session), stream_id_(stream_id), - max_offset_(0), - max_offset_ack_(0), - flags_(QUICSTREAM_FLAG_INITIAL), - available_outbound_length_(0), - inbound_consumed_data_while_paused_(0), data_rx_rate_( - HistogramBase::New( - session->env(), - 1, std::numeric_limits::max())), + HistogramBase::New( + session->env(), + 1, std::numeric_limits::max())), data_rx_size_( - HistogramBase::New( - session->env(), - 1, NGTCP2_MAX_PKT_SIZE)), + HistogramBase::New( + session->env(), + 1, NGTCP2_MAX_PKT_SIZE)), data_rx_ack_( - HistogramBase::New( - session->env(), - 1, std::numeric_limits::max())), + HistogramBase::New( + session->env(), + 1, std::numeric_limits::max())), stats_buffer_( - session->env()->isolate(), - sizeof(stream_stats_) / sizeof(uint64_t), - reinterpret_cast(&stream_stats_)) { + session->env()->isolate(), + sizeof(stream_stats_) / sizeof(uint64_t), + reinterpret_cast(&stream_stats_)) { CHECK_NOT_NULL(session); session->AddStream(this); Debug(this, "Created"); StreamBase::AttachToObject(GetObject()); - PushStreamListener(&stream_listener_); stream_stats_.created_at = uv_hrtime(); - USE(wrap->DefineOwnProperty( - env()->context(), - env()->stats_string(), - stats_buffer_.GetJSArray(), - PropertyAttribute::ReadOnly)); - - USE(wrap->DefineOwnProperty( - env()->context(), - FIXED_ONE_BYTE_STRING(env()->isolate(), "data_rx_rate"), - data_rx_rate_->object(), - PropertyAttribute::ReadOnly)); - - USE(wrap->DefineOwnProperty( - env()->context(), - FIXED_ONE_BYTE_STRING(env()->isolate(), "data_rx_size"), - data_rx_size_->object(), - PropertyAttribute::ReadOnly)); - - USE(wrap->DefineOwnProperty( - env()->context(), - FIXED_ONE_BYTE_STRING(env()->isolate(), "data_rx_ack"), - data_rx_ack_->object(), - PropertyAttribute::ReadOnly)); + if (wrap->DefineOwnProperty( + env()->context(), + env()->stats_string(), + stats_buffer_.GetJSArray(), + PropertyAttribute::ReadOnly).IsNothing()) return; + + if (wrap->DefineOwnProperty( + env()->context(), + FIXED_ONE_BYTE_STRING(env()->isolate(), "data_rx_rate"), + data_rx_rate_->object(), + PropertyAttribute::ReadOnly).IsNothing()) return; + + if (wrap->DefineOwnProperty( + env()->context(), + FIXED_ONE_BYTE_STRING(env()->isolate(), "data_rx_size"), + data_rx_size_->object(), + PropertyAttribute::ReadOnly).IsNothing()) return; + + if (wrap->DefineOwnProperty( + env()->context(), + FIXED_ONE_BYTE_STRING(env()->isolate(), "data_rx_ack"), + data_rx_ack_->object(), + PropertyAttribute::ReadOnly).IsNothing()) return; } std::string QuicStream::diagnostic_name() const { @@ -160,7 +134,7 @@ int QuicStream::DoShutdown(ShutdownWrap* req_wrap) { // Do nothing if the stream was already shutdown. Specifically, // we should not attempt to send anything on the QuicSession if (!IsWritable()) - return 1; + return UV_EPIPE; stream_stats_.closing_at = uv_hrtime(); SetWriteClose(); @@ -183,7 +157,7 @@ int QuicStream::DoWrite( // A write should not have happened if we've been destroyed or // the QuicStream is no longer (or was never) writable. if (IsDestroyed() || !IsWritable()) { - req_wrap->Done(UV_EOF); + req_wrap->Done(UV_EPIPE); return 0; } @@ -194,7 +168,7 @@ int QuicStream::DoWrite( streambuf_.Push( bufs, nbufs, - [&](int status, void* user_data) { + [req_wrap](int status) { // 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 @@ -205,10 +179,8 @@ int QuicStream::DoWrite( // in the sense of providing back-pressure, but // also means that writes will be significantly // less performant unless written in batches. - WriteWrap* req_wrap = static_cast(user_data); req_wrap->Done(status); }, - req_wrap, req_wrap->object()); Debug(this, "Queuing %" PRIu64 " bytes of data from %d buffers", length, nbufs); @@ -453,7 +425,7 @@ void QuicStreamShutdown(const FunctionCallbackInfo& args) { uint32_t family = QUIC_ERROR_APPLICATION; uint64_t code = ExtractErrorCode(env, args[0]); - USE(args[1]->Uint32Value(env->context()).To(&family)); + if (!args[1]->Uint32Value(env->context()).To(&family)) return; stream->Shutdown( family == QUIC_ERROR_APPLICATION ? diff --git a/src/node_quic_stream.h b/src/node_quic_stream.h index 723d02fab4..4e2e6f4412 100644 --- a/src/node_quic_stream.h +++ b/src/node_quic_stream.h @@ -19,12 +19,6 @@ namespace quic { class QuicSession; class QuicServerSession; -class QuicStreamListener : public StreamListener { - public: - uv_buf_t OnStreamAlloc(size_t suggested_size) override; - void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override; -}; - // QuicStream's are simple data flows that, fortunately, do not // require much. They may be: // @@ -95,7 +89,7 @@ class QuicStream : public AsyncWrap, public StreamBase, public std::enable_shared_from_this { public: - typedef enum QuicStreamStates : uint32_t { + enum QuicStreamStates : uint32_t { // QuicStream is fully open. Readable and Writable QUICSTREAM_FLAG_INITIAL = 0x0, @@ -125,24 +119,24 @@ class QuicStream : public AsyncWrap, // QuicStream has been destroyed QUICSTREAM_FLAG_DESTROYED = 0x40 - } QuicStreamStates; + }; - typedef enum QuicStreamDirection { + 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 - } QuicStreamDirection; + }; - typedef enum QuicStreamOrigin { + enum QuicStreamOrigin { // The QuicStream was created by the server. QUIC_STREAM_SERVER, // The QuicStream was created by the client. QUIC_STREAM_CLIENT - } QuicStreamOrigin; + }; static void Initialize( @@ -356,16 +350,15 @@ class QuicStream : public AsyncWrap, inline void IncrementStats(size_t datalen); - QuicStreamListener stream_listener_; QuicSession* session_; int64_t stream_id_; - uint64_t max_offset_; - uint64_t max_offset_ack_; - uint32_t flags_; + uint64_t max_offset_ = 0; + uint64_t max_offset_ack_ = 0; + uint32_t flags_ = QUICSTREAM_FLAG_INITIAL; QuicBuffer streambuf_; - size_t available_outbound_length_; - size_t inbound_consumed_data_while_paused_; + size_t available_outbound_length_ = 0; + size_t inbound_consumed_data_while_paused_ = 0; struct stream_stats { // The timestamp at which the stream was created diff --git a/src/node_quic_util.cc b/src/node_quic_util.cc index 7823f5bdc1..1a028d5931 100644 --- a/src/node_quic_util.cc +++ b/src/node_quic_util.cc @@ -19,7 +19,7 @@ void Timer::Free(Timer* timer) { void Timer::OnTimeout(uv_timer_t* timer) { Timer* t = ContainerOf(&Timer::timer_, timer); - t->OnTimeout(); + t->fn_(); } void Timer::CleanupHook(void* data) { diff --git a/src/node_quic_util.h b/src/node_quic_util.h index 0a0e59b464..adf33d1272 100644 --- a/src/node_quic_util.h +++ b/src/node_quic_util.h @@ -41,12 +41,12 @@ constexpr uint64_t DEFAULT_MAX_STREAMS_UNI = 3; constexpr uint64_t DEFAULT_IDLE_TIMEOUT = 10 * 1000; constexpr uint64_t DEFAULT_RETRYTOKEN_EXPIRATION = 10ULL; -typedef enum SelectPreferredAddressPolicy : int { +enum SelectPreferredAddressPolicy : int { // Ignore the server-provided preferred address QUIC_PREFERRED_ADDRESS_IGNORE, // Accept the server-provided preferred address QUIC_PREFERRED_ADDRESS_ACCEPT -} SelectPreferredAddressPolicy; +}; // Fun hash combine trick based on a variadic template that // I came across a while back but can't remember where. Will add an attribution @@ -70,11 +70,11 @@ inline void hash_combine(size_t* seed, const T& value, Args... rest) { // 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. -typedef enum QuicErrorFamily : int { +enum QuicErrorFamily : int { QUIC_ERROR_SESSION, QUIC_ERROR_CRYPTO, QUIC_ERROR_APPLICATION -} QuicErrorFamily; +}; struct QuicError { QuicErrorFamily family; @@ -340,11 +340,11 @@ class QuicCID { ngtcp2_cid_init(&cid_, cid, len); } - std::string ToStr() { + std::string ToStr() const { return std::string(cid_.data, cid_.data + cid_.datalen); } - std::string ToHex() { + std::string ToHex() const { MaybeStackBuffer dest; dest.AllocateSufficientStorage(cid_.datalen * 2); dest.SetLengthAndZeroTerminate(cid_.datalen * 2); @@ -359,8 +359,7 @@ class QuicCID { const ngtcp2_cid* operator*() const { return &cid_; } uint8_t* data() { return cid_.data; } - - size_t length() { return cid_.datalen; } + size_t length() const { return cid_.datalen; } private: ngtcp2_cid cid_; @@ -393,14 +392,10 @@ void IncrementStat( // reset the timer; Stop to halt the timer. class Timer { public: - inline explicit Timer( - Environment* env, - std::function fn, - void* data = nullptr) : - stopped_(false), + explicit Timer(Environment* env, std::function fn) + : stopped_(false), env_(env), - fn_(fn), - data_(data) { + fn_(fn) { uv_timer_init(env_->event_loop(), &timer_); timer_.data = this; env->AddCleanupHook(CleanupHook, this); @@ -412,7 +407,7 @@ class Timer { // Stops the timer with the side effect of the timer no longer being usable. // It will be cleaned up and the Timer object will be destroyed. - inline void Stop() { + void Stop() { if (stopped_) return; stopped_ = true; @@ -425,7 +420,7 @@ class Timer { // If the timer is not currently active, interval must be either 0 or greater. // If the timer is already active, interval is ignored. - inline void Update(uint64_t interval) { + void Update(uint64_t interval) { if (stopped_) return; uv_timer_start(&timer_, OnTimeout, interval, interval); @@ -435,18 +430,13 @@ class Timer { static void Free(Timer* timer); private: - inline void OnTimeout() { - fn_(data_); - } - static void OnTimeout(uv_timer_t* timer); static void CleanupHook(void* data); bool stopped_; Environment* env_; - std::function fn_; + std::function fn_; uv_timer_t timer_; - void* data_; }; using TimerPointer = DeleteFnPtr; diff --git a/test/cctest/test_quic_buffer.cc b/test/cctest/test_quic_buffer.cc index 2ad5891827..8f1bc9700e 100644 --- a/test/cctest/test_quic_buffer.cc +++ b/test/cctest/test_quic_buffer.cc @@ -39,15 +39,13 @@ TEST(QuicBuffer, Simple) { memset(&data, 0, node::arraysize(data)); uv_buf_t buf = uv_buf_init(data, node::arraysize(data)); - const char* test = "test data"; bool done = false; QuicBuffer buffer; - buffer.Push(&buf, 1, [&](int status, void* user_data) { - EXPECT_EQ(&test, user_data); + buffer.Push(&buf, 1, [&](int status) { EXPECT_EQ(0, status); done = true; - }, &test); + }); buffer.Consume(100); CHECK_EQ(0, buffer.Length()); @@ -66,15 +64,13 @@ TEST(QuicBuffer, ConsumeMore) { memset(&data, 0, node::arraysize(data)); uv_buf_t buf = uv_buf_init(data, node::arraysize(data)); - const char* test = "test data"; bool done = false; QuicBuffer buffer; - buffer.Push(&buf, 1, [&](int status, void* user_data) { - EXPECT_EQ(&test, user_data); + buffer.Push(&buf, 1, [&](int status) { EXPECT_EQ(0, status); done = true; - }, &test); + }); buffer.SeekHead(); buffer.Consume(150); // Consume more than what was buffered @@ -87,19 +83,18 @@ TEST(QuicBuffer, Multiple) { TestBuffer buf1(100); TestBuffer buf2(50, 1); - auto cb = [](int status, void* user_data) { - TestBuffer* test_buffer = static_cast(user_data); + auto cb = [](int status, TestBuffer* test_buffer) { test_buffer->Done(); }; QuicBuffer buffer; { uv_buf_t b = buf1.ToUVBuf(); - buffer.Push(&b, 1, cb, &buf1); + buffer.Push(&b, 1, [&](int status) { cb(status, &buf1); }); } { uv_buf_t b = buf2.ToUVBuf(); - buffer.Push(&b, 1, cb, &buf2); + buffer.Push(&b, 1, [&](int status) { cb(status, &buf2); }); } buffer.SeekHead(2); @@ -133,13 +128,11 @@ TEST(QuicBuffer, Multiple2) { QuicBuffer buffer; buffer.Push( bufs, node::arraysize(bufs), - [&](int status, void* user_data) { + [&](int status) { count++; CHECK_EQ(0, status); - char* data = static_cast(user_data); - CHECK_EQ(ptr, data); - delete data; - }, ptr); + delete[] ptr; + }); buffer.SeekHead(node::arraysize(bufs)); buffer.Consume(25); @@ -174,13 +167,11 @@ TEST(QuicBuffer, Cancel) { QuicBuffer buffer; buffer.Push( bufs, node::arraysize(bufs), - [&](int status, void* user_data) { + [&](int status) { count++; CHECK_EQ(UV_ECANCELED, status); - char* data = static_cast(user_data); - CHECK_EQ(ptr, data); - delete data; - }, ptr); + delete[] ptr; + }); buffer.SeekHead(); buffer.Consume(25); @@ -199,19 +190,18 @@ TEST(QuicBuffer, Multiple3) { TestBuffer buf2(50, 1); TestBuffer buf3(50, 2); - auto cb = [](int status, void* user_data) { - TestBuffer* test_buffer = static_cast(user_data); + auto cb = [](int status, TestBuffer* test_buffer) { test_buffer->Done(); }; QuicBuffer buffer; { uv_buf_t b = buf1.ToUVBuf(); - buffer.Push(&b, 1, cb, &buf1); + buffer.Push(&b, 1, [&](int status) { cb(status, &buf1); }); } { uv_buf_t b = buf2.ToUVBuf(); - buffer.Push(&b, 1, cb, &buf2); + buffer.Push(&b, 1, [&](int status) { cb(status, &buf2); }); } CHECK_EQ(150, buffer.Length()); CHECK_EQ(2, buffer.Size()); @@ -228,7 +218,7 @@ TEST(QuicBuffer, Multiple3) { { uv_buf_t b = buf2.ToUVBuf(); - buffer.Push(&b, 1, cb, &buf3); + buffer.Push(&b, 1, [&](int status) { cb(status, &buf3); }); } CHECK_EQ(75, buffer.Length()); @@ -296,7 +286,7 @@ TEST(QuicBuffer, Append) { TEST(QuicBuffer, MallocedBuffer) { uint8_t* data = node::Malloc(100); int count = 0; - auto cb = [&](int status, void* user_data) { + auto cb = [&](int status) { count++; };