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