1 #include "aliased_buffer.h"
2 #include "debug_utils.h"
3 #include "node.h"
4 #include "node_buffer.h"
5 #include "node_http2.h"
6 #include "node_http2_state.h"
7 #include "node_internals.h"
8 #include "node_perf.h"
9 #include "node_revert.h"
10 #include "util-inl.h"
11 
12 #include <algorithm>
13 
14 namespace node {
15 
16 using v8::ArrayBuffer;
17 using v8::ArrayBufferCreationMode;
18 using v8::Boolean;
19 using v8::Context;
20 using v8::Float64Array;
21 using v8::Function;
22 using v8::Integer;
23 using v8::NewStringType;
24 using v8::Number;
25 using v8::ObjectTemplate;
26 using v8::String;
27 using v8::Uint32;
28 using v8::Uint32Array;
29 using v8::Uint8Array;
30 using v8::Undefined;
31 
32 using node::performance::PerformanceEntry;
33 namespace http2 {
34 
35 namespace {
36 
37 const char zero_bytes_256[256] = {};
38 
GetStream(Http2Session * session,int32_t id,nghttp2_data_source * source)39 inline Http2Stream* GetStream(Http2Session* session,
40                               int32_t id,
41                               nghttp2_data_source* source) {
42   Http2Stream* stream = static_cast<Http2Stream*>(source->ptr);
43   if (stream == nullptr)
44     stream = session->FindStream(id);
45   CHECK_NOT_NULL(stream);
46   CHECK_EQ(id, stream->id());
47   return stream;
48 }
49 
50 }  // anonymous namespace
51 
52 // These configure the callbacks required by nghttp2 itself. There are
53 // two sets of callback functions, one that is used if a padding callback
54 // is set, and other that does not include the padding callback.
55 const Http2Session::Callbacks Http2Session::callback_struct_saved[2] = {
56     Callbacks(false),
57     Callbacks(true)};
58 
59 // The Http2Scope object is used to queue a write to the i/o stream. It is
60 // used whenever any action is take on the underlying nghttp2 API that may
61 // push data into nghttp2 outbound data queue.
62 //
63 // For example:
64 //
65 // Http2Scope h2scope(session);
66 // nghttp2_submit_ping(**session, ... );
67 //
68 // When the Http2Scope passes out of scope and is deconstructed, it will
69 // call Http2Session::MaybeScheduleWrite().
Http2Scope(Http2Stream * stream)70 Http2Scope::Http2Scope(Http2Stream* stream) : Http2Scope(stream->session()) {}
71 
Http2Scope(Http2Session * session)72 Http2Scope::Http2Scope(Http2Session* session) {
73   if (session == nullptr)
74     return;
75 
76   if (session->flags_ & (SESSION_STATE_HAS_SCOPE |
77                          SESSION_STATE_WRITE_SCHEDULED)) {
78     // There is another scope further below on the stack, or it is already
79     // known that a write is scheduled. In either case, there is nothing to do.
80     return;
81   }
82   session->flags_ |= SESSION_STATE_HAS_SCOPE;
83   session_ = session;
84 
85   // Always keep the session object alive for at least as long as
86   // this scope is active.
87   session_handle_ = session->object();
88   CHECK(!session_handle_.IsEmpty());
89 }
90 
~Http2Scope()91 Http2Scope::~Http2Scope() {
92   if (session_ == nullptr)
93     return;
94 
95   session_->flags_ &= ~SESSION_STATE_HAS_SCOPE;
96   session_->MaybeScheduleWrite();
97 }
98 
99 // The Http2Options object is used during the construction of Http2Session
100 // instances to configure an appropriate nghttp2_options struct. The class
101 // uses a single TypedArray instance that is shared with the JavaScript side
102 // to more efficiently pass values back and forth.
Http2Options(Environment * env,nghttp2_session_type type)103 Http2Options::Http2Options(Environment* env, nghttp2_session_type type) {
104   nghttp2_option_new(&options_);
105 
106   // Make sure closed connections aren't kept around, taking up memory.
107   // Note that this breaks the priority tree, which we don't use.
108   nghttp2_option_set_no_closed_streams(options_, 1);
109 
110   // We manually handle flow control within a session in order to
111   // implement backpressure -- that is, we only send WINDOW_UPDATE
112   // frames to the remote peer as data is actually consumed by user
113   // code. This ensures that the flow of data over the connection
114   // does not move too quickly and limits the amount of data we
115   // are required to buffer.
116   nghttp2_option_set_no_auto_window_update(options_, 1);
117 
118   // Enable built in support for receiving ALTSVC and ORIGIN frames (but
119   // only on client side sessions
120   if (type == NGHTTP2_SESSION_CLIENT) {
121     nghttp2_option_set_builtin_recv_extension_type(options_, NGHTTP2_ALTSVC);
122     nghttp2_option_set_builtin_recv_extension_type(options_, NGHTTP2_ORIGIN);
123   }
124 
125   AliasedBuffer<uint32_t, Uint32Array>& buffer =
126       env->http2_state()->options_buffer;
127   uint32_t flags = buffer[IDX_OPTIONS_FLAGS];
128 
129   if (flags & (1 << IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE)) {
130     nghttp2_option_set_max_deflate_dynamic_table_size(
131         options_,
132         buffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE]);
133   }
134 
135   if (flags & (1 << IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS)) {
136     nghttp2_option_set_max_reserved_remote_streams(
137         options_,
138         buffer[IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS]);
139   }
140 
141   if (flags & (1 << IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH)) {
142     nghttp2_option_set_max_send_header_block_length(
143         options_,
144         buffer[IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH]);
145   }
146 
147   // Recommended default
148   nghttp2_option_set_peer_max_concurrent_streams(options_, 100);
149   if (flags & (1 << IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS)) {
150     nghttp2_option_set_peer_max_concurrent_streams(
151         options_,
152         buffer[IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS]);
153   }
154 
155   if (IsReverted(SECURITY_REVERT_CVE_2019_9512))
156     nghttp2_option_set_max_outbound_ack(options_, 10000);
157 
158   // The padding strategy sets the mechanism by which we determine how much
159   // additional frame padding to apply to DATA and HEADERS frames. Currently
160   // this is set on a per-session basis, but eventually we may switch to
161   // a per-stream setting, giving users greater control
162   if (flags & (1 << IDX_OPTIONS_PADDING_STRATEGY)) {
163     padding_strategy_type strategy =
164         static_cast<padding_strategy_type>(
165             buffer.GetValue(IDX_OPTIONS_PADDING_STRATEGY));
166     SetPaddingStrategy(strategy);
167   }
168 
169   // The max header list pairs option controls the maximum number of
170   // header pairs the session may accept. This is a hard limit.. that is,
171   // if the remote peer sends more than this amount, the stream will be
172   // automatically closed with an RST_STREAM.
173   if (flags & (1 << IDX_OPTIONS_MAX_HEADER_LIST_PAIRS)) {
174     SetMaxHeaderPairs(buffer[IDX_OPTIONS_MAX_HEADER_LIST_PAIRS]);
175   }
176 
177   // The HTTP2 specification places no limits on the number of HTTP2
178   // PING frames that can be sent. In order to prevent PINGS from being
179   // abused as an attack vector, however, we place a strict upper limit
180   // on the number of unacknowledged PINGS that can be sent at any given
181   // time.
182   if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_PINGS)) {
183     SetMaxOutstandingPings(buffer[IDX_OPTIONS_MAX_OUTSTANDING_PINGS]);
184   }
185 
186   // The HTTP2 specification places no limits on the number of HTTP2
187   // SETTINGS frames that can be sent. In order to prevent PINGS from being
188   // abused as an attack vector, however, we place a strict upper limit
189   // on the number of unacknowledged SETTINGS that can be sent at any given
190   // time.
191   if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS)) {
192     SetMaxOutstandingSettings(buffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS]);
193   }
194 
195   // The HTTP2 specification places no limits on the amount of memory
196   // that a session can consume. In order to prevent abuse, we place a
197   // cap on the amount of memory a session can consume at any given time.
198   // this is a credit based system. Existing streams may cause the limit
199   // to be temporarily exceeded but once over the limit, new streams cannot
200   // created.
201   // Important: The maxSessionMemory option in javascript is expressed in
202   //            terms of MB increments (i.e. the value 1 == 1 MB)
203   if (flags & (1 << IDX_OPTIONS_MAX_SESSION_MEMORY)) {
204     SetMaxSessionMemory(buffer[IDX_OPTIONS_MAX_SESSION_MEMORY] * 1e6);
205   }
206 
207   if (flags & (1 << IDX_OPTIONS_MAX_SETTINGS)) {
208     nghttp2_option_set_max_settings(
209         options_,
210         static_cast<size_t>(buffer[IDX_OPTIONS_MAX_SETTINGS]));
211   }
212 }
213 
Init()214 void Http2Session::Http2Settings::Init() {
215   AliasedBuffer<uint32_t, Uint32Array>& buffer =
216       env()->http2_state()->settings_buffer;
217   uint32_t flags = buffer[IDX_SETTINGS_COUNT];
218 
219   size_t n = 0;
220 
221 #define GRABSETTING(N, trace)                                                 \
222   if (flags & (1 << IDX_SETTINGS_##N)) {                                      \
223     uint32_t val = buffer[IDX_SETTINGS_##N];                                  \
224     if (session_ != nullptr)                                                  \
225       Debug(session_, "setting " trace ": %d\n", val);                        \
226     entries_[n++] =                                                           \
227         nghttp2_settings_entry {NGHTTP2_SETTINGS_##N, val};                   \
228   }
229 
230   GRABSETTING(HEADER_TABLE_SIZE, "header table size");
231   GRABSETTING(MAX_CONCURRENT_STREAMS, "max concurrent streams");
232   GRABSETTING(MAX_FRAME_SIZE, "max frame size");
233   GRABSETTING(INITIAL_WINDOW_SIZE, "initial window size");
234   GRABSETTING(MAX_HEADER_LIST_SIZE, "max header list size");
235   GRABSETTING(ENABLE_PUSH, "enable push");
236   GRABSETTING(ENABLE_CONNECT_PROTOCOL, "enable connect protocol");
237 
238 #undef GRABSETTING
239 
240   count_ = n;
241 }
242 
243 // The Http2Settings class is used to configure a SETTINGS frame that is
244 // to be sent to the connected peer. The settings are set using a TypedArray
245 // that is shared with the JavaScript side.
Http2Settings(Environment * env,Http2Session * session,Local<Object> obj,uint64_t start_time)246 Http2Session::Http2Settings::Http2Settings(Environment* env,
247                                            Http2Session* session,
248                                            Local<Object> obj,
249                                            uint64_t start_time)
250     : AsyncWrap(env, obj, PROVIDER_HTTP2SETTINGS),
251       session_(session),
252       startTime_(start_time) {
253   Init();
254 }
255 
256 // Generates a Buffer that contains the serialized payload of a SETTINGS
257 // frame. This can be used, for instance, to create the Base64-encoded
258 // content of an Http2-Settings header field.
Pack()259 Local<Value> Http2Session::Http2Settings::Pack() {
260   const size_t len = count_ * 6;
261   Local<Value> buf = Buffer::New(env(), len).ToLocalChecked();
262   ssize_t ret =
263       nghttp2_pack_settings_payload(
264         reinterpret_cast<uint8_t*>(Buffer::Data(buf)), len,
265         &entries_[0], count_);
266   if (ret >= 0)
267     return buf;
268   else
269     return Undefined(env()->isolate());
270 }
271 
272 // Updates the shared TypedArray with the current remote or local settings for
273 // the session.
Update(Environment * env,Http2Session * session,get_setting fn)274 void Http2Session::Http2Settings::Update(Environment* env,
275                                          Http2Session* session,
276                                          get_setting fn) {
277   AliasedBuffer<uint32_t, Uint32Array>& buffer =
278       env->http2_state()->settings_buffer;
279   buffer[IDX_SETTINGS_HEADER_TABLE_SIZE] =
280       fn(**session, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE);
281   buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS] =
282       fn(**session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
283   buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] =
284       fn(**session, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
285   buffer[IDX_SETTINGS_MAX_FRAME_SIZE] =
286       fn(**session, NGHTTP2_SETTINGS_MAX_FRAME_SIZE);
287   buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] =
288       fn(**session, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE);
289   buffer[IDX_SETTINGS_ENABLE_PUSH] =
290       fn(**session, NGHTTP2_SETTINGS_ENABLE_PUSH);
291   buffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL] =
292       fn(**session, NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL);
293 }
294 
295 // Initializes the shared TypedArray with the default settings values.
RefreshDefaults(Environment * env)296 void Http2Session::Http2Settings::RefreshDefaults(Environment* env) {
297   AliasedBuffer<uint32_t, Uint32Array>& buffer =
298       env->http2_state()->settings_buffer;
299 
300   buffer[IDX_SETTINGS_HEADER_TABLE_SIZE] =
301       DEFAULT_SETTINGS_HEADER_TABLE_SIZE;
302   buffer[IDX_SETTINGS_ENABLE_PUSH] =
303       DEFAULT_SETTINGS_ENABLE_PUSH;
304   buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] =
305       DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE;
306   buffer[IDX_SETTINGS_MAX_FRAME_SIZE] =
307       DEFAULT_SETTINGS_MAX_FRAME_SIZE;
308   buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] =
309       DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE;
310   buffer[IDX_SETTINGS_COUNT] =
311     (1 << IDX_SETTINGS_HEADER_TABLE_SIZE) |
312     (1 << IDX_SETTINGS_ENABLE_PUSH) |
313     (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE) |
314     (1 << IDX_SETTINGS_MAX_FRAME_SIZE) |
315     (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE);
316 }
317 
318 
Send()319 void Http2Session::Http2Settings::Send() {
320   Http2Scope h2scope(session_);
321   CHECK_EQ(nghttp2_submit_settings(**session_, NGHTTP2_FLAG_NONE,
322                                    &entries_[0], count_), 0);
323 }
324 
Done(bool ack)325 void Http2Session::Http2Settings::Done(bool ack) {
326   uint64_t end = uv_hrtime();
327   double duration = (end - startTime_) / 1e6;
328 
329   Local<Value> argv[2] = {
330     Boolean::New(env()->isolate(), ack),
331     Number::New(env()->isolate(), duration)
332   };
333   MakeCallback(env()->ondone_string(), arraysize(argv), argv);
334   delete this;
335 }
336 
337 // The Http2Priority class initializes an appropriate nghttp2_priority_spec
338 // struct used when either creating a stream or updating its priority
339 // settings.
Http2Priority(Environment * env,Local<Value> parent,Local<Value> weight,Local<Value> exclusive)340 Http2Priority::Http2Priority(Environment* env,
341                              Local<Value> parent,
342                              Local<Value> weight,
343                              Local<Value> exclusive) {
344   Local<Context> context = env->context();
345   int32_t parent_ = parent->Int32Value(context).ToChecked();
346   int32_t weight_ = weight->Int32Value(context).ToChecked();
347   bool exclusive_ = exclusive->BooleanValue(context).ToChecked();
348   Debug(env, DebugCategory::HTTP2STREAM,
349         "Http2Priority: parent: %d, weight: %d, exclusive: %d\n",
350         parent_, weight_, exclusive_);
351   nghttp2_priority_spec_init(&spec, parent_, weight_, exclusive_ ? 1 : 0);
352 }
353 
354 
TypeName() const355 const char* Http2Session::TypeName() const {
356   switch (session_type_) {
357     case NGHTTP2_SESSION_SERVER: return "server";
358     case NGHTTP2_SESSION_CLIENT: return "client";
359     default:
360       // This should never happen
361       ABORT();
362   }
363 }
364 
365 // The Headers class initializes a proper array of nghttp2_nv structs
366 // containing the header name value pairs.
Headers(Isolate * isolate,Local<Context> context,Local<Array> headers)367 Headers::Headers(Isolate* isolate,
368                  Local<Context> context,
369                  Local<Array> headers) {
370   Local<Value> header_string = headers->Get(context, 0).ToLocalChecked();
371   Local<Value> header_count = headers->Get(context, 1).ToLocalChecked();
372   count_ = header_count.As<Uint32>()->Value();
373   int header_string_len = header_string.As<String>()->Length();
374 
375   if (count_ == 0) {
376     CHECK_EQ(header_string_len, 0);
377     return;
378   }
379 
380   // Allocate a single buffer with count_ nghttp2_nv structs, followed
381   // by the raw header data as passed from JS. This looks like:
382   // | possible padding | nghttp2_nv | nghttp2_nv | ... | header contents |
383   buf_.AllocateSufficientStorage((alignof(nghttp2_nv) - 1) +
384                                  count_ * sizeof(nghttp2_nv) +
385                                  header_string_len);
386   // Make sure the start address is aligned appropriately for an nghttp2_nv*.
387   char* start = reinterpret_cast<char*>(
388       ROUND_UP(reinterpret_cast<uintptr_t>(*buf_), alignof(nghttp2_nv)));
389   char* header_contents = start + (count_ * sizeof(nghttp2_nv));
390   nghttp2_nv* const nva = reinterpret_cast<nghttp2_nv*>(start);
391 
392   CHECK_LE(header_contents + header_string_len, *buf_ + buf_.length());
393   CHECK_EQ(header_string.As<String>()->WriteOneByte(
394                isolate,
395                reinterpret_cast<uint8_t*>(header_contents),
396                0,
397                header_string_len,
398                String::NO_NULL_TERMINATION),
399            header_string_len);
400 
401   size_t n = 0;
402   char* p;
403   for (p = header_contents; p < header_contents + header_string_len; n++) {
404     if (n >= count_) {
405       // This can happen if a passed header contained a null byte. In that
406       // case, just provide nghttp2 with an invalid header to make it reject
407       // the headers list.
408       static uint8_t zero = '\0';
409       nva[0].name = nva[0].value = &zero;
410       nva[0].namelen = nva[0].valuelen = 1;
411       count_ = 1;
412       return;
413     }
414 
415     nva[n].flags = NGHTTP2_NV_FLAG_NONE;
416     nva[n].name = reinterpret_cast<uint8_t*>(p);
417     nva[n].namelen = strlen(p);
418     p += nva[n].namelen + 1;
419     nva[n].value = reinterpret_cast<uint8_t*>(p);
420     nva[n].valuelen = strlen(p);
421     p += nva[n].valuelen + 1;
422   }
423 }
424 
Origins(Isolate * isolate,Local<Context> context,Local<String> origin_string,size_t origin_count)425 Origins::Origins(Isolate* isolate,
426                  Local<Context> context,
427                  Local<String> origin_string,
428                  size_t origin_count) : count_(origin_count) {
429   int origin_string_len = origin_string->Length();
430   if (count_ == 0) {
431     CHECK_EQ(origin_string_len, 0);
432     return;
433   }
434 
435   // Allocate a single buffer with count_ nghttp2_nv structs, followed
436   // by the raw header data as passed from JS. This looks like:
437   // | possible padding | nghttp2_nv | nghttp2_nv | ... | header contents |
438   buf_.AllocateSufficientStorage((alignof(nghttp2_origin_entry) - 1) +
439                                  count_ * sizeof(nghttp2_origin_entry) +
440                                  origin_string_len);
441 
442   // Make sure the start address is aligned appropriately for an nghttp2_nv*.
443   char* start = reinterpret_cast<char*>(
444       ROUND_UP(reinterpret_cast<uintptr_t>(*buf_),
445                alignof(nghttp2_origin_entry)));
446   char* origin_contents = start + (count_ * sizeof(nghttp2_origin_entry));
447   nghttp2_origin_entry* const nva =
448       reinterpret_cast<nghttp2_origin_entry*>(start);
449 
450   CHECK_LE(origin_contents + origin_string_len, *buf_ + buf_.length());
451   CHECK_EQ(origin_string->WriteOneByte(
452                isolate,
453                reinterpret_cast<uint8_t*>(origin_contents),
454                0,
455                origin_string_len,
456                String::NO_NULL_TERMINATION),
457            origin_string_len);
458 
459   size_t n = 0;
460   char* p;
461   for (p = origin_contents; p < origin_contents + origin_string_len; n++) {
462     if (n >= count_) {
463       static uint8_t zero = '\0';
464       nva[0].origin = &zero;
465       nva[0].origin_len = 1;
466       count_ = 1;
467       return;
468     }
469 
470     nva[n].origin = reinterpret_cast<uint8_t*>(p);
471     nva[n].origin_len = strlen(p);
472     p += nva[n].origin_len + 1;
473   }
474 }
475 
476 // Sets the various callback functions that nghttp2 will use to notify us
477 // about significant events while processing http2 stuff.
Callbacks(bool kHasGetPaddingCallback)478 Http2Session::Callbacks::Callbacks(bool kHasGetPaddingCallback) {
479   CHECK_EQ(nghttp2_session_callbacks_new(&callbacks), 0);
480 
481   nghttp2_session_callbacks_set_on_begin_headers_callback(
482     callbacks, OnBeginHeadersCallback);
483   nghttp2_session_callbacks_set_on_header_callback2(
484     callbacks, OnHeaderCallback);
485   nghttp2_session_callbacks_set_on_frame_recv_callback(
486     callbacks, OnFrameReceive);
487   nghttp2_session_callbacks_set_on_stream_close_callback(
488     callbacks, OnStreamClose);
489   nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
490     callbacks, OnDataChunkReceived);
491   nghttp2_session_callbacks_set_on_frame_not_send_callback(
492     callbacks, OnFrameNotSent);
493   nghttp2_session_callbacks_set_on_invalid_header_callback2(
494     callbacks, OnInvalidHeader);
495   nghttp2_session_callbacks_set_error_callback(
496     callbacks, OnNghttpError);
497   nghttp2_session_callbacks_set_send_data_callback(
498     callbacks, OnSendData);
499   nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
500     callbacks, OnInvalidFrame);
501   nghttp2_session_callbacks_set_on_frame_send_callback(
502     callbacks, OnFrameSent);
503 
504   if (kHasGetPaddingCallback) {
505     nghttp2_session_callbacks_set_select_padding_callback(
506       callbacks, OnSelectPadding);
507   }
508 }
509 
510 
~Callbacks()511 Http2Session::Callbacks::~Callbacks() {
512   nghttp2_session_callbacks_del(callbacks);
513 }
514 
515 // Track memory allocated by nghttp2 using a custom allocator.
516 class Http2Session::MemoryAllocatorInfo {
517  public:
MemoryAllocatorInfo(Http2Session * session)518   explicit MemoryAllocatorInfo(Http2Session* session)
519       : info({ session, H2Malloc, H2Free, H2Calloc, H2Realloc }) {}
520 
H2Malloc(size_t size,void * user_data)521   static void* H2Malloc(size_t size, void* user_data) {
522     return H2Realloc(nullptr, size, user_data);
523   }
524 
H2Calloc(size_t nmemb,size_t size,void * user_data)525   static void* H2Calloc(size_t nmemb, size_t size, void* user_data) {
526     size_t real_size = MultiplyWithOverflowCheck(nmemb, size);
527     void* mem = H2Malloc(real_size, user_data);
528     if (mem != nullptr)
529       memset(mem, 0, real_size);
530     return mem;
531   }
532 
H2Free(void * ptr,void * user_data)533   static void H2Free(void* ptr, void* user_data) {
534     if (ptr == nullptr) return;  // free(null); happens quite often.
535     void* result = H2Realloc(ptr, 0, user_data);
536     CHECK_EQ(result, nullptr);
537   }
538 
H2Realloc(void * ptr,size_t size,void * user_data)539   static void* H2Realloc(void* ptr, size_t size, void* user_data) {
540     Http2Session* session = static_cast<Http2Session*>(user_data);
541     size_t previous_size = 0;
542     char* original_ptr = nullptr;
543 
544     // We prepend each allocated buffer with a size_t containing the full
545     // size of the allocation.
546     if (size > 0) size += sizeof(size_t);
547 
548     if (ptr != nullptr) {
549       // We are free()ing or re-allocating.
550       original_ptr = static_cast<char*>(ptr) - sizeof(size_t);
551       previous_size = *reinterpret_cast<size_t*>(original_ptr);
552       // This means we called StopTracking() on this pointer before.
553       if (previous_size == 0) {
554         // Fall back to the standard Realloc() function.
555         char* ret = UncheckedRealloc(original_ptr, size);
556         if (ret != nullptr)
557           ret += sizeof(size_t);
558         return ret;
559       }
560     }
561     CHECK_GE(session->current_nghttp2_memory_, previous_size);
562 
563     // TODO(addaleax): Add the following, and handle NGHTTP2_ERR_NOMEM properly
564     // everywhere:
565     //
566     // if (size > previous_size &&
567     //     !session->IsAvailableSessionMemory(size - previous_size)) {
568     //  return nullptr;
569     //}
570 
571     char* mem = UncheckedRealloc(original_ptr, size);
572 
573     if (mem != nullptr) {
574       // Adjust the memory info counter.
575       session->current_nghttp2_memory_ += size - previous_size;
576       *reinterpret_cast<size_t*>(mem) = size;
577       mem += sizeof(size_t);
578     } else if (size == 0) {
579       session->current_nghttp2_memory_ -= previous_size;
580     }
581 
582     return mem;
583   }
584 
StopTracking(Http2Session * session,void * ptr)585   static void StopTracking(Http2Session* session, void* ptr) {
586     size_t* original_ptr = reinterpret_cast<size_t*>(
587         static_cast<char*>(ptr) - sizeof(size_t));
588     session->current_nghttp2_memory_ -= *original_ptr;
589     *original_ptr = 0;
590   }
591 
operator *()592   inline nghttp2_mem* operator*() { return &info; }
593 
594   nghttp2_mem info;
595 };
596 
StopTrackingRcbuf(nghttp2_rcbuf * buf)597 void Http2Session::StopTrackingRcbuf(nghttp2_rcbuf* buf) {
598   MemoryAllocatorInfo::StopTracking(this, buf);
599 }
600 
Http2Session(Environment * env,Local<Object> wrap,nghttp2_session_type type)601 Http2Session::Http2Session(Environment* env,
602                            Local<Object> wrap,
603                            nghttp2_session_type type)
604     : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_HTTP2SESSION),
605       session_type_(type) {
606   MakeWeak();
607   statistics_.start_time = uv_hrtime();
608 
609   // Capture the configuration options for this session
610   Http2Options opts(env, type);
611 
612   max_session_memory_ = opts.GetMaxSessionMemory();
613 
614   uint32_t maxHeaderPairs = opts.GetMaxHeaderPairs();
615   max_header_pairs_ =
616       type == NGHTTP2_SESSION_SERVER
617           ? std::max(maxHeaderPairs, 4U)     // minimum # of request headers
618           : std::max(maxHeaderPairs, 1U);    // minimum # of response headers
619 
620   max_outstanding_pings_ = opts.GetMaxOutstandingPings();
621   max_outstanding_settings_ = opts.GetMaxOutstandingSettings();
622 
623   padding_strategy_ = opts.GetPaddingStrategy();
624 
625   bool hasGetPaddingCallback =
626       padding_strategy_ != PADDING_STRATEGY_NONE;
627 
628   nghttp2_session_callbacks* callbacks
629       = callback_struct_saved[hasGetPaddingCallback ? 1 : 0].callbacks;
630 
631   auto fn = type == NGHTTP2_SESSION_SERVER ?
632       nghttp2_session_server_new3 :
633       nghttp2_session_client_new3;
634 
635   MemoryAllocatorInfo allocator_info(this);
636 
637   // This should fail only if the system is out of memory, which
638   // is going to cause lots of other problems anyway, or if any
639   // of the options are out of acceptable range, which we should
640   // be catching before it gets this far. Either way, crash if this
641   // fails.
642   CHECK_EQ(fn(&session_, callbacks, this, *opts, *allocator_info), 0);
643 
644   outgoing_storage_.reserve(1024);
645   outgoing_buffers_.reserve(32);
646 
647   {
648     // Make the js_fields_ property accessible to JS land.
649     Local<ArrayBuffer> ab =
650         ArrayBuffer::New(env->isolate(), js_fields_, kSessionUint8FieldCount);
651     Local<Uint8Array> uint8_arr =
652         Uint8Array::New(ab, 0, kSessionUint8FieldCount);
653     USE(wrap->Set(env->context(), env->fields_string(), uint8_arr));
654   }
655 }
656 
~Http2Session()657 Http2Session::~Http2Session() {
658   CHECK_EQ(flags_ & SESSION_STATE_HAS_SCOPE, 0);
659   Debug(this, "freeing nghttp2 session");
660   for (const auto& stream : streams_)
661     stream.second->session_ = nullptr;
662   nghttp2_session_del(session_);
663   CHECK_EQ(current_nghttp2_memory_, 0);
664   free(stream_buf_allocation_.base);
665 }
666 
diagnostic_name() const667 std::string Http2Session::diagnostic_name() const {
668   return std::string("Http2Session ") + TypeName() + " (" +
669       std::to_string(static_cast<int64_t>(get_async_id())) + ")";
670 }
671 
HasHttp2Observer(Environment * env)672 inline bool HasHttp2Observer(Environment* env) {
673   AliasedBuffer<uint32_t, Uint32Array>& observers =
674       env->performance_state()->observers;
675   return observers[performance::NODE_PERFORMANCE_ENTRY_TYPE_HTTP2] != 0;
676 }
677 
EmitStatistics()678 void Http2Stream::EmitStatistics() {
679   if (!HasHttp2Observer(env()))
680     return;
681   Http2StreamPerformanceEntry* entry =
682     new Http2StreamPerformanceEntry(env(), id_, statistics_);
683   env()->SetImmediate([](Environment* env, void* data) {
684     // This takes ownership, the entry is destroyed at the end of this scope.
685     std::unique_ptr<Http2StreamPerformanceEntry> entry {
686         static_cast<Http2StreamPerformanceEntry*>(data) };
687     if (!HasHttp2Observer(env))
688       return;
689     HandleScope handle_scope(env->isolate());
690     AliasedBuffer<double, Float64Array>& buffer =
691         env->http2_state()->stream_stats_buffer;
692     buffer[IDX_STREAM_STATS_ID] = entry->id();
693     if (entry->first_byte() != 0) {
694       buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTE] =
695           (entry->first_byte() - entry->startTimeNano()) / 1e6;
696     } else {
697       buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTE] = 0;
698     }
699     if (entry->first_header() != 0) {
700       buffer[IDX_STREAM_STATS_TIMETOFIRSTHEADER] =
701           (entry->first_header() - entry->startTimeNano()) / 1e6;
702     } else {
703       buffer[IDX_STREAM_STATS_TIMETOFIRSTHEADER] = 0;
704     }
705     if (entry->first_byte_sent() != 0) {
706       buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTESENT] =
707           (entry->first_byte_sent() - entry->startTimeNano()) / 1e6;
708     } else {
709       buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTESENT] = 0;
710     }
711     buffer[IDX_STREAM_STATS_SENTBYTES] = entry->sent_bytes();
712     buffer[IDX_STREAM_STATS_RECEIVEDBYTES] = entry->received_bytes();
713     entry->Notify(entry->ToObject());
714   }, static_cast<void*>(entry));
715 }
716 
EmitStatistics()717 void Http2Session::EmitStatistics() {
718   if (!HasHttp2Observer(env()))
719     return;
720   Http2SessionPerformanceEntry* entry =
721     new Http2SessionPerformanceEntry(env(), statistics_, session_type_);
722   env()->SetImmediate([](Environment* env, void* data) {
723     // This takes ownership, the entr is destroyed at the end of this scope.
724     std::unique_ptr<Http2SessionPerformanceEntry> entry {
725         static_cast<Http2SessionPerformanceEntry*>(data) };
726     if (!HasHttp2Observer(env))
727       return;
728     HandleScope handle_scope(env->isolate());
729     AliasedBuffer<double, Float64Array>& buffer =
730         env->http2_state()->session_stats_buffer;
731     buffer[IDX_SESSION_STATS_TYPE] = entry->type();
732     buffer[IDX_SESSION_STATS_PINGRTT] = entry->ping_rtt() / 1e6;
733     buffer[IDX_SESSION_STATS_FRAMESRECEIVED] = entry->frame_count();
734     buffer[IDX_SESSION_STATS_FRAMESSENT] = entry->frame_sent();
735     buffer[IDX_SESSION_STATS_STREAMCOUNT] = entry->stream_count();
736     buffer[IDX_SESSION_STATS_STREAMAVERAGEDURATION] =
737         entry->stream_average_duration();
738     buffer[IDX_SESSION_STATS_DATA_SENT] = entry->data_sent();
739     buffer[IDX_SESSION_STATS_DATA_RECEIVED] = entry->data_received();
740     buffer[IDX_SESSION_STATS_MAX_CONCURRENT_STREAMS] =
741         entry->max_concurrent_streams();
742     entry->Notify(entry->ToObject());
743   }, static_cast<void*>(entry));
744 }
745 
746 // Closes the session and frees the associated resources
Close(uint32_t code,bool socket_closed)747 void Http2Session::Close(uint32_t code, bool socket_closed) {
748   Debug(this, "closing session");
749 
750   if (flags_ & SESSION_STATE_CLOSING)
751     return;
752   flags_ |= SESSION_STATE_CLOSING;
753 
754   // Stop reading on the i/o stream
755   if (stream_ != nullptr) {
756     flags_ |= SESSION_STATE_READING_STOPPED;
757     stream_->ReadStop();
758   }
759 
760   // If the socket is not closed, then attempt to send a closing GOAWAY
761   // frame. There is no guarantee that this GOAWAY will be received by
762   // the peer but the HTTP/2 spec recommends sending it anyway. We'll
763   // make a best effort.
764   if (!socket_closed) {
765     Debug(this, "terminating session with code %d", code);
766     CHECK_EQ(nghttp2_session_terminate_session(session_, code), 0);
767     SendPendingData();
768   } else if (stream_ != nullptr) {
769     stream_->RemoveStreamListener(this);
770   }
771 
772   flags_ |= SESSION_STATE_CLOSED;
773 
774   // If there are outstanding pings, those will need to be canceled, do
775   // so on the next iteration of the event loop to avoid calling out into
776   // javascript since this may be called during garbage collection.
777   while (!outstanding_pings_.empty()) {
778     Http2Session::Http2Ping* ping = PopPing();
779     env()->SetImmediate([](Environment* env, void* data) {
780       static_cast<Http2Session::Http2Ping*>(data)->Done(false);
781     }, static_cast<void*>(ping));
782   }
783 
784   statistics_.end_time = uv_hrtime();
785   EmitStatistics();
786 }
787 
788 // Locates an existing known stream by ID. nghttp2 has a similar method
789 // but this is faster and does not fail if the stream is not found.
FindStream(int32_t id)790 inline Http2Stream* Http2Session::FindStream(int32_t id) {
791   auto s = streams_.find(id);
792   return s != streams_.end() ? s->second : nullptr;
793 }
794 
CanAddStream()795 inline bool Http2Session::CanAddStream() {
796   uint32_t maxConcurrentStreams =
797       nghttp2_session_get_local_settings(
798           session_, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
799   size_t maxSize =
800       std::min(streams_.max_size(), static_cast<size_t>(maxConcurrentStreams));
801   // We can add a new stream so long as we are less than the current
802   // maximum on concurrent streams and there's enough available memory
803   return streams_.size() < maxSize &&
804          IsAvailableSessionMemory(sizeof(Http2Stream));
805 }
806 
AddStream(Http2Stream * stream)807 inline void Http2Session::AddStream(Http2Stream* stream) {
808   CHECK_GE(++statistics_.stream_count, 0);
809   streams_[stream->id()] = stream;
810   size_t size = streams_.size();
811   if (size > statistics_.max_concurrent_streams)
812     statistics_.max_concurrent_streams = size;
813   IncrementCurrentSessionMemory(sizeof(*stream));
814 }
815 
816 
RemoveStream(Http2Stream * stream)817 inline void Http2Session::RemoveStream(Http2Stream* stream) {
818   if (streams_.empty() || stream == nullptr)
819     return;  // Nothing to remove, item was never added?
820   streams_.erase(stream->id());
821   DecrementCurrentSessionMemory(sizeof(*stream));
822 }
823 
824 // Used as one of the Padding Strategy functions. Will attempt to ensure
825 // that the total frame size, including header bytes, are 8-byte aligned.
826 // If maxPayloadLen is smaller than the number of bytes necessary to align,
827 // will return maxPayloadLen instead.
OnDWordAlignedPadding(size_t frameLen,size_t maxPayloadLen)828 ssize_t Http2Session::OnDWordAlignedPadding(size_t frameLen,
829                                             size_t maxPayloadLen) {
830   size_t r = (frameLen + 9) % 8;
831   if (r == 0) return frameLen;  // If already a multiple of 8, return.
832 
833   size_t pad = frameLen + (8 - r);
834 
835   // If maxPayloadLen happens to be less than the calculated pad length,
836   // use the max instead, even tho this means the frame will not be
837   // aligned.
838   pad = std::min(maxPayloadLen, pad);
839   Debug(this, "using frame size padding: %d", pad);
840   return pad;
841 }
842 
843 // Used as one of the Padding Strategy functions. Uses the maximum amount
844 // of padding allowed for the current frame.
OnMaxFrameSizePadding(size_t frameLen,size_t maxPayloadLen)845 ssize_t Http2Session::OnMaxFrameSizePadding(size_t frameLen,
846                                             size_t maxPayloadLen) {
847   Debug(this, "using max frame size padding: %d", maxPayloadLen);
848   return maxPayloadLen;
849 }
850 
851 // Used as one of the Padding Strategy functions. Uses a callback to JS land
852 // to determine the amount of padding for the current frame. This option is
853 // rather more expensive because of the JS boundary cross. It generally should
854 // not be the preferred option.
OnCallbackPadding(size_t frameLen,size_t maxPayloadLen)855 ssize_t Http2Session::OnCallbackPadding(size_t frameLen,
856                                         size_t maxPayloadLen) {
857   if (frameLen == 0) return 0;
858   Debug(this, "using callback to determine padding");
859   Isolate* isolate = env()->isolate();
860   HandleScope handle_scope(isolate);
861   Local<Context> context = env()->context();
862   Context::Scope context_scope(context);
863 
864   AliasedBuffer<uint32_t, Uint32Array>& buffer =
865       env()->http2_state()->padding_buffer;
866   buffer[PADDING_BUF_FRAME_LENGTH] = frameLen;
867   buffer[PADDING_BUF_MAX_PAYLOAD_LENGTH] = maxPayloadLen;
868   buffer[PADDING_BUF_RETURN_VALUE] = frameLen;
869   MakeCallback(env()->ongetpadding_string(), 0, nullptr);
870   uint32_t retval = buffer[PADDING_BUF_RETURN_VALUE];
871   retval = std::min(retval, static_cast<uint32_t>(maxPayloadLen));
872   retval = std::max(retval, static_cast<uint32_t>(frameLen));
873   Debug(this, "using padding size %d", retval);
874   return retval;
875 }
876 
877 
878 // Write data received from the i/o stream to the underlying nghttp2_session.
879 // On each call to nghttp2_session_mem_recv, nghttp2 will begin calling the
880 // various callback functions. Each of these will typically result in a call
881 // out to JavaScript so this particular function is rather hot and can be
882 // quite expensive. This is a potential performance optimization target later.
ConsumeHTTP2Data()883 ssize_t Http2Session::ConsumeHTTP2Data() {
884   CHECK_NOT_NULL(stream_buf_.base);
885   CHECK_LE(stream_buf_offset_, stream_buf_.len);
886   size_t read_len = stream_buf_.len - stream_buf_offset_;
887 
888   // multiple side effects.
889   Debug(this, "receiving %d bytes [wants data? %d]",
890         read_len,
891         nghttp2_session_want_read(session_));
892   flags_ &= ~SESSION_STATE_NGHTTP2_RECV_PAUSED;
893   ssize_t ret =
894     nghttp2_session_mem_recv(session_,
895                              reinterpret_cast<uint8_t*>(stream_buf_.base) +
896                                  stream_buf_offset_,
897                              read_len);
898   CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
899 
900   if (flags_ & SESSION_STATE_NGHTTP2_RECV_PAUSED) {
901     CHECK_NE(flags_ & SESSION_STATE_READING_STOPPED, 0);
902 
903     CHECK_GT(ret, 0);
904     CHECK_LE(static_cast<size_t>(ret), read_len);
905 
906     // Mark the remainder of the data as available for later consumption.
907     // Even if all bytes were received, a paused stream may delay the
908     // nghttp2_on_frame_recv_callback which may have an END_STREAM flag.
909     stream_buf_offset_ += ret;
910     return ret;
911   }
912 
913   // We are done processing the current input chunk.
914   DecrementCurrentSessionMemory(stream_buf_.len);
915   stream_buf_offset_ = 0;
916   stream_buf_ab_.Reset();
917   free(stream_buf_allocation_.base);
918   stream_buf_allocation_ = uv_buf_init(nullptr, 0);
919   stream_buf_ = uv_buf_init(nullptr, 0);
920 
921   if (ret < 0)
922     return ret;
923 
924   // Send any data that was queued up while processing the received data.
925   if (!IsDestroyed()) {
926     SendPendingData();
927   }
928   return ret;
929 }
930 
931 
GetFrameID(const nghttp2_frame * frame)932 inline int32_t GetFrameID(const nghttp2_frame* frame) {
933   // If this is a push promise, we want to grab the id of the promised stream
934   return (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
935       frame->push_promise.promised_stream_id :
936       frame->hd.stream_id;
937 }
938 
939 
940 // Called by nghttp2 at the start of receiving a HEADERS frame. We use this
941 // callback to determine if a new stream is being created or if we are simply
942 // adding a new block of headers to an existing stream. The header pairs
943 // themselves are set in the OnHeaderCallback
OnBeginHeadersCallback(nghttp2_session * handle,const nghttp2_frame * frame,void * user_data)944 int Http2Session::OnBeginHeadersCallback(nghttp2_session* handle,
945                                          const nghttp2_frame* frame,
946                                          void* user_data) {
947   Http2Session* session = static_cast<Http2Session*>(user_data);
948   int32_t id = GetFrameID(frame);
949   Debug(session, "beginning headers for stream %d", id);
950 
951   Http2Stream* stream = session->FindStream(id);
952   // The common case is that we're creating a new stream. The less likely
953   // case is that we're receiving a set of trailers
954   if (LIKELY(stream == nullptr)) {
955     if (UNLIKELY(!session->CanAddStream() ||
956                  Http2Stream::New(session, id, frame->headers.cat) ==
957                      nullptr)) {
958       if (session->rejected_stream_count_++ > 100 &&
959           !IsReverted(SECURITY_REVERT_CVE_2019_9514)) {
960         return NGHTTP2_ERR_CALLBACK_FAILURE;
961       }
962       // Too many concurrent streams being opened
963       nghttp2_submit_rst_stream(**session, NGHTTP2_FLAG_NONE, id,
964                                 NGHTTP2_ENHANCE_YOUR_CALM);
965       return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
966     }
967 
968     session->rejected_stream_count_ = 0;
969   } else if (!stream->IsDestroyed()) {
970     stream->StartHeaders(frame->headers.cat);
971   }
972   return 0;
973 }
974 
975 // Called by nghttp2 for each header name/value pair in a HEADERS block.
976 // This had to have been preceded by a call to OnBeginHeadersCallback so
977 // the Http2Stream is guaranteed to already exist.
OnHeaderCallback(nghttp2_session * handle,const nghttp2_frame * frame,nghttp2_rcbuf * name,nghttp2_rcbuf * value,uint8_t flags,void * user_data)978 int Http2Session::OnHeaderCallback(nghttp2_session* handle,
979                                    const nghttp2_frame* frame,
980                                    nghttp2_rcbuf* name,
981                                    nghttp2_rcbuf* value,
982                                    uint8_t flags,
983                                    void* user_data) {
984   Http2Session* session = static_cast<Http2Session*>(user_data);
985   int32_t id = GetFrameID(frame);
986   Http2Stream* stream = session->FindStream(id);
987   // If stream is null at this point, either something odd has happened
988   // or the stream was closed locally while header processing was occurring.
989   // either way, do not proceed and close the stream.
990   if (UNLIKELY(stream == nullptr))
991     return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
992 
993   // If the stream has already been destroyed, ignore.
994   if (!stream->IsDestroyed() && !stream->AddHeader(name, value, flags)) {
995     // This will only happen if the connected peer sends us more
996     // than the allowed number of header items at any given time
997     stream->SubmitRstStream(NGHTTP2_ENHANCE_YOUR_CALM);
998     return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
999   }
1000   return 0;
1001 }
1002 
1003 
1004 // Called by nghttp2 when a complete HTTP2 frame has been received. There are
1005 // only a handful of frame types that we care about handling here.
OnFrameReceive(nghttp2_session * handle,const nghttp2_frame * frame,void * user_data)1006 int Http2Session::OnFrameReceive(nghttp2_session* handle,
1007                                  const nghttp2_frame* frame,
1008                                  void* user_data) {
1009   Http2Session* session = static_cast<Http2Session*>(user_data);
1010   session->statistics_.frame_count++;
1011   Debug(session, "complete frame received: type: %d",
1012         frame->hd.type);
1013   switch (frame->hd.type) {
1014     case NGHTTP2_DATA:
1015       return session->HandleDataFrame(frame);
1016     case NGHTTP2_PUSH_PROMISE:
1017       // Intentional fall-through, handled just like headers frames
1018     case NGHTTP2_HEADERS:
1019       session->HandleHeadersFrame(frame);
1020       break;
1021     case NGHTTP2_SETTINGS:
1022       session->HandleSettingsFrame(frame);
1023       break;
1024     case NGHTTP2_PRIORITY:
1025       session->HandlePriorityFrame(frame);
1026       break;
1027     case NGHTTP2_GOAWAY:
1028       session->HandleGoawayFrame(frame);
1029       break;
1030     case NGHTTP2_PING:
1031       session->HandlePingFrame(frame);
1032       break;
1033     case NGHTTP2_ALTSVC:
1034       session->HandleAltSvcFrame(frame);
1035       break;
1036     case NGHTTP2_ORIGIN:
1037       session->HandleOriginFrame(frame);
1038       break;
1039     default:
1040       break;
1041   }
1042   return 0;
1043 }
1044 
OnInvalidFrame(nghttp2_session * handle,const nghttp2_frame * frame,int lib_error_code,void * user_data)1045 int Http2Session::OnInvalidFrame(nghttp2_session* handle,
1046                                  const nghttp2_frame* frame,
1047                                  int lib_error_code,
1048                                  void* user_data) {
1049   Http2Session* session = static_cast<Http2Session*>(user_data);
1050 
1051   Debug(session, "invalid frame received, code: %d", lib_error_code);
1052   if (session->invalid_frame_count_++ > 1000 &&
1053       !IsReverted(SECURITY_REVERT_CVE_2019_9514)) {
1054     return 1;
1055   }
1056 
1057   // If the error is fatal or if error code is ERR_STREAM_CLOSED... emit error
1058   if (nghttp2_is_fatal(lib_error_code) ||
1059       lib_error_code == NGHTTP2_ERR_STREAM_CLOSED) {
1060     Environment* env = session->env();
1061     Isolate* isolate = env->isolate();
1062     HandleScope scope(isolate);
1063     Local<Context> context = env->context();
1064     Context::Scope context_scope(context);
1065     Local<Value> arg = Integer::New(isolate, lib_error_code);
1066     session->MakeCallback(env->error_string(), 1, &arg);
1067   }
1068   return 0;
1069 }
1070 
1071 // If nghttp2 is unable to send a queued up frame, it will call this callback
1072 // to let us know. If the failure occurred because we are in the process of
1073 // closing down the session or stream, we go ahead and ignore it. We don't
1074 // really care about those and there's nothing we can reasonably do about it
1075 // anyway. Other types of failures are reported up to JavaScript. This should
1076 // be exceedingly rare.
OnFrameNotSent(nghttp2_session * handle,const nghttp2_frame * frame,int error_code,void * user_data)1077 int Http2Session::OnFrameNotSent(nghttp2_session* handle,
1078                                  const nghttp2_frame* frame,
1079                                  int error_code,
1080                                  void* user_data) {
1081   Http2Session* session = static_cast<Http2Session*>(user_data);
1082   Environment* env = session->env();
1083   Debug(session, "frame type %d was not sent, code: %d",
1084         frame->hd.type, error_code);
1085 
1086   // Do not report if the frame was not sent due to the session closing
1087   if (error_code == NGHTTP2_ERR_SESSION_CLOSING ||
1088       error_code == NGHTTP2_ERR_STREAM_CLOSED ||
1089       error_code == NGHTTP2_ERR_STREAM_CLOSING ||
1090       session->js_fields_[kSessionFrameErrorListenerCount] == 0) {
1091     return 0;
1092   }
1093 
1094   Isolate* isolate = env->isolate();
1095   HandleScope scope(isolate);
1096   Local<Context> context = env->context();
1097   Context::Scope context_scope(context);
1098 
1099   Local<Value> argv[3] = {
1100     Integer::New(isolate, frame->hd.stream_id),
1101     Integer::New(isolate, frame->hd.type),
1102     Integer::New(isolate, error_code)
1103   };
1104   session->MakeCallback(env->onframeerror_string(), arraysize(argv), argv);
1105   return 0;
1106 }
1107 
OnFrameSent(nghttp2_session * handle,const nghttp2_frame * frame,void * user_data)1108 int Http2Session::OnFrameSent(nghttp2_session* handle,
1109                               const nghttp2_frame* frame,
1110                               void* user_data) {
1111   Http2Session* session = static_cast<Http2Session*>(user_data);
1112   session->statistics_.frame_sent += 1;
1113   return 0;
1114 }
1115 
1116 // Called by nghttp2 when a stream closes.
OnStreamClose(nghttp2_session * handle,int32_t id,uint32_t code,void * user_data)1117 int Http2Session::OnStreamClose(nghttp2_session* handle,
1118                                 int32_t id,
1119                                 uint32_t code,
1120                                 void* user_data) {
1121   Http2Session* session = static_cast<Http2Session*>(user_data);
1122   Environment* env = session->env();
1123   Isolate* isolate = env->isolate();
1124   HandleScope scope(isolate);
1125   Local<Context> context = env->context();
1126   Context::Scope context_scope(context);
1127   Debug(session, "stream %d closed with code: %d", id, code);
1128   Http2Stream* stream = session->FindStream(id);
1129   // Intentionally ignore the callback if the stream does not exist or has
1130   // already been destroyed
1131   if (stream == nullptr || stream->IsDestroyed())
1132     return 0;
1133 
1134   stream->Close(code);
1135   // It is possible for the stream close to occur before the stream is
1136   // ever passed on to the javascript side. If that happens, skip straight
1137   // to destroying the stream. We can check this by looking for the
1138   // onstreamclose function. If it exists, then the stream has already
1139   // been passed on to javascript.
1140   Local<Value> fn =
1141       stream->object()->Get(context, env->onstreamclose_string())
1142           .ToLocalChecked();
1143 
1144   if (!fn->IsFunction()) {
1145     stream->Destroy();
1146     return 0;
1147   }
1148 
1149   Local<Value> arg = Integer::NewFromUnsigned(isolate, code);
1150   stream->MakeCallback(fn.As<Function>(), 1, &arg);
1151   return 0;
1152 }
1153 
1154 // Called by nghttp2 when an invalid header has been received. For now, we
1155 // ignore these. If this callback was not provided, nghttp2 would handle
1156 // invalid headers strictly and would shut down the stream. We are intentionally
1157 // being more lenient here although we may want to revisit this choice later.
OnInvalidHeader(nghttp2_session * session,const nghttp2_frame * frame,nghttp2_rcbuf * name,nghttp2_rcbuf * value,uint8_t flags,void * user_data)1158 int Http2Session::OnInvalidHeader(nghttp2_session* session,
1159                                   const nghttp2_frame* frame,
1160                                   nghttp2_rcbuf* name,
1161                                   nghttp2_rcbuf* value,
1162                                   uint8_t flags,
1163                                   void* user_data) {
1164   // Ignore invalid header fields by default.
1165   return 0;
1166 }
1167 
1168 // When nghttp2 receives a DATA frame, it will deliver the data payload to
1169 // us in discrete chunks. We push these into a linked list stored in the
1170 // Http2Sttream which is flushed out to JavaScript as quickly as possible.
1171 // This can be a particularly hot path.
OnDataChunkReceived(nghttp2_session * handle,uint8_t flags,int32_t id,const uint8_t * data,size_t len,void * user_data)1172 int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
1173                                       uint8_t flags,
1174                                       int32_t id,
1175                                       const uint8_t* data,
1176                                       size_t len,
1177                                       void* user_data) {
1178   Http2Session* session = static_cast<Http2Session*>(user_data);
1179   Debug(session, "buffering data chunk for stream %d, size: "
1180         "%d, flags: %d", id, len, flags);
1181   Environment* env = session->env();
1182   HandleScope scope(env->isolate());
1183 
1184   // We should never actually get a 0-length chunk so this check is
1185   // only a precaution at this point.
1186   if (len == 0)
1187     return 0;
1188 
1189   // Notify nghttp2 that we've consumed a chunk of data on the connection
1190   // so that it can send a WINDOW_UPDATE frame. This is a critical part of
1191   // the flow control process in http2
1192   CHECK_EQ(nghttp2_session_consume_connection(handle, len), 0);
1193   Http2Stream* stream = session->FindStream(id);
1194   // If the stream has been destroyed, ignore this chunk
1195   if (stream->IsDestroyed())
1196     return 0;
1197 
1198   stream->statistics_.received_bytes += len;
1199 
1200   // Repeatedly ask the stream's owner for memory, and copy the read data
1201   // into those buffers.
1202   // The typical case is actually the exception here; Http2StreamListeners
1203   // know about the HTTP2 session associated with this stream, so they know
1204   // about the larger from-socket read buffer, so they do not require copying.
1205   do {
1206     uv_buf_t buf = stream->EmitAlloc(len);
1207     ssize_t avail = len;
1208     if (static_cast<ssize_t>(buf.len) < avail)
1209       avail = buf.len;
1210 
1211     // `buf.base == nullptr` is the default Http2StreamListener's way
1212     // of saying that it wants a pointer to the raw original.
1213     // Since it has access to the original socket buffer from which the data
1214     // was read in the first place, it can use that to minimize ArrayBuffer
1215     // allocations.
1216     if (LIKELY(buf.base == nullptr))
1217       buf.base = reinterpret_cast<char*>(const_cast<uint8_t*>(data));
1218     else
1219       memcpy(buf.base, data, avail);
1220     data += avail;
1221     len -= avail;
1222     stream->EmitRead(avail, buf);
1223 
1224     // If the stream owner (e.g. the JS Http2Stream) wants more data, just
1225     // tell nghttp2 that all data has been consumed. Otherwise, defer until
1226     // more data is being requested.
1227     if (stream->IsReading())
1228       nghttp2_session_consume_stream(handle, id, avail);
1229     else
1230       stream->inbound_consumed_data_while_paused_ += avail;
1231 
1232     // If we have a gathered a lot of data for output, try sending it now.
1233     if (session->outgoing_length_ > 4096 ||
1234         stream->available_outbound_length_ > 4096) {
1235       session->SendPendingData();
1236     }
1237   } while (len != 0);
1238 
1239   // If we are currently waiting for a write operation to finish, we should
1240   // tell nghttp2 that we want to wait before we process more input data.
1241   if (session->flags_ & SESSION_STATE_WRITE_IN_PROGRESS) {
1242     CHECK_NE(session->flags_ & SESSION_STATE_READING_STOPPED, 0);
1243     session->flags_ |= SESSION_STATE_NGHTTP2_RECV_PAUSED;
1244     Debug(session, "receive paused");
1245     return NGHTTP2_ERR_PAUSE;
1246   }
1247 
1248   return 0;
1249 }
1250 
1251 // Called by nghttp2 when it needs to determine how much padding to use in
1252 // a DATA or HEADERS frame.
OnSelectPadding(nghttp2_session * handle,const nghttp2_frame * frame,size_t maxPayloadLen,void * user_data)1253 ssize_t Http2Session::OnSelectPadding(nghttp2_session* handle,
1254                                       const nghttp2_frame* frame,
1255                                       size_t maxPayloadLen,
1256                                       void* user_data) {
1257   Http2Session* session = static_cast<Http2Session*>(user_data);
1258   ssize_t padding = frame->hd.length;
1259 
1260   switch (session->padding_strategy_) {
1261     case PADDING_STRATEGY_NONE:
1262       // Fall-through
1263       break;
1264     case PADDING_STRATEGY_MAX:
1265       padding = session->OnMaxFrameSizePadding(padding, maxPayloadLen);
1266       break;
1267     case PADDING_STRATEGY_ALIGNED:
1268       padding = session->OnDWordAlignedPadding(padding, maxPayloadLen);
1269       break;
1270     case PADDING_STRATEGY_CALLBACK:
1271       padding = session->OnCallbackPadding(padding, maxPayloadLen);
1272       break;
1273   }
1274   return padding;
1275 }
1276 
1277 #define BAD_PEER_MESSAGE "Remote peer returned unexpected data while we "     \
1278                          "expected SETTINGS frame.  Perhaps, peer does not "  \
1279                          "support HTTP/2 properly."
1280 
1281 // We use this currently to determine when an attempt is made to use the http2
1282 // protocol with a non-http2 peer.
OnNghttpError(nghttp2_session * handle,const char * message,size_t len,void * user_data)1283 int Http2Session::OnNghttpError(nghttp2_session* handle,
1284                                 const char* message,
1285                                 size_t len,
1286                                 void* user_data) {
1287   // Unfortunately, this is currently the only way for us to know if
1288   // the session errored because the peer is not an http2 peer.
1289   Http2Session* session = static_cast<Http2Session*>(user_data);
1290   Debug(session, "Error '%.*s'", len, message);
1291   if (strncmp(message, BAD_PEER_MESSAGE, len) == 0) {
1292     Environment* env = session->env();
1293     Isolate* isolate = env->isolate();
1294     HandleScope scope(isolate);
1295     Local<Context> context = env->context();
1296     Context::Scope context_scope(context);
1297     Local<Value> arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1298     session->MakeCallback(env->error_string(), 1, &arg);
1299   }
1300   return 0;
1301 }
1302 
OnStreamAlloc(size_t size)1303 uv_buf_t Http2StreamListener::OnStreamAlloc(size_t size) {
1304   // See the comments in Http2Session::OnDataChunkReceived
1305   // (which is the only possible call site for this method).
1306   return uv_buf_init(nullptr, size);
1307 }
1308 
OnStreamRead(ssize_t nread,const uv_buf_t & buf)1309 void Http2StreamListener::OnStreamRead(ssize_t nread, const uv_buf_t& buf) {
1310   Http2Stream* stream = static_cast<Http2Stream*>(stream_);
1311   Http2Session* session = stream->session();
1312   Environment* env = stream->env();
1313   HandleScope handle_scope(env->isolate());
1314   Context::Scope context_scope(env->context());
1315 
1316   if (nread < 0) {
1317     PassReadErrorToPreviousListener(nread);
1318     return;
1319   }
1320 
1321   Local<ArrayBuffer> ab;
1322   if (session->stream_buf_ab_.IsEmpty()) {
1323     ab = ArrayBuffer::New(env->isolate(),
1324                           session->stream_buf_allocation_.base,
1325                           session->stream_buf_allocation_.len,
1326                           v8::ArrayBufferCreationMode::kInternalized);
1327     session->stream_buf_allocation_ = uv_buf_init(nullptr, 0);
1328     session->stream_buf_ab_.Reset(env->isolate(), ab);
1329   } else {
1330     ab = session->stream_buf_ab_.Get(env->isolate());
1331   }
1332 
1333   // There is a single large array buffer for the entire data read from the
1334   // network; create a slice of that array buffer and emit it as the
1335   // received data buffer.
1336   size_t offset = buf.base - session->stream_buf_.base;
1337 
1338   // Verify that the data offset is inside the current read buffer.
1339   CHECK_GE(offset, session->stream_buf_offset_);
1340   CHECK_LE(offset, session->stream_buf_.len);
1341   CHECK_LE(offset + buf.len, session->stream_buf_.len);
1342 
1343   Local<Object> buffer =
1344       Buffer::New(env, ab, offset, nread).ToLocalChecked();
1345 
1346   stream->CallJSOnreadMethod(nread, buffer);
1347 }
1348 
1349 
1350 // Called by OnFrameReceived to notify JavaScript land that a complete
1351 // HEADERS frame has been received and processed. This method converts the
1352 // received headers into a JavaScript array and pushes those out to JS.
HandleHeadersFrame(const nghttp2_frame * frame)1353 void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
1354   Isolate* isolate = env()->isolate();
1355   HandleScope scope(isolate);
1356   Local<Context> context = env()->context();
1357   Context::Scope context_scope(context);
1358 
1359   int32_t id = GetFrameID(frame);
1360   Debug(this, "handle headers frame for stream %d", id);
1361   Http2Stream* stream = FindStream(id);
1362 
1363   // If the stream has already been destroyed, ignore.
1364   if (stream->IsDestroyed())
1365     return;
1366 
1367   std::vector<nghttp2_header> headers(stream->move_headers());
1368   DecrementCurrentSessionMemory(stream->current_headers_length_);
1369   stream->current_headers_length_ = 0;
1370 
1371   Local<String> name_str;
1372   Local<String> value_str;
1373 
1374   Local<Array> holder = Array::New(isolate);
1375   Local<Function> fn = env()->push_values_to_array_function();
1376   Local<Value> argv[NODE_PUSH_VAL_TO_ARRAY_MAX * 2];
1377 
1378   // The headers are passed in above as a queue of nghttp2_header structs.
1379   // The following converts that into a JS array with the structure:
1380   // [name1, value1, name2, value2, name3, value3, name3, value4] and so on.
1381   // That array is passed up to the JS layer and converted into an Object form
1382   // like {name1: value1, name2: value2, name3: [value3, value4]}. We do it
1383   // this way for performance reasons (it's faster to generate and pass an
1384   // array than it is to generate and pass the object).
1385   size_t n = 0;
1386   while (n < headers.size()) {
1387     size_t j = 0;
1388     while (n < headers.size() && j < arraysize(argv) / 2) {
1389       nghttp2_header item = headers[n++];
1390       // The header name and value are passed as external one-byte strings
1391       name_str =
1392           ExternalHeader::New<true>(this, item.name).ToLocalChecked();
1393       value_str =
1394           ExternalHeader::New<false>(this, item.value).ToLocalChecked();
1395       argv[j * 2] = name_str;
1396       argv[j * 2 + 1] = value_str;
1397       j++;
1398     }
1399     // For performance, we pass name and value pairs to array.protototype.push
1400     // in batches of size NODE_PUSH_VAL_TO_ARRAY_MAX * 2 until there are no
1401     // more items to push.
1402     if (j > 0) {
1403       fn->Call(env()->context(), holder, j * 2, argv).ToLocalChecked();
1404     }
1405   }
1406 
1407   Local<Value> args[5] = {
1408     stream->object(),
1409     Integer::New(isolate, id),
1410     Integer::New(isolate, stream->headers_category()),
1411     Integer::New(isolate, frame->hd.flags),
1412     holder
1413   };
1414   MakeCallback(env()->onheaders_string(), arraysize(args), args);
1415 }
1416 
1417 
1418 // Called by OnFrameReceived when a complete PRIORITY frame has been
1419 // received. Notifies JS land about the priority change. Note that priorities
1420 // are considered advisory only, so this has no real effect other than to
1421 // simply let user code know that the priority has changed.
HandlePriorityFrame(const nghttp2_frame * frame)1422 void Http2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
1423   if (js_fields_[kSessionPriorityListenerCount] == 0) return;
1424   Isolate* isolate = env()->isolate();
1425   HandleScope scope(isolate);
1426   Local<Context> context = env()->context();
1427   Context::Scope context_scope(context);
1428 
1429   nghttp2_priority priority_frame = frame->priority;
1430   int32_t id = GetFrameID(frame);
1431   Debug(this, "handle priority frame for stream %d", id);
1432   // Priority frame stream ID should never be <= 0. nghttp2 handles this for us
1433   nghttp2_priority_spec spec = priority_frame.pri_spec;
1434 
1435   Local<Value> argv[4] = {
1436     Integer::New(isolate, id),
1437     Integer::New(isolate, spec.stream_id),
1438     Integer::New(isolate, spec.weight),
1439     Boolean::New(isolate, spec.exclusive)
1440   };
1441   MakeCallback(env()->onpriority_string(), arraysize(argv), argv);
1442 }
1443 
1444 
1445 // Called by OnFrameReceived when a complete DATA frame has been received.
1446 // If we know that this was the last DATA frame (because the END_STREAM flag
1447 // is set), then we'll terminate the readable side of the StreamBase.
HandleDataFrame(const nghttp2_frame * frame)1448 int Http2Session::HandleDataFrame(const nghttp2_frame* frame) {
1449   int32_t id = GetFrameID(frame);
1450   Debug(this, "handling data frame for stream %d", id);
1451   Http2Stream* stream = FindStream(id);
1452 
1453   if (!stream->IsDestroyed() && frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
1454     stream->EmitRead(UV_EOF);
1455   } else if (frame->hd.length == 0 &&
1456            !IsReverted(SECURITY_REVERT_CVE_2019_9518)) {
1457     return 1;  // Consider 0-length frame without END_STREAM an error.
1458   }
1459   return 0;
1460 }
1461 
1462 
1463 // Called by OnFrameReceived when a complete GOAWAY frame has been received.
HandleGoawayFrame(const nghttp2_frame * frame)1464 void Http2Session::HandleGoawayFrame(const nghttp2_frame* frame) {
1465   Isolate* isolate = env()->isolate();
1466   HandleScope scope(isolate);
1467   Local<Context> context = env()->context();
1468   Context::Scope context_scope(context);
1469 
1470   nghttp2_goaway goaway_frame = frame->goaway;
1471   Debug(this, "handling goaway frame");
1472 
1473   Local<Value> argv[3] = {
1474     Integer::NewFromUnsigned(isolate, goaway_frame.error_code),
1475     Integer::New(isolate, goaway_frame.last_stream_id),
1476     Undefined(isolate)
1477   };
1478 
1479   size_t length = goaway_frame.opaque_data_len;
1480   if (length > 0) {
1481     argv[2] = Buffer::Copy(isolate,
1482                            reinterpret_cast<char*>(goaway_frame.opaque_data),
1483                            length).ToLocalChecked();
1484   }
1485 
1486   MakeCallback(env()->ongoawaydata_string(), arraysize(argv), argv);
1487 }
1488 
1489 // Called by OnFrameReceived when a complete ALTSVC frame has been received.
HandleAltSvcFrame(const nghttp2_frame * frame)1490 void Http2Session::HandleAltSvcFrame(const nghttp2_frame* frame) {
1491   if (!(js_fields_[kBitfield] & (1 << kSessionHasAltsvcListeners))) return;
1492   Isolate* isolate = env()->isolate();
1493   HandleScope scope(isolate);
1494   Local<Context> context = env()->context();
1495   Context::Scope context_scope(context);
1496 
1497   int32_t id = GetFrameID(frame);
1498 
1499   nghttp2_extension ext = frame->ext;
1500   nghttp2_ext_altsvc* altsvc = static_cast<nghttp2_ext_altsvc*>(ext.payload);
1501   Debug(this, "handling altsvc frame");
1502 
1503   Local<Value> argv[3] = {
1504     Integer::New(isolate, id),
1505     String::NewFromOneByte(isolate,
1506                            altsvc->origin,
1507                            NewStringType::kNormal,
1508                            altsvc->origin_len).ToLocalChecked(),
1509     String::NewFromOneByte(isolate,
1510                            altsvc->field_value,
1511                            NewStringType::kNormal,
1512                            altsvc->field_value_len).ToLocalChecked(),
1513   };
1514 
1515   MakeCallback(env()->onaltsvc_string(), arraysize(argv), argv);
1516 }
1517 
HandleOriginFrame(const nghttp2_frame * frame)1518 void Http2Session::HandleOriginFrame(const nghttp2_frame* frame) {
1519   Isolate* isolate = env()->isolate();
1520   HandleScope scope(isolate);
1521   Local<Context> context = env()->context();
1522   Context::Scope context_scope(context);
1523 
1524   Debug(this, "handling origin frame");
1525 
1526   nghttp2_extension ext = frame->ext;
1527   nghttp2_ext_origin* origin = static_cast<nghttp2_ext_origin*>(ext.payload);
1528 
1529   Local<Value> holder = Array::New(isolate);
1530   Local<Function> fn = env()->push_values_to_array_function();
1531   Local<Value> argv[NODE_PUSH_VAL_TO_ARRAY_MAX];
1532 
1533   size_t n = 0;
1534   while (n < origin->nov) {
1535     size_t j = 0;
1536     while (n < origin->nov && j < arraysize(argv)) {
1537       auto entry = origin->ov[n++];
1538       argv[j++] =
1539         String::NewFromOneByte(isolate,
1540                                entry.origin,
1541                                NewStringType::kNormal,
1542                                entry.origin_len).ToLocalChecked();
1543     }
1544     if (j > 0)
1545       fn->Call(context, holder, j, argv).ToLocalChecked();
1546   }
1547 
1548   MakeCallback(env()->onorigin_string(), 1, &holder);
1549 }
1550 
1551 // Called by OnFrameReceived when a complete PING frame has been received.
HandlePingFrame(const nghttp2_frame * frame)1552 void Http2Session::HandlePingFrame(const nghttp2_frame* frame) {
1553   Isolate* isolate = env()->isolate();
1554   HandleScope scope(isolate);
1555   Local<Context> context = env()->context();
1556   Context::Scope context_scope(context);
1557   Local<Value> arg;
1558   bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
1559   if (ack) {
1560     Http2Ping* ping = PopPing();
1561 
1562     if (ping == nullptr) {
1563       // PING Ack is unsolicited. Treat as a connection error. The HTTP/2
1564       // spec does not require this, but there is no legitimate reason to
1565       // receive an unsolicited PING ack on a connection. Either the peer
1566       // is buggy or malicious, and we're not going to tolerate such
1567       // nonsense.
1568       arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1569       MakeCallback(env()->error_string(), 1, &arg);
1570       return;
1571     }
1572 
1573     ping->Done(true, frame->ping.opaque_data);
1574     return;
1575   }
1576 
1577   if (!(js_fields_[kBitfield] & (1 << kSessionHasPingListeners))) return;
1578   // Notify the session that a ping occurred
1579   arg = Buffer::Copy(env(),
1580                       reinterpret_cast<const char*>(frame->ping.opaque_data),
1581                       8).ToLocalChecked();
1582   MakeCallback(env()->onping_string(), 1, &arg);
1583 }
1584 
1585 // Called by OnFrameReceived when a complete SETTINGS frame has been received.
HandleSettingsFrame(const nghttp2_frame * frame)1586 void Http2Session::HandleSettingsFrame(const nghttp2_frame* frame) {
1587   bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
1588   if (!ack) {
1589     js_fields_[kBitfield] &= ~(1 << kSessionRemoteSettingsIsUpToDate);
1590     if (!(js_fields_[kBitfield] & (1 << kSessionHasRemoteSettingsListeners)))
1591       return;
1592     // This is not a SETTINGS acknowledgement, notify and return
1593     MakeCallback(env()->onsettings_string(), 0, nullptr);
1594     return;
1595   }
1596 
1597   // If this is an acknowledgement, we should have an Http2Settings
1598   // object for it.
1599   Http2Settings* settings = PopSettings();
1600   if (settings != nullptr) {
1601     settings->Done(true);
1602     return;
1603   }
1604   // SETTINGS Ack is unsolicited. Treat as a connection error. The HTTP/2
1605   // spec does not require this, but there is no legitimate reason to
1606   // receive an unsolicited SETTINGS ack on a connection. Either the peer
1607   // is buggy or malicious, and we're not going to tolerate such
1608   // nonsense.
1609   // Note that nghttp2 currently prevents this from happening for SETTINGS
1610   // frames, so this block is purely defensive just in case that behavior
1611   // changes. Specifically, unlike unsolicited PING acks, unsolicited
1612   // SETTINGS acks should *never* make it this far.
1613   Isolate* isolate = env()->isolate();
1614   HandleScope scope(isolate);
1615   Local<Context> context = env()->context();
1616   Context::Scope context_scope(context);
1617   Local<Value> arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1618   MakeCallback(env()->error_string(), 1, &arg);
1619 }
1620 
1621 // Callback used when data has been written to the stream.
OnStreamAfterWrite(WriteWrap * w,int status)1622 void Http2Session::OnStreamAfterWrite(WriteWrap* w, int status) {
1623   Debug(this, "write finished with status %d", status);
1624 
1625   CHECK_NE(flags_ & SESSION_STATE_WRITE_IN_PROGRESS, 0);
1626   flags_ &= ~SESSION_STATE_WRITE_IN_PROGRESS;
1627 
1628   // Inform all pending writes about their completion.
1629   ClearOutgoing(status);
1630 
1631   if ((flags_ & SESSION_STATE_READING_STOPPED) &&
1632       !(flags_ & SESSION_STATE_WRITE_IN_PROGRESS) &&
1633       nghttp2_session_want_read(session_)) {
1634     flags_ &= ~SESSION_STATE_READING_STOPPED;
1635     stream_->ReadStart();
1636   }
1637 
1638   // If there is more incoming data queued up, consume it.
1639   if (stream_buf_offset_ > 0) {
1640     ConsumeHTTP2Data();
1641   }
1642 
1643   if (!(flags_ & SESSION_STATE_WRITE_SCHEDULED)) {
1644     // Schedule a new write if nghttp2 wants to send data.
1645     MaybeScheduleWrite();
1646   }
1647 }
1648 
1649 // If the underlying nghttp2_session struct has data pending in its outbound
1650 // queue, MaybeScheduleWrite will schedule a SendPendingData() call to occur
1651 // on the next iteration of the Node.js event loop (using the SetImmediate
1652 // queue), but only if a write has not already been scheduled.
MaybeScheduleWrite()1653 void Http2Session::MaybeScheduleWrite() {
1654   CHECK_EQ(flags_ & SESSION_STATE_WRITE_SCHEDULED, 0);
1655   if (UNLIKELY(session_ == nullptr))
1656     return;
1657 
1658   if (nghttp2_session_want_write(session_)) {
1659     HandleScope handle_scope(env()->isolate());
1660     Debug(this, "scheduling write");
1661     flags_ |= SESSION_STATE_WRITE_SCHEDULED;
1662     env()->SetImmediate([](Environment* env, void* data) {
1663       Http2Session* session = static_cast<Http2Session*>(data);
1664       if (session->session_ == nullptr ||
1665           !(session->flags_ & SESSION_STATE_WRITE_SCHEDULED)) {
1666         // This can happen e.g. when a stream was reset before this turn
1667         // of the event loop, in which case SendPendingData() is called early,
1668         // or the session was destroyed in the meantime.
1669         return;
1670       }
1671 
1672       // Sending data may call arbitrary JS code, so keep track of
1673       // async context.
1674       HandleScope handle_scope(env->isolate());
1675       InternalCallbackScope callback_scope(session);
1676       session->SendPendingData();
1677     }, static_cast<void*>(this), object());
1678   }
1679 }
1680 
MaybeStopReading()1681 void Http2Session::MaybeStopReading() {
1682   if (flags_ & SESSION_STATE_READING_STOPPED) return;
1683   int want_read = nghttp2_session_want_read(session_);
1684   Debug(this, "wants read? %d", want_read);
1685   if (want_read == 0 || (flags_ & SESSION_STATE_WRITE_IN_PROGRESS)) {
1686     flags_ |= SESSION_STATE_READING_STOPPED;
1687     stream_->ReadStop();
1688   }
1689 }
1690 
1691 // Unset the sending state, finish up all current writes, and reset
1692 // storage for data and metadata that was associated with these writes.
ClearOutgoing(int status)1693 void Http2Session::ClearOutgoing(int status) {
1694   CHECK_NE(flags_ & SESSION_STATE_SENDING, 0);
1695 
1696   flags_ &= ~SESSION_STATE_SENDING;
1697 
1698   if (outgoing_buffers_.size() > 0) {
1699     outgoing_storage_.clear();
1700     outgoing_length_ = 0;
1701 
1702     std::vector<nghttp2_stream_write> current_outgoing_buffers_;
1703     current_outgoing_buffers_.swap(outgoing_buffers_);
1704     for (const nghttp2_stream_write& wr : current_outgoing_buffers_) {
1705       WriteWrap* wrap = wr.req_wrap;
1706       if (wrap != nullptr)
1707         wrap->Done(status);
1708     }
1709   }
1710 
1711   // Now that we've finished sending queued data, if there are any pending
1712   // RstStreams we should try sending again and then flush them one by one.
1713   if (pending_rst_streams_.size() > 0) {
1714     std::vector<int32_t> current_pending_rst_streams;
1715     pending_rst_streams_.swap(current_pending_rst_streams);
1716 
1717     SendPendingData();
1718 
1719     for (int32_t stream_id : current_pending_rst_streams) {
1720       Http2Stream* stream = FindStream(stream_id);
1721       if (LIKELY(stream != nullptr))
1722         stream->FlushRstStream();
1723     }
1724   }
1725 }
1726 
PushOutgoingBuffer(nghttp2_stream_write && write)1727 void Http2Session::PushOutgoingBuffer(nghttp2_stream_write&& write) {
1728   outgoing_length_ += write.buf.len;
1729   outgoing_buffers_.emplace_back(std::move(write));
1730 }
1731 
1732 // Queue a given block of data for sending. This always creates a copy,
1733 // so it is used for the cases in which nghttp2 requests sending of a
1734 // small chunk of data.
CopyDataIntoOutgoing(const uint8_t * src,size_t src_length)1735 void Http2Session::CopyDataIntoOutgoing(const uint8_t* src, size_t src_length) {
1736   size_t offset = outgoing_storage_.size();
1737   outgoing_storage_.resize(offset + src_length);
1738   memcpy(&outgoing_storage_[offset], src, src_length);
1739 
1740   // Store with a base of `nullptr` initially, since future resizes
1741   // of the outgoing_buffers_ vector may invalidate the pointer.
1742   // The correct base pointers will be set later, before writing to the
1743   // underlying socket.
1744   PushOutgoingBuffer(nghttp2_stream_write {
1745     uv_buf_init(nullptr, src_length)
1746   });
1747 }
1748 
1749 // Prompts nghttp2 to begin serializing it's pending data and pushes each
1750 // chunk out to the i/o socket to be sent. This is a particularly hot method
1751 // that will generally be called at least twice be event loop iteration.
1752 // This is a potential performance optimization target later.
1753 // Returns non-zero value if a write is already in progress.
SendPendingData()1754 uint8_t Http2Session::SendPendingData() {
1755   Debug(this, "sending pending data");
1756   // Do not attempt to send data on the socket if the destroying flag has
1757   // been set. That means everything is shutting down and the socket
1758   // will not be usable.
1759   if (IsDestroyed())
1760     return 0;
1761   flags_ &= ~SESSION_STATE_WRITE_SCHEDULED;
1762 
1763   // SendPendingData should not be called recursively.
1764   if (flags_ & SESSION_STATE_SENDING)
1765     return 1;
1766   // This is cleared by ClearOutgoing().
1767   flags_ |= SESSION_STATE_SENDING;
1768 
1769   ssize_t src_length;
1770   const uint8_t* src;
1771 
1772   CHECK_EQ(outgoing_buffers_.size(), 0);
1773   CHECK_EQ(outgoing_storage_.size(), 0);
1774 
1775   // Part One: Gather data from nghttp2
1776 
1777   while ((src_length = nghttp2_session_mem_send(session_, &src)) > 0) {
1778     Debug(this, "nghttp2 has %d bytes to send", src_length);
1779     CopyDataIntoOutgoing(src, src_length);
1780   }
1781 
1782   CHECK_NE(src_length, NGHTTP2_ERR_NOMEM);
1783 
1784   if (stream_ == nullptr) {
1785     // It would seem nice to bail out earlier, but `nghttp2_session_mem_send()`
1786     // does take care of things like closing the individual streams after
1787     // a socket has been torn down, so we still need to call it.
1788     ClearOutgoing(UV_ECANCELED);
1789     return 0;
1790   }
1791 
1792   // Part Two: Pass Data to the underlying stream
1793 
1794   size_t count = outgoing_buffers_.size();
1795   if (count == 0) {
1796     ClearOutgoing(0);
1797     return 0;
1798   }
1799   MaybeStackBuffer<uv_buf_t, 32> bufs;
1800   bufs.AllocateSufficientStorage(count);
1801 
1802   // Set the buffer base pointers for copied data that ended up in the
1803   // sessions's own storage since it might have shifted around during gathering.
1804   // (Those are marked by having .base == nullptr.)
1805   size_t offset = 0;
1806   size_t i = 0;
1807   for (const nghttp2_stream_write& write : outgoing_buffers_) {
1808     statistics_.data_sent += write.buf.len;
1809     if (write.buf.base == nullptr) {
1810       bufs[i++] = uv_buf_init(
1811           reinterpret_cast<char*>(outgoing_storage_.data() + offset),
1812           write.buf.len);
1813       offset += write.buf.len;
1814     } else {
1815       bufs[i++] = write.buf;
1816     }
1817   }
1818 
1819   chunks_sent_since_last_write_++;
1820 
1821   CHECK_EQ(flags_ & SESSION_STATE_WRITE_IN_PROGRESS, 0);
1822   flags_ |= SESSION_STATE_WRITE_IN_PROGRESS;
1823   StreamWriteResult res = underlying_stream()->Write(*bufs, count);
1824   if (!res.async) {
1825     flags_ &= ~SESSION_STATE_WRITE_IN_PROGRESS;
1826     ClearOutgoing(res.err);
1827   }
1828 
1829   MaybeStopReading();
1830 
1831   return 0;
1832 }
1833 
1834 
1835 // This callback is called from nghttp2 when it wants to send DATA frames for a
1836 // given Http2Stream, when we set the `NGHTTP2_DATA_FLAG_NO_COPY` flag earlier
1837 // in the Http2Stream::Provider::Stream::OnRead callback.
1838 // We take the write information directly out of the stream's data queue.
OnSendData(nghttp2_session * session_,nghttp2_frame * frame,const uint8_t * framehd,size_t length,nghttp2_data_source * source,void * user_data)1839 int Http2Session::OnSendData(
1840       nghttp2_session* session_,
1841       nghttp2_frame* frame,
1842       const uint8_t* framehd,
1843       size_t length,
1844       nghttp2_data_source* source,
1845       void* user_data) {
1846   Http2Session* session = static_cast<Http2Session*>(user_data);
1847   Http2Stream* stream = GetStream(session, frame->hd.stream_id, source);
1848 
1849   // Send the frame header + a byte that indicates padding length.
1850   session->CopyDataIntoOutgoing(framehd, 9);
1851   if (frame->data.padlen > 0) {
1852     uint8_t padding_byte = frame->data.padlen - 1;
1853     CHECK_EQ(padding_byte, frame->data.padlen - 1);
1854     session->CopyDataIntoOutgoing(&padding_byte, 1);
1855   }
1856 
1857   Debug(session, "nghttp2 has %d bytes to send directly", length);
1858   while (length > 0) {
1859     // nghttp2 thinks that there is data available (length > 0), which means
1860     // we told it so, which means that we *should* have data available.
1861     CHECK(!stream->queue_.empty());
1862 
1863     nghttp2_stream_write& write = stream->queue_.front();
1864     if (write.buf.len <= length) {
1865       // This write does not suffice by itself, so we can consume it completely.
1866       length -= write.buf.len;
1867       session->PushOutgoingBuffer(std::move(write));
1868       stream->queue_.pop();
1869       continue;
1870     }
1871 
1872     // Slice off `length` bytes of the first write in the queue.
1873     session->PushOutgoingBuffer(nghttp2_stream_write {
1874       uv_buf_init(write.buf.base, length)
1875     });
1876     write.buf.base += length;
1877     write.buf.len -= length;
1878     break;
1879   }
1880 
1881   if (frame->data.padlen > 0) {
1882     // Send padding if that was requested.
1883     session->PushOutgoingBuffer(nghttp2_stream_write {
1884       uv_buf_init(const_cast<char*>(zero_bytes_256), frame->data.padlen - 1)
1885     });
1886   }
1887 
1888   return 0;
1889 }
1890 
1891 // Creates a new Http2Stream and submits a new http2 request.
SubmitRequest(nghttp2_priority_spec * prispec,nghttp2_nv * nva,size_t len,int32_t * ret,int options)1892 Http2Stream* Http2Session::SubmitRequest(
1893     nghttp2_priority_spec* prispec,
1894     nghttp2_nv* nva,
1895     size_t len,
1896     int32_t* ret,
1897     int options) {
1898   Debug(this, "submitting request");
1899   Http2Scope h2scope(this);
1900   Http2Stream* stream = nullptr;
1901   Http2Stream::Provider::Stream prov(options);
1902   *ret = nghttp2_submit_request(session_, prispec, nva, len, *prov, nullptr);
1903   CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
1904   if (LIKELY(*ret > 0))
1905     stream = Http2Stream::New(this, *ret, NGHTTP2_HCAT_HEADERS, options);
1906   return stream;
1907 }
1908 
1909 // Callback used to receive inbound data from the i/o stream
OnStreamRead(ssize_t nread,const uv_buf_t & buf_)1910 void Http2Session::OnStreamRead(ssize_t nread, const uv_buf_t& buf_) {
1911   HandleScope handle_scope(env()->isolate());
1912   Context::Scope context_scope(env()->context());
1913   Http2Scope h2scope(this);
1914   CHECK_NOT_NULL(stream_);
1915   Debug(this, "receiving %d bytes", nread);
1916 
1917   // Only pass data on if nread > 0
1918   if (nread <= 0) {
1919     free(buf_.base);
1920     if (nread < 0) {
1921       PassReadErrorToPreviousListener(nread);
1922     }
1923     return;
1924   }
1925 
1926   uv_buf_t buf = buf_;
1927 
1928   statistics_.data_received += nread;
1929 
1930   if (UNLIKELY(stream_buf_offset_ > 0)) {
1931     // This is a very unlikely case, and should only happen if the ReadStart()
1932     // call in OnStreamAfterWrite() immediately provides data. If that does
1933     // happen, we concatenate the data we received with the already-stored
1934     // pending input data, slicing off the already processed part.
1935     char* new_buf = Malloc(stream_buf_.len - stream_buf_offset_ + nread);
1936     memcpy(new_buf,
1937            stream_buf_.base + stream_buf_offset_,
1938            stream_buf_.len - stream_buf_offset_);
1939     memcpy(new_buf + stream_buf_.len - stream_buf_offset_,
1940            buf.base,
1941            nread);
1942     free(buf.base);
1943     nread = stream_buf_.len - stream_buf_offset_ + nread;
1944     buf = uv_buf_init(new_buf, nread);
1945     stream_buf_offset_ = 0;
1946     stream_buf_ab_.Reset();
1947 
1948     // We have now fully processed the stream_buf_ input chunk (by moving the
1949     // remaining part into buf, which will be accounted for below).
1950     DecrementCurrentSessionMemory(stream_buf_.len);
1951   }
1952 
1953   // Shrink to the actual amount of used data.
1954   buf.base = Realloc(buf.base, nread);
1955   IncrementCurrentSessionMemory(nread);
1956 
1957   // Remember the current buffer, so that OnDataChunkReceived knows the
1958   // offset of a DATA frame's data into the socket read buffer.
1959   stream_buf_ = uv_buf_init(buf.base, nread);
1960 
1961   // Verify that currently: There is memory allocated into which
1962   // the data has been read, and that memory buffer is at least as large
1963   // as the amount of data we have read, but we have not yet made an
1964   // ArrayBuffer out of it.
1965   CHECK_LE(static_cast<size_t>(nread), stream_buf_.len);
1966 
1967   Isolate* isolate = env()->isolate();
1968 
1969   // Store this so we can create an ArrayBuffer for read data from it.
1970   // DATA frames will be emitted as slices of that ArrayBuffer to avoid having
1971   // to copy memory.
1972   stream_buf_allocation_ = buf;
1973 
1974   ssize_t ret = ConsumeHTTP2Data();
1975 
1976   if (UNLIKELY(ret < 0)) {
1977     Debug(this, "fatal error receiving data: %d", ret);
1978     Local<Value> arg = Integer::New(isolate, ret);
1979     MakeCallback(env()->error_string(), 1, &arg);
1980     return;
1981   }
1982 
1983   MaybeStopReading();
1984 }
1985 
HasWritesOnSocketForStream(Http2Stream * stream)1986 bool Http2Session::HasWritesOnSocketForStream(Http2Stream* stream) {
1987   for (const nghttp2_stream_write& wr : outgoing_buffers_) {
1988     if (wr.req_wrap != nullptr && wr.req_wrap->stream() == stream)
1989       return true;
1990   }
1991   return false;
1992 }
1993 
1994 // Every Http2Session session is tightly bound to a single i/o StreamBase
1995 // (typically a net.Socket or tls.TLSSocket). The lifecycle of the two is
1996 // tightly coupled with all data transfer between the two happening at the
1997 // C++ layer via the StreamBase API.
Consume(Local<External> external)1998 void Http2Session::Consume(Local<External> external) {
1999   StreamBase* stream = static_cast<StreamBase*>(external->Value());
2000   stream->PushStreamListener(this);
2001   Debug(this, "i/o stream consumed");
2002 }
2003 
New(Http2Session * session,int32_t id,nghttp2_headers_category category,int options)2004 Http2Stream* Http2Stream::New(Http2Session* session,
2005                               int32_t id,
2006                               nghttp2_headers_category category,
2007                               int options) {
2008   Local<Object> obj;
2009   if (!session->env()
2010            ->http2stream_constructor_template()
2011            ->NewInstance(session->env()->context())
2012            .ToLocal(&obj)) {
2013     return nullptr;
2014   }
2015   return new Http2Stream(session, obj, id, category, options);
2016 }
2017 
Http2Stream(Http2Session * session,Local<Object> obj,int32_t id,nghttp2_headers_category category,int options)2018 Http2Stream::Http2Stream(Http2Session* session,
2019                          Local<Object> obj,
2020                          int32_t id,
2021                          nghttp2_headers_category category,
2022                          int options)
2023     : AsyncWrap(session->env(), obj, AsyncWrap::PROVIDER_HTTP2STREAM),
2024       StreamBase(session->env()),
2025       session_(session),
2026       id_(id),
2027       current_headers_category_(category) {
2028   MakeWeak();
2029   statistics_.start_time = uv_hrtime();
2030 
2031   // Limit the number of header pairs
2032   max_header_pairs_ = session->GetMaxHeaderPairs();
2033   if (max_header_pairs_ == 0) {
2034     max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
2035   }
2036   current_headers_.reserve(std::min(max_header_pairs_, 12u));
2037 
2038   // Limit the number of header octets
2039   max_header_length_ =
2040       std::min(
2041         nghttp2_session_get_local_settings(
2042           session->session(),
2043           NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE),
2044       MAX_MAX_HEADER_LIST_SIZE);
2045 
2046   if (options & STREAM_OPTION_GET_TRAILERS)
2047     flags_ |= NGHTTP2_STREAM_FLAG_TRAILERS;
2048 
2049   PushStreamListener(&stream_listener_);
2050 
2051   if (options & STREAM_OPTION_EMPTY_PAYLOAD)
2052     Shutdown();
2053   session->AddStream(this);
2054 }
2055 
2056 
~Http2Stream()2057 Http2Stream::~Http2Stream() {
2058   for (nghttp2_header& header : current_headers_) {
2059     nghttp2_rcbuf_decref(header.name);
2060     nghttp2_rcbuf_decref(header.value);
2061   }
2062 
2063   if (session_ == nullptr)
2064     return;
2065   Debug(this, "tearing down stream");
2066   session_->DecrementCurrentSessionMemory(current_headers_length_);
2067   session_->RemoveStream(this);
2068   session_ = nullptr;
2069 }
2070 
diagnostic_name() const2071 std::string Http2Stream::diagnostic_name() const {
2072   return "HttpStream " + std::to_string(id()) + " (" +
2073       std::to_string(static_cast<int64_t>(get_async_id())) + ") [" +
2074       session()->diagnostic_name() + "]";
2075 }
2076 
2077 // Notify the Http2Stream that a new block of HEADERS is being processed.
StartHeaders(nghttp2_headers_category category)2078 void Http2Stream::StartHeaders(nghttp2_headers_category category) {
2079   Debug(this, "starting headers, category: %d", id_, category);
2080   CHECK(!this->IsDestroyed());
2081   session_->DecrementCurrentSessionMemory(current_headers_length_);
2082   current_headers_length_ = 0;
2083   current_headers_.clear();
2084   current_headers_category_ = category;
2085 }
2086 
2087 
operator *()2088 nghttp2_stream* Http2Stream::operator*() {
2089   return nghttp2_session_find_stream(**session_, id_);
2090 }
2091 
Close(int32_t code)2092 void Http2Stream::Close(int32_t code) {
2093   CHECK(!this->IsDestroyed());
2094   flags_ |= NGHTTP2_STREAM_FLAG_CLOSED;
2095   code_ = code;
2096   Debug(this, "closed with code %d", code);
2097 }
2098 
DoShutdown(ShutdownWrap * req_wrap)2099 int Http2Stream::DoShutdown(ShutdownWrap* req_wrap) {
2100   if (IsDestroyed())
2101     return UV_EPIPE;
2102 
2103   {
2104     Http2Scope h2scope(this);
2105     flags_ |= NGHTTP2_STREAM_FLAG_SHUT;
2106     CHECK_NE(nghttp2_session_resume_data(**session_, id_),
2107              NGHTTP2_ERR_NOMEM);
2108     Debug(this, "writable side shutdown");
2109   }
2110   return 1;
2111 }
2112 
2113 // Destroy the Http2Stream and render it unusable. Actual resources for the
2114 // Stream will not be freed until the next tick of the Node.js event loop
2115 // using the SetImmediate queue.
Destroy()2116 void Http2Stream::Destroy() {
2117   // Do nothing if this stream instance is already destroyed
2118   if (IsDestroyed())
2119     return;
2120   if (session_->HasPendingRstStream(id_))
2121     FlushRstStream();
2122   flags_ |= NGHTTP2_STREAM_FLAG_DESTROYED;
2123 
2124   Debug(this, "destroying stream");
2125 
2126   // Wait until the start of the next loop to delete because there
2127   // may still be some pending operations queued for this stream.
2128   env()->SetImmediate([](Environment* env, void* data) {
2129     Http2Stream* stream = static_cast<Http2Stream*>(data);
2130     // Free any remaining outgoing data chunks here. This should be done
2131     // here because it's possible for destroy to have been called while
2132     // we still have queued outbound writes.
2133     while (!stream->queue_.empty()) {
2134       nghttp2_stream_write& head = stream->queue_.front();
2135       if (head.req_wrap != nullptr)
2136         head.req_wrap->Done(UV_ECANCELED);
2137       stream->queue_.pop();
2138     }
2139 
2140     // We can destroy the stream now if there are no writes for it
2141     // already on the socket. Otherwise, we'll wait for the garbage collector
2142     // to take care of cleaning up.
2143     if (stream->session() == nullptr ||
2144         !stream->session()->HasWritesOnSocketForStream(stream))
2145       delete stream;
2146   }, this, this->object());
2147 
2148   statistics_.end_time = uv_hrtime();
2149   session_->statistics_.stream_average_duration =
2150       ((statistics_.end_time - statistics_.start_time) /
2151           session_->statistics_.stream_count) / 1e6;
2152   EmitStatistics();
2153 }
2154 
2155 
2156 // Initiates a response on the Http2Stream using data provided via the
2157 // StreamBase Streams API.
SubmitResponse(nghttp2_nv * nva,size_t len,int options)2158 int Http2Stream::SubmitResponse(nghttp2_nv* nva, size_t len, int options) {
2159   CHECK(!this->IsDestroyed());
2160   Http2Scope h2scope(this);
2161   Debug(this, "submitting response");
2162   if (options & STREAM_OPTION_GET_TRAILERS)
2163     flags_ |= NGHTTP2_STREAM_FLAG_TRAILERS;
2164 
2165   if (!IsWritable())
2166     options |= STREAM_OPTION_EMPTY_PAYLOAD;
2167 
2168   Http2Stream::Provider::Stream prov(this, options);
2169   int ret = nghttp2_submit_response(**session_, id_, nva, len, *prov);
2170   CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2171   return ret;
2172 }
2173 
2174 
2175 // Submit informational headers for a stream.
SubmitInfo(nghttp2_nv * nva,size_t len)2176 int Http2Stream::SubmitInfo(nghttp2_nv* nva, size_t len) {
2177   CHECK(!this->IsDestroyed());
2178   Http2Scope h2scope(this);
2179   Debug(this, "sending %d informational headers", len);
2180   int ret = nghttp2_submit_headers(**session_,
2181                                    NGHTTP2_FLAG_NONE,
2182                                    id_, nullptr,
2183                                    nva, len, nullptr);
2184   CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2185   return ret;
2186 }
2187 
OnTrailers()2188 void Http2Stream::OnTrailers() {
2189   Debug(this, "let javascript know we are ready for trailers");
2190   CHECK(!this->IsDestroyed());
2191   Isolate* isolate = env()->isolate();
2192   HandleScope scope(isolate);
2193   Local<Context> context = env()->context();
2194   Context::Scope context_scope(context);
2195   flags_ &= ~NGHTTP2_STREAM_FLAG_TRAILERS;
2196   MakeCallback(env()->ontrailers_string(), 0, nullptr);
2197 }
2198 
2199 // Submit informational headers for a stream.
SubmitTrailers(nghttp2_nv * nva,size_t len)2200 int Http2Stream::SubmitTrailers(nghttp2_nv* nva, size_t len) {
2201   CHECK(!this->IsDestroyed());
2202   Http2Scope h2scope(this);
2203   Debug(this, "sending %d trailers", len);
2204   int ret;
2205   // Sending an empty trailers frame poses problems in Safari, Edge & IE.
2206   // Instead we can just send an empty data frame with NGHTTP2_FLAG_END_STREAM
2207   // to indicate that the stream is ready to be closed.
2208   if (len == 0) {
2209     Http2Stream::Provider::Stream prov(this, 0);
2210     ret = nghttp2_submit_data(**session_, NGHTTP2_FLAG_END_STREAM, id_, *prov);
2211   } else {
2212     ret = nghttp2_submit_trailer(**session_, id_, nva, len);
2213   }
2214   CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2215   return ret;
2216 }
2217 
2218 // Submit a PRIORITY frame to the connected peer.
SubmitPriority(nghttp2_priority_spec * prispec,bool silent)2219 int Http2Stream::SubmitPriority(nghttp2_priority_spec* prispec,
2220                                 bool silent) {
2221   CHECK(!this->IsDestroyed());
2222   Http2Scope h2scope(this);
2223   Debug(this, "sending priority spec");
2224   int ret = silent ?
2225       nghttp2_session_change_stream_priority(**session_,
2226                                              id_, prispec) :
2227       nghttp2_submit_priority(**session_,
2228                               NGHTTP2_FLAG_NONE,
2229                               id_, prispec);
2230   CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2231   return ret;
2232 }
2233 
2234 // Closes the Http2Stream by submitting an RST_STREAM frame to the connected
2235 // peer.
SubmitRstStream(const uint32_t code)2236 void Http2Stream::SubmitRstStream(const uint32_t code) {
2237   CHECK(!this->IsDestroyed());
2238   code_ = code;
2239   // If possible, force a purge of any currently pending data here to make sure
2240   // it is sent before closing the stream. If it returns non-zero then we need
2241   // to wait until the current write finishes and try again to avoid nghttp2
2242   // behaviour where it prioritizes RstStream over everything else.
2243   if (session_->SendPendingData() != 0) {
2244     session_->AddPendingRstStream(id_);
2245     return;
2246   }
2247 
2248   FlushRstStream();
2249 }
2250 
FlushRstStream()2251 void Http2Stream::FlushRstStream() {
2252   if (IsDestroyed())
2253     return;
2254   Http2Scope h2scope(this);
2255   CHECK_EQ(nghttp2_submit_rst_stream(**session_, NGHTTP2_FLAG_NONE,
2256                                      id_, code_), 0);
2257 }
2258 
2259 
2260 // Submit a push promise and create the associated Http2Stream if successful.
SubmitPushPromise(nghttp2_nv * nva,size_t len,int32_t * ret,int options)2261 Http2Stream* Http2Stream::SubmitPushPromise(nghttp2_nv* nva,
2262                                             size_t len,
2263                                             int32_t* ret,
2264                                             int options) {
2265   CHECK(!this->IsDestroyed());
2266   Http2Scope h2scope(this);
2267   Debug(this, "sending push promise");
2268   *ret = nghttp2_submit_push_promise(**session_, NGHTTP2_FLAG_NONE,
2269                                      id_, nva, len, nullptr);
2270   CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
2271   Http2Stream* stream = nullptr;
2272   if (*ret > 0)
2273     stream = Http2Stream::New(session_, *ret, NGHTTP2_HCAT_HEADERS, options);
2274 
2275   return stream;
2276 }
2277 
2278 // Switch the StreamBase into flowing mode to begin pushing chunks of data
2279 // out to JS land.
ReadStart()2280 int Http2Stream::ReadStart() {
2281   Http2Scope h2scope(this);
2282   CHECK(!this->IsDestroyed());
2283   flags_ |= NGHTTP2_STREAM_FLAG_READ_START;
2284   flags_ &= ~NGHTTP2_STREAM_FLAG_READ_PAUSED;
2285 
2286   Debug(this, "reading starting");
2287 
2288   // Tell nghttp2 about our consumption of the data that was handed
2289   // off to JS land.
2290   nghttp2_session_consume_stream(**session_,
2291                                  id_,
2292                                  inbound_consumed_data_while_paused_);
2293   inbound_consumed_data_while_paused_ = 0;
2294 
2295   return 0;
2296 }
2297 
2298 // Switch the StreamBase into paused mode.
ReadStop()2299 int Http2Stream::ReadStop() {
2300   CHECK(!this->IsDestroyed());
2301   if (!IsReading())
2302     return 0;
2303   flags_ |= NGHTTP2_STREAM_FLAG_READ_PAUSED;
2304   Debug(this, "reading stopped");
2305   return 0;
2306 }
2307 
2308 // The Http2Stream class is a subclass of StreamBase. The DoWrite method
2309 // receives outbound chunks of data to send as outbound DATA frames. These
2310 // are queued in an internal linked list of uv_buf_t structs that are sent
2311 // when nghttp2 is ready to serialize the data frame.
2312 //
2313 // Queue the given set of uv_but_t handles for writing to an
2314 // nghttp2_stream. The WriteWrap's Done callback will be invoked once the
2315 // chunks of data have been flushed to the underlying nghttp2_session.
2316 // Note that this does *not* mean that the data has been flushed
2317 // to the socket yet.
DoWrite(std::unique_ptr<WriteWrap> & req_wrap,uv_buf_t * bufs,size_t nbufs,uv_stream_t * send_handle)2318 int Http2Stream::DoWrite(std::unique_ptr<WriteWrap>& req_wrap,
2319                          uv_buf_t* bufs,
2320                          size_t nbufs,
2321                          uv_stream_t* send_handle) {
2322   CHECK_NULL(send_handle);
2323   Http2Scope h2scope(this);
2324   if (!IsWritable() || IsDestroyed()) {
2325     req_wrap->Done(UV_EOF);
2326     return 0;
2327   }
2328   Debug(this, "queuing %d buffers to send", id_, nbufs);
2329   for (size_t i = 0; i < nbufs; ++i) {
2330     // Store the req_wrap on the last write info in the queue, so that it is
2331     // only marked as finished once all buffers associated with it are finished.
2332     queue_.emplace(nghttp2_stream_write {
2333       i == nbufs - 1 ? req_wrap.get() : nullptr,
2334       bufs[i]
2335     });
2336     IncrementAvailableOutboundLength(bufs[i].len);
2337   }
2338   CHECK_NE(nghttp2_session_resume_data(**session_, id_), NGHTTP2_ERR_NOMEM);
2339   return 0;
2340 }
2341 
2342 // Ads a header to the Http2Stream. Note that the header name and value are
2343 // provided using a buffer structure provided by nghttp2 that allows us to
2344 // avoid unnecessary memcpy's. Those buffers are ref counted. The ref count
2345 // is incremented here and are decremented when the header name and values
2346 // are garbage collected later.
AddHeader(nghttp2_rcbuf * name,nghttp2_rcbuf * value,uint8_t flags)2347 bool Http2Stream::AddHeader(nghttp2_rcbuf* name,
2348                             nghttp2_rcbuf* value,
2349                             uint8_t flags) {
2350   CHECK(!this->IsDestroyed());
2351   if (this->statistics_.first_header == 0)
2352     this->statistics_.first_header = uv_hrtime();
2353   size_t name_len = nghttp2_rcbuf_get_buf(name).len;
2354   if (name_len == 0 && !IsReverted(SECURITY_REVERT_CVE_2019_9516)) {
2355     return true;  // Ignore headers with empty names.
2356   }
2357   size_t value_len = nghttp2_rcbuf_get_buf(value).len;
2358   size_t length = name_len + value_len + 32;
2359   // A header can only be added if we have not exceeded the maximum number
2360   // of headers and the session has memory available for it.
2361   if (!session_->IsAvailableSessionMemory(length) ||
2362       current_headers_.size() == max_header_pairs_ ||
2363       current_headers_length_ + length > max_header_length_) {
2364     return false;
2365   }
2366   nghttp2_header header;
2367   header.name = name;
2368   header.value = value;
2369   header.flags = flags;
2370   current_headers_.push_back(header);
2371   nghttp2_rcbuf_incref(name);
2372   nghttp2_rcbuf_incref(value);
2373   current_headers_length_ += length;
2374   session_->IncrementCurrentSessionMemory(length);
2375   return true;
2376 }
2377 
2378 // A Provider is the thing that provides outbound DATA frame data.
Provider(Http2Stream * stream,int options)2379 Http2Stream::Provider::Provider(Http2Stream* stream, int options) {
2380   CHECK(!stream->IsDestroyed());
2381   provider_.source.ptr = stream;
2382   empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
2383 }
2384 
Provider(int options)2385 Http2Stream::Provider::Provider(int options) {
2386   provider_.source.ptr = nullptr;
2387   empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
2388 }
2389 
~Provider()2390 Http2Stream::Provider::~Provider() {
2391   provider_.source.ptr = nullptr;
2392 }
2393 
2394 // The Stream Provider pulls data from a linked list of uv_buf_t structs
2395 // built via the StreamBase API and the Streams js API.
Stream(int options)2396 Http2Stream::Provider::Stream::Stream(int options)
2397     : Http2Stream::Provider(options) {
2398   provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
2399 }
2400 
Stream(Http2Stream * stream,int options)2401 Http2Stream::Provider::Stream::Stream(Http2Stream* stream, int options)
2402     : Http2Stream::Provider(stream, options) {
2403   provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
2404 }
2405 
OnRead(nghttp2_session * handle,int32_t id,uint8_t * buf,size_t length,uint32_t * flags,nghttp2_data_source * source,void * user_data)2406 ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
2407                                               int32_t id,
2408                                               uint8_t* buf,
2409                                               size_t length,
2410                                               uint32_t* flags,
2411                                               nghttp2_data_source* source,
2412                                               void* user_data) {
2413   Http2Session* session = static_cast<Http2Session*>(user_data);
2414   Debug(session, "reading outbound data for stream %d", id);
2415   Http2Stream* stream = GetStream(session, id, source);
2416   if (stream->statistics_.first_byte_sent == 0)
2417     stream->statistics_.first_byte_sent = uv_hrtime();
2418   CHECK_EQ(id, stream->id());
2419 
2420   size_t amount = 0;          // amount of data being sent in this data frame.
2421 
2422   // Remove all empty chunks from the head of the queue.
2423   // This is done here so that .write('', cb) is still a meaningful way to
2424   // find out when the HTTP2 stream wants to consume data, and because the
2425   // StreamBase API allows empty input chunks.
2426   while (!stream->queue_.empty() && stream->queue_.front().buf.len == 0) {
2427     WriteWrap* finished = stream->queue_.front().req_wrap;
2428     stream->queue_.pop();
2429     if (finished != nullptr)
2430       finished->Done(0);
2431   }
2432 
2433   if (!stream->queue_.empty()) {
2434     Debug(session, "stream %d has pending outbound data", id);
2435     amount = std::min(stream->available_outbound_length_, length);
2436     Debug(session, "sending %d bytes for data frame on stream %d", amount, id);
2437     if (amount > 0) {
2438       // Just return the length, let Http2Session::OnSendData take care of
2439       // actually taking the buffers out of the queue.
2440       *flags |= NGHTTP2_DATA_FLAG_NO_COPY;
2441       stream->DecrementAvailableOutboundLength(amount);
2442     }
2443   }
2444 
2445   if (amount == 0 && stream->IsWritable()) {
2446     CHECK(stream->queue_.empty());
2447     Debug(session, "deferring stream %d", id);
2448     stream->EmitWantsWrite(length);
2449     if (stream->available_outbound_length_ > 0 || !stream->IsWritable()) {
2450       // EmitWantsWrite() did something interesting synchronously, restart:
2451       return OnRead(handle, id, buf, length, flags, source, user_data);
2452     }
2453     return NGHTTP2_ERR_DEFERRED;
2454   }
2455 
2456   if (stream->available_outbound_length_ == 0 && !stream->IsWritable()) {
2457     Debug(session, "no more data for stream %d", id);
2458     *flags |= NGHTTP2_DATA_FLAG_EOF;
2459     if (stream->HasTrailers()) {
2460       *flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
2461       stream->OnTrailers();
2462     }
2463   }
2464 
2465   stream->statistics_.sent_bytes += amount;
2466   return amount;
2467 }
2468 
IncrementAvailableOutboundLength(size_t amount)2469 inline void Http2Stream::IncrementAvailableOutboundLength(size_t amount) {
2470   available_outbound_length_ += amount;
2471   session_->IncrementCurrentSessionMemory(amount);
2472 }
2473 
DecrementAvailableOutboundLength(size_t amount)2474 inline void Http2Stream::DecrementAvailableOutboundLength(size_t amount) {
2475   available_outbound_length_ -= amount;
2476   session_->DecrementCurrentSessionMemory(amount);
2477 }
2478 
2479 
2480 // Implementation of the JavaScript API
2481 
2482 // Fetches the string description of a nghttp2 error code and passes that
2483 // back to JS land
HttpErrorString(const FunctionCallbackInfo<Value> & args)2484 void HttpErrorString(const FunctionCallbackInfo<Value>& args) {
2485   Environment* env = Environment::GetCurrent(args);
2486   uint32_t val = args[0]->Uint32Value(env->context()).ToChecked();
2487   args.GetReturnValue().Set(
2488       String::NewFromOneByte(
2489           env->isolate(),
2490           reinterpret_cast<const uint8_t*>(nghttp2_strerror(val)),
2491           NewStringType::kInternalized).ToLocalChecked());
2492 }
2493 
2494 
2495 // Serializes the settings object into a Buffer instance that
2496 // would be suitable, for instance, for creating the Base64
2497 // output for an HTTP2-Settings header field.
PackSettings(const FunctionCallbackInfo<Value> & args)2498 void PackSettings(const FunctionCallbackInfo<Value>& args) {
2499   Environment* env = Environment::GetCurrent(args);
2500   // TODO(addaleax): We should not be creating a full AsyncWrap for this.
2501   Local<Object> obj;
2502   if (!env->http2settings_constructor_template()
2503            ->NewInstance(env->context())
2504            .ToLocal(&obj)) {
2505     return;
2506   }
2507   Http2Session::Http2Settings settings(env, nullptr, obj);
2508   args.GetReturnValue().Set(settings.Pack());
2509 }
2510 
2511 // A TypedArray instance is shared between C++ and JS land to contain the
2512 // default SETTINGS. RefreshDefaultSettings updates that TypedArray with the
2513 // default values.
RefreshDefaultSettings(const FunctionCallbackInfo<Value> & args)2514 void RefreshDefaultSettings(const FunctionCallbackInfo<Value>& args) {
2515   Environment* env = Environment::GetCurrent(args);
2516   Http2Session::Http2Settings::RefreshDefaults(env);
2517 }
2518 
2519 // Sets the next stream ID the Http2Session. If successful, returns true.
SetNextStreamID(const FunctionCallbackInfo<Value> & args)2520 void Http2Session::SetNextStreamID(const FunctionCallbackInfo<Value>& args) {
2521   Environment* env = Environment::GetCurrent(args);
2522   Http2Session* session;
2523   ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2524   int32_t id = args[0]->Int32Value(env->context()).ToChecked();
2525   if (nghttp2_session_set_next_stream_id(**session, id) < 0) {
2526     Debug(session, "failed to set next stream id to %d", id);
2527     return args.GetReturnValue().Set(false);
2528   }
2529   args.GetReturnValue().Set(true);
2530   Debug(session, "set next stream id to %d", id);
2531 }
2532 
2533 // A TypedArray instance is shared between C++ and JS land to contain the
2534 // SETTINGS (either remote or local). RefreshSettings updates the current
2535 // values established for each of the settings so those can be read in JS land.
2536 template <get_setting fn>
RefreshSettings(const FunctionCallbackInfo<Value> & args)2537 void Http2Session::RefreshSettings(const FunctionCallbackInfo<Value>& args) {
2538   Environment* env = Environment::GetCurrent(args);
2539   Http2Session* session;
2540   ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2541   Http2Settings::Update(env, session, fn);
2542   Debug(session, "settings refreshed for session");
2543 }
2544 
2545 // A TypedArray instance is shared between C++ and JS land to contain state
2546 // information of the current Http2Session. This updates the values in the
2547 // TypedArray so those can be read in JS land.
RefreshState(const FunctionCallbackInfo<Value> & args)2548 void Http2Session::RefreshState(const FunctionCallbackInfo<Value>& args) {
2549   Environment* env = Environment::GetCurrent(args);
2550   Http2Session* session;
2551   ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2552   Debug(session, "refreshing state");
2553 
2554   AliasedBuffer<double, Float64Array>& buffer =
2555       env->http2_state()->session_state_buffer;
2556 
2557   nghttp2_session* s = **session;
2558 
2559   buffer[IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE] =
2560       nghttp2_session_get_effective_local_window_size(s);
2561   buffer[IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH] =
2562       nghttp2_session_get_effective_recv_data_length(s);
2563   buffer[IDX_SESSION_STATE_NEXT_STREAM_ID] =
2564       nghttp2_session_get_next_stream_id(s);
2565   buffer[IDX_SESSION_STATE_LOCAL_WINDOW_SIZE] =
2566       nghttp2_session_get_local_window_size(s);
2567   buffer[IDX_SESSION_STATE_LAST_PROC_STREAM_ID] =
2568       nghttp2_session_get_last_proc_stream_id(s);
2569   buffer[IDX_SESSION_STATE_REMOTE_WINDOW_SIZE] =
2570       nghttp2_session_get_remote_window_size(s);
2571   buffer[IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE] =
2572       nghttp2_session_get_outbound_queue_size(s);
2573   buffer[IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE] =
2574       nghttp2_session_get_hd_deflate_dynamic_table_size(s);
2575   buffer[IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE] =
2576       nghttp2_session_get_hd_inflate_dynamic_table_size(s);
2577 }
2578 
2579 
2580 // Constructor for new Http2Session instances.
New(const FunctionCallbackInfo<Value> & args)2581 void Http2Session::New(const FunctionCallbackInfo<Value>& args) {
2582   Environment* env = Environment::GetCurrent(args);
2583   CHECK(args.IsConstructCall());
2584   int val = args[0]->IntegerValue(env->context()).ToChecked();
2585   nghttp2_session_type type = static_cast<nghttp2_session_type>(val);
2586   Http2Session* session = new Http2Session(env, args.This(), type);
2587   session->get_async_id();  // avoid compiler warning
2588   Debug(session, "session created");
2589 }
2590 
2591 
2592 // Binds the Http2Session with a StreamBase used for i/o
Consume(const FunctionCallbackInfo<Value> & args)2593 void Http2Session::Consume(const FunctionCallbackInfo<Value>& args) {
2594   Http2Session* session;
2595   ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2596   CHECK(args[0]->IsExternal());
2597   session->Consume(args[0].As<External>());
2598 }
2599 
2600 // Destroys the Http2Session instance and renders it unusable
Destroy(const FunctionCallbackInfo<Value> & args)2601 void Http2Session::Destroy(const FunctionCallbackInfo<Value>& args) {
2602   Http2Session* session;
2603   ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2604   Debug(session, "destroying session");
2605   Environment* env = Environment::GetCurrent(args);
2606   Local<Context> context = env->context();
2607 
2608   uint32_t code = args[0]->Uint32Value(context).ToChecked();
2609   bool socketDestroyed = args[1]->BooleanValue(context).ToChecked();
2610 
2611   session->Close(code, socketDestroyed);
2612 }
2613 
2614 // Submits a new request on the Http2Session and returns either an error code
2615 // or the Http2Stream object.
Request(const FunctionCallbackInfo<Value> & args)2616 void Http2Session::Request(const FunctionCallbackInfo<Value>& args) {
2617   Http2Session* session;
2618   ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2619   Environment* env = session->env();
2620   Local<Context> context = env->context();
2621   Isolate* isolate = env->isolate();
2622 
2623   Local<Array> headers = args[0].As<Array>();
2624   int options = args[1]->IntegerValue(context).ToChecked();
2625   Http2Priority priority(env, args[2], args[3], args[4]);
2626 
2627   Headers list(isolate, context, headers);
2628 
2629   Debug(session, "request submitted");
2630 
2631   int32_t ret = 0;
2632   Http2Stream* stream =
2633       session->Http2Session::SubmitRequest(*priority, *list, list.length(),
2634                                            &ret, options);
2635 
2636   if (ret <= 0 || stream == nullptr) {
2637     Debug(session, "could not submit request: %s", nghttp2_strerror(ret));
2638     return args.GetReturnValue().Set(ret);
2639   }
2640 
2641   Debug(session, "request submitted, new stream id %d", stream->id());
2642   args.GetReturnValue().Set(stream->object());
2643 }
2644 
2645 // Submits a GOAWAY frame to signal that the Http2Session is in the process
2646 // of shutting down. Note that this function does not actually alter the
2647 // state of the Http2Session, it's simply a notification.
Goaway(uint32_t code,int32_t lastStreamID,uint8_t * data,size_t len)2648 void Http2Session::Goaway(uint32_t code,
2649                           int32_t lastStreamID,
2650                           uint8_t* data,
2651                           size_t len) {
2652   if (IsDestroyed())
2653     return;
2654 
2655   Http2Scope h2scope(this);
2656   // the last proc stream id is the most recently created Http2Stream.
2657   if (lastStreamID <= 0)
2658     lastStreamID = nghttp2_session_get_last_proc_stream_id(session_);
2659   Debug(this, "submitting goaway");
2660   nghttp2_submit_goaway(session_, NGHTTP2_FLAG_NONE,
2661                         lastStreamID, code, data, len);
2662 }
2663 
2664 // Submits a GOAWAY frame to signal that the Http2Session is in the process
2665 // of shutting down. The opaque data argument is an optional TypedArray that
2666 // can be used to send debugging data to the connected peer.
Goaway(const FunctionCallbackInfo<Value> & args)2667 void Http2Session::Goaway(const FunctionCallbackInfo<Value>& args) {
2668   Environment* env = Environment::GetCurrent(args);
2669   Local<Context> context = env->context();
2670   Http2Session* session;
2671   ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2672 
2673   uint32_t code = args[0]->Uint32Value(context).ToChecked();
2674   int32_t lastStreamID = args[1]->Int32Value(context).ToChecked();
2675   Local<Value> opaqueData = args[2];
2676   uint8_t* data = nullptr;
2677   size_t length = 0;
2678 
2679   if (Buffer::HasInstance(opaqueData)) {
2680     data = reinterpret_cast<uint8_t*>(Buffer::Data(opaqueData));
2681     length = Buffer::Length(opaqueData);
2682   }
2683 
2684   session->Goaway(code, lastStreamID, data, length);
2685 }
2686 
2687 // Update accounting of data chunks. This is used primarily to manage timeout
2688 // logic when using the FD Provider.
UpdateChunksSent(const FunctionCallbackInfo<Value> & args)2689 void Http2Session::UpdateChunksSent(const FunctionCallbackInfo<Value>& args) {
2690   Environment* env = Environment::GetCurrent(args);
2691   Isolate* isolate = env->isolate();
2692   HandleScope scope(isolate);
2693   Http2Session* session;
2694   ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2695 
2696   uint32_t length = session->chunks_sent_since_last_write_;
2697 
2698   session->object()->Set(env->context(),
2699                          env->chunks_sent_since_last_write_string(),
2700                          Integer::NewFromUnsigned(isolate, length)).FromJust();
2701 
2702   args.GetReturnValue().Set(length);
2703 }
2704 
2705 // Submits an RST_STREAM frame effectively closing the Http2Stream. Note that
2706 // this *WILL* alter the state of the stream, causing the OnStreamClose
2707 // callback to the triggered.
RstStream(const FunctionCallbackInfo<Value> & args)2708 void Http2Stream::RstStream(const FunctionCallbackInfo<Value>& args) {
2709   Environment* env = Environment::GetCurrent(args);
2710   Local<Context> context = env->context();
2711   Http2Stream* stream;
2712   ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2713   uint32_t code = args[0]->Uint32Value(context).ToChecked();
2714   Debug(stream, "sending rst_stream with code %d", code);
2715   stream->SubmitRstStream(code);
2716 }
2717 
2718 // Initiates a response on the Http2Stream using the StreamBase API to provide
2719 // outbound DATA frames.
Respond(const FunctionCallbackInfo<Value> & args)2720 void Http2Stream::Respond(const FunctionCallbackInfo<Value>& args) {
2721   Environment* env = Environment::GetCurrent(args);
2722   Local<Context> context = env->context();
2723   Isolate* isolate = env->isolate();
2724   Http2Stream* stream;
2725   ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2726 
2727   Local<Array> headers = args[0].As<Array>();
2728   int options = args[1]->IntegerValue(context).ToChecked();
2729 
2730   Headers list(isolate, context, headers);
2731 
2732   args.GetReturnValue().Set(
2733       stream->SubmitResponse(*list, list.length(), options));
2734   Debug(stream, "response submitted");
2735 }
2736 
2737 
2738 // Submits informational headers on the Http2Stream
Info(const FunctionCallbackInfo<Value> & args)2739 void Http2Stream::Info(const FunctionCallbackInfo<Value>& args) {
2740   Environment* env = Environment::GetCurrent(args);
2741   Local<Context> context = env->context();
2742   Isolate* isolate = env->isolate();
2743   Http2Stream* stream;
2744   ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2745 
2746   Local<Array> headers = args[0].As<Array>();
2747 
2748   Headers list(isolate, context, headers);
2749   args.GetReturnValue().Set(stream->SubmitInfo(*list, list.length()));
2750   Debug(stream, "%d informational headers sent", list.length());
2751 }
2752 
2753 // Submits trailing headers on the Http2Stream
Trailers(const FunctionCallbackInfo<Value> & args)2754 void Http2Stream::Trailers(const FunctionCallbackInfo<Value>& args) {
2755   Environment* env = Environment::GetCurrent(args);
2756   Local<Context> context = env->context();
2757   Isolate* isolate = env->isolate();
2758   Http2Stream* stream;
2759   ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2760 
2761   Local<Array> headers = args[0].As<Array>();
2762 
2763   Headers list(isolate, context, headers);
2764   args.GetReturnValue().Set(stream->SubmitTrailers(*list, list.length()));
2765   Debug(stream, "%d trailing headers sent", list.length());
2766 }
2767 
2768 // Grab the numeric id of the Http2Stream
GetID(const FunctionCallbackInfo<Value> & args)2769 void Http2Stream::GetID(const FunctionCallbackInfo<Value>& args) {
2770   Http2Stream* stream;
2771   ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2772   args.GetReturnValue().Set(stream->id());
2773 }
2774 
2775 // Destroy the Http2Stream, rendering it no longer usable
Destroy(const FunctionCallbackInfo<Value> & args)2776 void Http2Stream::Destroy(const FunctionCallbackInfo<Value>& args) {
2777   Http2Stream* stream;
2778   ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2779   Debug(stream, "destroying stream");
2780   stream->Destroy();
2781 }
2782 
2783 // Prompt the Http2Stream to begin sending data to the JS land.
FlushData(const FunctionCallbackInfo<Value> & args)2784 void Http2Stream::FlushData(const FunctionCallbackInfo<Value>& args) {
2785   Http2Stream* stream;
2786   ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2787   stream->ReadStart();
2788   Debug(stream, "data flushed to js");
2789 }
2790 
2791 // Initiate a Push Promise and create the associated Http2Stream
PushPromise(const FunctionCallbackInfo<Value> & args)2792 void Http2Stream::PushPromise(const FunctionCallbackInfo<Value>& args) {
2793   Environment* env = Environment::GetCurrent(args);
2794   Local<Context> context = env->context();
2795   Isolate* isolate = env->isolate();
2796   Http2Stream* parent;
2797   ASSIGN_OR_RETURN_UNWRAP(&parent, args.Holder());
2798 
2799   Local<Array> headers = args[0].As<Array>();
2800   int options = args[1]->IntegerValue(context).ToChecked();
2801 
2802   Headers list(isolate, context, headers);
2803 
2804   Debug(parent, "creating push promise");
2805 
2806   int32_t ret = 0;
2807   Http2Stream* stream = parent->SubmitPushPromise(*list, list.length(),
2808                                                   &ret, options);
2809   if (ret <= 0 || stream == nullptr) {
2810     Debug(parent, "failed to create push stream: %d", ret);
2811     return args.GetReturnValue().Set(ret);
2812   }
2813   Debug(parent, "push stream %d created", stream->id());
2814   args.GetReturnValue().Set(stream->object());
2815 }
2816 
2817 // Send a PRIORITY frame
Priority(const FunctionCallbackInfo<Value> & args)2818 void Http2Stream::Priority(const FunctionCallbackInfo<Value>& args) {
2819   Environment* env = Environment::GetCurrent(args);
2820   Local<Context> context = env->context();
2821   Http2Stream* stream;
2822   ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2823 
2824   Http2Priority priority(env, args[0], args[1], args[2]);
2825   bool silent = args[3]->BooleanValue(context).ToChecked();
2826 
2827   CHECK_EQ(stream->SubmitPriority(*priority, silent), 0);
2828   Debug(stream, "priority submitted");
2829 }
2830 
2831 // A TypedArray shared by C++ and JS land is used to communicate state
2832 // information about the Http2Stream. This updates the values in that
2833 // TypedArray so that the state can be read by JS.
RefreshState(const FunctionCallbackInfo<Value> & args)2834 void Http2Stream::RefreshState(const FunctionCallbackInfo<Value>& args) {
2835   Environment* env = Environment::GetCurrent(args);
2836   Http2Stream* stream;
2837   ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2838 
2839   Debug(stream, "refreshing state");
2840 
2841   AliasedBuffer<double, Float64Array>& buffer =
2842       env->http2_state()->stream_state_buffer;
2843 
2844   nghttp2_stream* str = **stream;
2845   nghttp2_session* s = **(stream->session());
2846 
2847   if (str == nullptr) {
2848     buffer[IDX_STREAM_STATE] = NGHTTP2_STREAM_STATE_IDLE;
2849     buffer[IDX_STREAM_STATE_WEIGHT] =
2850         buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2851         buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2852         buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2853         buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] = 0;
2854   } else {
2855     buffer[IDX_STREAM_STATE] =
2856         nghttp2_stream_get_state(str);
2857     buffer[IDX_STREAM_STATE_WEIGHT] =
2858         nghttp2_stream_get_weight(str);
2859     buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2860         nghttp2_stream_get_sum_dependency_weight(str);
2861     buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2862         nghttp2_session_get_stream_local_close(s, stream->id());
2863     buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2864         nghttp2_session_get_stream_remote_close(s, stream->id());
2865     buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] =
2866         nghttp2_session_get_stream_local_window_size(s, stream->id());
2867   }
2868 }
2869 
AltSvc(int32_t id,uint8_t * origin,size_t origin_len,uint8_t * value,size_t value_len)2870 void Http2Session::AltSvc(int32_t id,
2871                           uint8_t* origin,
2872                           size_t origin_len,
2873                           uint8_t* value,
2874                           size_t value_len) {
2875   Http2Scope h2scope(this);
2876   CHECK_EQ(nghttp2_submit_altsvc(session_, NGHTTP2_FLAG_NONE, id,
2877                                  origin, origin_len, value, value_len), 0);
2878 }
2879 
Origin(nghttp2_origin_entry * ov,size_t count)2880 void Http2Session::Origin(nghttp2_origin_entry* ov, size_t count) {
2881   Http2Scope h2scope(this);
2882   CHECK_EQ(nghttp2_submit_origin(session_, NGHTTP2_FLAG_NONE, ov, count), 0);
2883 }
2884 
2885 // Submits an AltSvc frame to be sent to the connected peer.
AltSvc(const FunctionCallbackInfo<Value> & args)2886 void Http2Session::AltSvc(const FunctionCallbackInfo<Value>& args) {
2887   Environment* env = Environment::GetCurrent(args);
2888   Http2Session* session;
2889   ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2890 
2891   int32_t id = args[0]->Int32Value(env->context()).ToChecked();
2892 
2893   // origin and value are both required to be ASCII, handle them as such.
2894   Local<String> origin_str = args[1]->ToString(env->context()).ToLocalChecked();
2895   Local<String> value_str = args[2]->ToString(env->context()).ToLocalChecked();
2896 
2897   size_t origin_len = origin_str->Length();
2898   size_t value_len = value_str->Length();
2899 
2900   CHECK_LE(origin_len + value_len, 16382);  // Max permitted for ALTSVC
2901   // Verify that origin len != 0 if stream id == 0, or
2902   // that origin len == 0 if stream id != 0
2903   CHECK((origin_len != 0 && id == 0) || (origin_len == 0 && id != 0));
2904 
2905   MaybeStackBuffer<uint8_t> origin(origin_len);
2906   MaybeStackBuffer<uint8_t> value(value_len);
2907   origin_str->WriteOneByte(env->isolate(), *origin);
2908   value_str->WriteOneByte(env->isolate(), *value);
2909 
2910   session->AltSvc(id, *origin, origin_len, *value, value_len);
2911 }
2912 
Origin(const FunctionCallbackInfo<Value> & args)2913 void Http2Session::Origin(const FunctionCallbackInfo<Value>& args) {
2914   Environment* env = Environment::GetCurrent(args);
2915   Local<Context> context = env->context();
2916   Http2Session* session;
2917   ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2918 
2919   Local<String> origin_string = args[0].As<String>();
2920   int count = args[1]->IntegerValue(context).ToChecked();
2921 
2922 
2923   Origins origins(env->isolate(),
2924                   env->context(),
2925                   origin_string,
2926                   count);
2927 
2928   session->Origin(*origins, origins.length());
2929 }
2930 
2931 // Submits a PING frame to be sent to the connected peer.
Ping(const FunctionCallbackInfo<Value> & args)2932 void Http2Session::Ping(const FunctionCallbackInfo<Value>& args) {
2933   Environment* env = Environment::GetCurrent(args);
2934   Http2Session* session;
2935   ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2936 
2937   // A PING frame may have exactly 8 bytes of payload data. If not provided,
2938   // then the current hrtime will be used as the payload.
2939   uint8_t* payload = nullptr;
2940   if (Buffer::HasInstance(args[0])) {
2941     payload = reinterpret_cast<uint8_t*>(Buffer::Data(args[0]));
2942     CHECK_EQ(Buffer::Length(args[0]), 8);
2943   }
2944 
2945   Local<Object> obj;
2946   if (!env->http2ping_constructor_template()
2947            ->NewInstance(env->context())
2948            .ToLocal(&obj)) {
2949     return;
2950   }
2951   if (obj->Set(env->context(), env->ondone_string(), args[1]).IsNothing())
2952     return;
2953   Http2Session::Http2Ping* ping = new Http2Ping(session, obj);
2954 
2955   // To prevent abuse, we strictly limit the number of unacknowledged PING
2956   // frames that may be sent at any given time. This is configurable in the
2957   // Options when creating a Http2Session.
2958   if (!session->AddPing(ping)) {
2959     ping->Done(false);
2960     return args.GetReturnValue().Set(false);
2961   }
2962 
2963   // The Ping itself is an Async resource. When the acknowledgement is received,
2964   // the callback will be invoked and a notification sent out to JS land. The
2965   // notification will include the duration of the ping, allowing the round
2966   // trip to be measured.
2967   ping->Send(payload);
2968   args.GetReturnValue().Set(true);
2969 }
2970 
2971 // Submits a SETTINGS frame for the Http2Session
Settings(const FunctionCallbackInfo<Value> & args)2972 void Http2Session::Settings(const FunctionCallbackInfo<Value>& args) {
2973   Environment* env = Environment::GetCurrent(args);
2974   Http2Session* session;
2975   ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2976 
2977   Local<Object> obj;
2978   if (!env->http2settings_constructor_template()
2979            ->NewInstance(env->context())
2980            .ToLocal(&obj)) {
2981     return;
2982   }
2983   if (obj->Set(env->context(), env->ondone_string(), args[0]).IsNothing())
2984     return;
2985 
2986   Http2Session::Http2Settings* settings =
2987       new Http2Settings(session->env(), session, obj, 0);
2988   if (!session->AddSettings(settings)) {
2989     settings->Done(false);
2990     return args.GetReturnValue().Set(false);
2991   }
2992 
2993   settings->Send();
2994   args.GetReturnValue().Set(true);
2995 }
2996 
2997 
PopPing()2998 Http2Session::Http2Ping* Http2Session::PopPing() {
2999   Http2Ping* ping = nullptr;
3000   if (!outstanding_pings_.empty()) {
3001     ping = outstanding_pings_.front();
3002     outstanding_pings_.pop();
3003     DecrementCurrentSessionMemory(sizeof(*ping));
3004   }
3005   return ping;
3006 }
3007 
AddPing(Http2Session::Http2Ping * ping)3008 bool Http2Session::AddPing(Http2Session::Http2Ping* ping) {
3009   if (outstanding_pings_.size() == max_outstanding_pings_)
3010     return false;
3011   outstanding_pings_.push(ping);
3012   IncrementCurrentSessionMemory(sizeof(*ping));
3013   return true;
3014 }
3015 
PopSettings()3016 Http2Session::Http2Settings* Http2Session::PopSettings() {
3017   Http2Settings* settings = nullptr;
3018   if (!outstanding_settings_.empty()) {
3019     settings = outstanding_settings_.front();
3020     outstanding_settings_.pop();
3021     DecrementCurrentSessionMemory(sizeof(*settings));
3022   }
3023   return settings;
3024 }
3025 
AddSettings(Http2Session::Http2Settings * settings)3026 bool Http2Session::AddSettings(Http2Session::Http2Settings* settings) {
3027   if (outstanding_settings_.size() == max_outstanding_settings_)
3028     return false;
3029   outstanding_settings_.push(settings);
3030   IncrementCurrentSessionMemory(sizeof(*settings));
3031   return true;
3032 }
3033 
Http2Ping(Http2Session * session,Local<Object> obj)3034 Http2Session::Http2Ping::Http2Ping(Http2Session* session, Local<Object> obj)
3035     : AsyncWrap(session->env(), obj, AsyncWrap::PROVIDER_HTTP2PING),
3036       session_(session),
3037       startTime_(uv_hrtime()) {}
3038 
Send(uint8_t * payload)3039 void Http2Session::Http2Ping::Send(uint8_t* payload) {
3040   uint8_t data[8];
3041   if (payload == nullptr) {
3042     memcpy(&data, &startTime_, arraysize(data));
3043     payload = data;
3044   }
3045   Http2Scope h2scope(session_);
3046   CHECK_EQ(nghttp2_submit_ping(**session_, NGHTTP2_FLAG_NONE, payload), 0);
3047 }
3048 
Done(bool ack,const uint8_t * payload)3049 void Http2Session::Http2Ping::Done(bool ack, const uint8_t* payload) {
3050   session_->statistics_.ping_rtt = uv_hrtime() - startTime_;
3051   double duration = session_->statistics_.ping_rtt / 1e6;
3052 
3053   Local<Value> buf = Undefined(env()->isolate());
3054   if (payload != nullptr) {
3055     buf = Buffer::Copy(env()->isolate(),
3056                        reinterpret_cast<const char*>(payload),
3057                        8).ToLocalChecked();
3058   }
3059 
3060   Local<Value> argv[3] = {
3061     Boolean::New(env()->isolate(), ack),
3062     Number::New(env()->isolate(), duration),
3063     buf
3064   };
3065   MakeCallback(env()->ondone_string(), arraysize(argv), argv);
3066   delete this;
3067 }
3068 
3069 
MemoryInfo(MemoryTracker * tracker) const3070 void nghttp2_stream_write::MemoryInfo(MemoryTracker* tracker) const {
3071   if (req_wrap != nullptr)
3072     tracker->TrackField("req_wrap", req_wrap->GetAsyncWrap());
3073   tracker->TrackField("buf", buf);
3074 }
3075 
3076 
MemoryInfo(MemoryTracker * tracker) const3077 void nghttp2_header::MemoryInfo(MemoryTracker* tracker) const {
3078   tracker->TrackFieldWithSize("name", nghttp2_rcbuf_get_buf(name).len);
3079   tracker->TrackFieldWithSize("value", nghttp2_rcbuf_get_buf(value).len);
3080 }
3081 
3082 
3083 // Set up the process.binding('http2') binding.
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)3084 void Initialize(Local<Object> target,
3085                 Local<Value> unused,
3086                 Local<Context> context,
3087                 void* priv) {
3088   Environment* env = Environment::GetCurrent(context);
3089   Isolate* isolate = env->isolate();
3090   HandleScope scope(isolate);
3091 
3092   std::unique_ptr<Http2State> state(new Http2State(isolate));
3093 
3094 #define SET_STATE_TYPEDARRAY(name, field)             \
3095   target->Set(context,                                \
3096               FIXED_ONE_BYTE_STRING(isolate, (name)), \
3097               (field)).FromJust()
3098 
3099   // Initialize the buffer used for padding callbacks
3100   SET_STATE_TYPEDARRAY(
3101     "paddingBuffer", state->padding_buffer.GetJSArray());
3102   // Initialize the buffer used to store the session state
3103   SET_STATE_TYPEDARRAY(
3104     "sessionState", state->session_state_buffer.GetJSArray());
3105   // Initialize the buffer used to store the stream state
3106   SET_STATE_TYPEDARRAY(
3107     "streamState", state->stream_state_buffer.GetJSArray());
3108   SET_STATE_TYPEDARRAY(
3109     "settingsBuffer", state->settings_buffer.GetJSArray());
3110   SET_STATE_TYPEDARRAY(
3111     "optionsBuffer", state->options_buffer.GetJSArray());
3112   SET_STATE_TYPEDARRAY(
3113     "streamStats", state->stream_stats_buffer.GetJSArray());
3114   SET_STATE_TYPEDARRAY(
3115     "sessionStats", state->session_stats_buffer.GetJSArray());
3116 #undef SET_STATE_TYPEDARRAY
3117 
3118   env->set_http2_state(std::move(state));
3119 
3120   NODE_DEFINE_CONSTANT(target, PADDING_BUF_FRAME_LENGTH);
3121   NODE_DEFINE_CONSTANT(target, PADDING_BUF_MAX_PAYLOAD_LENGTH);
3122   NODE_DEFINE_CONSTANT(target, PADDING_BUF_RETURN_VALUE);
3123 
3124   NODE_DEFINE_CONSTANT(target, kBitfield);
3125   NODE_DEFINE_CONSTANT(target, kSessionPriorityListenerCount);
3126   NODE_DEFINE_CONSTANT(target, kSessionFrameErrorListenerCount);
3127   NODE_DEFINE_CONSTANT(target, kSessionUint8FieldCount);
3128 
3129   NODE_DEFINE_CONSTANT(target, kSessionHasRemoteSettingsListeners);
3130   NODE_DEFINE_CONSTANT(target, kSessionRemoteSettingsIsUpToDate);
3131   NODE_DEFINE_CONSTANT(target, kSessionHasPingListeners);
3132   NODE_DEFINE_CONSTANT(target, kSessionHasAltsvcListeners);
3133 
3134   // Method to fetch the nghttp2 string description of an nghttp2 error code
3135   env->SetMethod(target, "nghttp2ErrorString", HttpErrorString);
3136 
3137   Local<String> http2SessionClassName =
3138     FIXED_ONE_BYTE_STRING(isolate, "Http2Session");
3139 
3140   Local<FunctionTemplate> ping = FunctionTemplate::New(env->isolate());
3141   ping->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Ping"));
3142   ping->Inherit(AsyncWrap::GetConstructorTemplate(env));
3143   Local<ObjectTemplate> pingt = ping->InstanceTemplate();
3144   pingt->SetInternalFieldCount(1);
3145   env->set_http2ping_constructor_template(pingt);
3146 
3147   Local<FunctionTemplate> setting = FunctionTemplate::New(env->isolate());
3148   setting->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Setting"));
3149   setting->Inherit(AsyncWrap::GetConstructorTemplate(env));
3150   Local<ObjectTemplate> settingt = setting->InstanceTemplate();
3151   settingt->SetInternalFieldCount(1);
3152   env->set_http2settings_constructor_template(settingt);
3153 
3154   Local<FunctionTemplate> stream = FunctionTemplate::New(env->isolate());
3155   stream->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Stream"));
3156   env->SetProtoMethod(stream, "id", Http2Stream::GetID);
3157   env->SetProtoMethod(stream, "destroy", Http2Stream::Destroy);
3158   env->SetProtoMethod(stream, "flushData", Http2Stream::FlushData);
3159   env->SetProtoMethod(stream, "priority", Http2Stream::Priority);
3160   env->SetProtoMethod(stream, "pushPromise", Http2Stream::PushPromise);
3161   env->SetProtoMethod(stream, "info", Http2Stream::Info);
3162   env->SetProtoMethod(stream, "trailers", Http2Stream::Trailers);
3163   env->SetProtoMethod(stream, "respond", Http2Stream::Respond);
3164   env->SetProtoMethod(stream, "rstStream", Http2Stream::RstStream);
3165   env->SetProtoMethod(stream, "refreshState", Http2Stream::RefreshState);
3166   stream->Inherit(AsyncWrap::GetConstructorTemplate(env));
3167   StreamBase::AddMethods<Http2Stream>(env, stream);
3168   Local<ObjectTemplate> streamt = stream->InstanceTemplate();
3169   streamt->SetInternalFieldCount(1);
3170   env->set_http2stream_constructor_template(streamt);
3171   target->Set(context,
3172               FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Stream"),
3173               stream->GetFunction(env->context()).ToLocalChecked()).FromJust();
3174 
3175   Local<FunctionTemplate> session =
3176       env->NewFunctionTemplate(Http2Session::New);
3177   session->SetClassName(http2SessionClassName);
3178   session->InstanceTemplate()->SetInternalFieldCount(1);
3179   session->Inherit(AsyncWrap::GetConstructorTemplate(env));
3180   env->SetProtoMethod(session, "origin", Http2Session::Origin);
3181   env->SetProtoMethod(session, "altsvc", Http2Session::AltSvc);
3182   env->SetProtoMethod(session, "ping", Http2Session::Ping);
3183   env->SetProtoMethod(session, "consume", Http2Session::Consume);
3184   env->SetProtoMethod(session, "destroy", Http2Session::Destroy);
3185   env->SetProtoMethod(session, "goaway", Http2Session::Goaway);
3186   env->SetProtoMethod(session, "settings", Http2Session::Settings);
3187   env->SetProtoMethod(session, "request", Http2Session::Request);
3188   env->SetProtoMethod(session, "setNextStreamID",
3189                       Http2Session::SetNextStreamID);
3190   env->SetProtoMethod(session, "updateChunksSent",
3191                       Http2Session::UpdateChunksSent);
3192   env->SetProtoMethod(session, "refreshState", Http2Session::RefreshState);
3193   env->SetProtoMethod(
3194       session, "localSettings",
3195       Http2Session::RefreshSettings<nghttp2_session_get_local_settings>);
3196   env->SetProtoMethod(
3197       session, "remoteSettings",
3198       Http2Session::RefreshSettings<nghttp2_session_get_remote_settings>);
3199   target->Set(context,
3200               http2SessionClassName,
3201               session->GetFunction(env->context()).ToLocalChecked()).FromJust();
3202 
3203   Local<Object> constants = Object::New(isolate);
3204   Local<Array> name_for_error_code = Array::New(isolate);
3205 
3206 #define NODE_NGHTTP2_ERROR_CODES(V)                       \
3207   V(NGHTTP2_SESSION_SERVER);                              \
3208   V(NGHTTP2_SESSION_CLIENT);                              \
3209   V(NGHTTP2_STREAM_STATE_IDLE);                           \
3210   V(NGHTTP2_STREAM_STATE_OPEN);                           \
3211   V(NGHTTP2_STREAM_STATE_RESERVED_LOCAL);                 \
3212   V(NGHTTP2_STREAM_STATE_RESERVED_REMOTE);                \
3213   V(NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL);              \
3214   V(NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE);             \
3215   V(NGHTTP2_STREAM_STATE_CLOSED);                         \
3216   V(NGHTTP2_NO_ERROR);                                    \
3217   V(NGHTTP2_PROTOCOL_ERROR);                              \
3218   V(NGHTTP2_INTERNAL_ERROR);                              \
3219   V(NGHTTP2_FLOW_CONTROL_ERROR);                          \
3220   V(NGHTTP2_SETTINGS_TIMEOUT);                            \
3221   V(NGHTTP2_STREAM_CLOSED);                               \
3222   V(NGHTTP2_FRAME_SIZE_ERROR);                            \
3223   V(NGHTTP2_REFUSED_STREAM);                              \
3224   V(NGHTTP2_CANCEL);                                      \
3225   V(NGHTTP2_COMPRESSION_ERROR);                           \
3226   V(NGHTTP2_CONNECT_ERROR);                               \
3227   V(NGHTTP2_ENHANCE_YOUR_CALM);                           \
3228   V(NGHTTP2_INADEQUATE_SECURITY);                         \
3229   V(NGHTTP2_HTTP_1_1_REQUIRED);                           \
3230 
3231 #define V(name)                                                         \
3232   NODE_DEFINE_CONSTANT(constants, name);                                \
3233   name_for_error_code->Set(static_cast<int>(name),                      \
3234                            FIXED_ONE_BYTE_STRING(isolate, #name));
3235   NODE_NGHTTP2_ERROR_CODES(V)
3236 #undef V
3237 
3238   NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_REQUEST);
3239   NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_RESPONSE);
3240   NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_PUSH_RESPONSE);
3241   NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_HEADERS);
3242   NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_NV_FLAG_NONE);
3243   NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_NV_FLAG_NO_INDEX);
3244   NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_DEFERRED);
3245   NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE);
3246   NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_INVALID_ARGUMENT);
3247   NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_STREAM_CLOSED);
3248   NODE_DEFINE_CONSTANT(constants, NGHTTP2_ERR_FRAME_SIZE_ERROR);
3249 
3250   NODE_DEFINE_HIDDEN_CONSTANT(constants, STREAM_OPTION_EMPTY_PAYLOAD);
3251   NODE_DEFINE_HIDDEN_CONSTANT(constants, STREAM_OPTION_GET_TRAILERS);
3252 
3253   NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_NONE);
3254   NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_END_STREAM);
3255   NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_END_HEADERS);
3256   NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_ACK);
3257   NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_PADDED);
3258   NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_PRIORITY);
3259 
3260   NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_HEADER_TABLE_SIZE);
3261   NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_ENABLE_PUSH);
3262   NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE);
3263   NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_MAX_FRAME_SIZE);
3264   NODE_DEFINE_CONSTANT(constants, MAX_MAX_FRAME_SIZE);
3265   NODE_DEFINE_CONSTANT(constants, MIN_MAX_FRAME_SIZE);
3266   NODE_DEFINE_CONSTANT(constants, MAX_INITIAL_WINDOW_SIZE);
3267   NODE_DEFINE_CONSTANT(constants, NGHTTP2_DEFAULT_WEIGHT);
3268 
3269   NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE);
3270   NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_ENABLE_PUSH);
3271   NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
3272   NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
3273   NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_MAX_FRAME_SIZE);
3274   NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE);
3275   NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL);
3276 
3277   NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_NONE);
3278   NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_ALIGNED);
3279   NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_MAX);
3280   NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_CALLBACK);
3281 
3282 #define STRING_CONSTANT(NAME, VALUE)                                          \
3283   NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_HEADER_" # NAME, VALUE);
3284 HTTP_KNOWN_HEADERS(STRING_CONSTANT)
3285 #undef STRING_CONSTANT
3286 
3287 #define STRING_CONSTANT(NAME, VALUE)                                          \
3288   NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_METHOD_" # NAME, VALUE);
3289 HTTP_KNOWN_METHODS(STRING_CONSTANT)
3290 #undef STRING_CONSTANT
3291 
3292 #define V(name, _) NODE_DEFINE_CONSTANT(constants, HTTP_STATUS_##name);
3293 HTTP_STATUS_CODES(V)
3294 #undef V
3295 
3296   env->SetMethod(target, "refreshDefaultSettings", RefreshDefaultSettings);
3297   env->SetMethod(target, "packSettings", PackSettings);
3298 
3299   target->Set(context,
3300               env->constants_string(),
3301               constants).FromJust();
3302   target->Set(context,
3303               FIXED_ONE_BYTE_STRING(isolate, "nameForErrorCode"),
3304               name_for_error_code).FromJust();
3305 }
3306 }  // namespace http2
3307 }  // namespace node
3308 
3309 NODE_BUILTIN_MODULE_CONTEXT_AWARE(http2, node::http2::Initialize)
3310