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