GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_http2.cc Lines: 1501 1581 94.9 %
Date: 2021-10-07 04:12:46 Branches: 603 842 71.6 %

Line Branch Exec Source
1
#include "aliased_buffer.h"
2
#include "allocated_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_http2.h"
10
#include "node_http_common-inl.h"
11
#include "node_mem-inl.h"
12
#include "node_perf.h"
13
#include "node_revert.h"
14
#include "stream_base-inl.h"
15
#include "util-inl.h"
16
17
#include <algorithm>
18
19
namespace node {
20
21
using v8::Array;
22
using v8::ArrayBuffer;
23
using v8::ArrayBufferView;
24
using v8::Boolean;
25
using v8::Context;
26
using v8::EscapableHandleScope;
27
using v8::Function;
28
using v8::FunctionCallbackInfo;
29
using v8::FunctionTemplate;
30
using v8::HandleScope;
31
using v8::Integer;
32
using v8::Isolate;
33
using v8::Local;
34
using v8::MaybeLocal;
35
using v8::NewStringType;
36
using v8::Number;
37
using v8::Object;
38
using v8::ObjectTemplate;
39
using v8::String;
40
using v8::Uint8Array;
41
using v8::Undefined;
42
using v8::Value;
43
44
namespace http2 {
45
46
namespace {
47
48
const char zero_bytes_256[256] = {};
49
50
24296
bool HasHttp2Observer(Environment* env) {
51
24296
  AliasedUint32Array& observers = env->performance_state()->observers;
52
24296
  return observers[performance::NODE_PERFORMANCE_ENTRY_TYPE_HTTP2] != 0;
53
}
54
55
}  // anonymous namespace
56
57
// These configure the callbacks required by nghttp2 itself. There are
58
// two sets of callback functions, one that is used if a padding callback
59
// is set, and other that does not include the padding callback.
60
const Http2Session::Callbacks Http2Session::callback_struct_saved[2] = {
61
    Callbacks(false),
62
    Callbacks(true)};
63
64
// The Http2Scope object is used to queue a write to the i/o stream. It is
65
// used whenever any action is take on the underlying nghttp2 API that may
66
// push data into nghttp2 outbound data queue.
67
//
68
// For example:
69
//
70
// Http2Scope h2scope(session);
71
// nghttp2_submit_ping(session->session(), ... );
72
//
73
// When the Http2Scope passes out of scope and is deconstructed, it will
74
// call Http2Session::MaybeScheduleWrite().
75
63394
Http2Scope::Http2Scope(Http2Stream* stream) : Http2Scope(stream->session()) {}
76
77
107665
Http2Scope::Http2Scope(Http2Session* session) : session_(session) {
78
107665
  if (!session_) return;
79
80
  // If there is another scope further below on the stack, or
81
  // a write is already scheduled, there's nothing to do.
82

107665
  if (session_->is_in_scope() || session_->is_write_scheduled()) {
83
75009
    session_.reset();
84
75009
    return;
85
  }
86
32656
  session_->set_in_scope();
87
}
88
89
107665
Http2Scope::~Http2Scope() {
90
107665
  if (!session_) return;
91
32656
  session_->set_in_scope(false);
92
32656
  if (!session_->is_write_scheduled())
93
32656
    session_->MaybeScheduleWrite();
94
107665
}
95
96
// The Http2Options object is used during the construction of Http2Session
97
// instances to configure an appropriate nghttp2_options struct. The class
98
// uses a single TypedArray instance that is shared with the JavaScript side
99
// to more efficiently pass values back and forth.
100
668
Http2Options::Http2Options(Http2State* http2_state, SessionType type) {
101
  nghttp2_option* option;
102
668
  CHECK_EQ(nghttp2_option_new(&option), 0);
103
668
  CHECK_NOT_NULL(option);
104
668
  options_.reset(option);
105
106
  // Make sure closed connections aren't kept around, taking up memory.
107
  // Note that this breaks the priority tree, which we don't use.
108
668
  nghttp2_option_set_no_closed_streams(option, 1);
109
110
  // We manually handle flow control within a session in order to
111
  // implement backpressure -- that is, we only send WINDOW_UPDATE
112
  // frames to the remote peer as data is actually consumed by user
113
  // code. This ensures that the flow of data over the connection
114
  // does not move too quickly and limits the amount of data we
115
  // are required to buffer.
116
668
  nghttp2_option_set_no_auto_window_update(option, 1);
117
118
  // Enable built in support for receiving ALTSVC and ORIGIN frames (but
119
  // only on client side sessions
120
668
  if (type == NGHTTP2_SESSION_CLIENT) {
121
326
    nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ALTSVC);
122
326
    nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ORIGIN);
123
  }
124
125
668
  AliasedUint32Array& buffer = http2_state->options_buffer;
126
668
  uint32_t flags = buffer[IDX_OPTIONS_FLAGS];
127
128
668
  if (flags & (1 << IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE)) {
129
    nghttp2_option_set_max_deflate_dynamic_table_size(
130
        option,
131
        buffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE]);
132
  }
133
134
668
  if (flags & (1 << IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS)) {
135
1
    nghttp2_option_set_max_reserved_remote_streams(
136
        option,
137
        buffer[IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS]);
138
  }
139
140
668
  if (flags & (1 << IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH)) {
141
1
    nghttp2_option_set_max_send_header_block_length(
142
        option,
143
2
        buffer[IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH]);
144
  }
145
146
  // Recommended default
147
668
  nghttp2_option_set_peer_max_concurrent_streams(option, 100);
148
668
  if (flags & (1 << IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS)) {
149
    nghttp2_option_set_peer_max_concurrent_streams(
150
        option,
151
        buffer[IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS]);
152
  }
153
154
  // The padding strategy sets the mechanism by which we determine how much
155
  // additional frame padding to apply to DATA and HEADERS frames. Currently
156
  // this is set on a per-session basis, but eventually we may switch to
157
  // a per-stream setting, giving users greater control
158
668
  if (flags & (1 << IDX_OPTIONS_PADDING_STRATEGY)) {
159
    PaddingStrategy strategy =
160
        static_cast<PaddingStrategy>(
161
2
            buffer.GetValue(IDX_OPTIONS_PADDING_STRATEGY));
162
2
    set_padding_strategy(strategy);
163
  }
164
165
  // The max header list pairs option controls the maximum number of
166
  // header pairs the session may accept. This is a hard limit.. that is,
167
  // if the remote peer sends more than this amount, the stream will be
168
  // automatically closed with an RST_STREAM.
169
668
  if (flags & (1 << IDX_OPTIONS_MAX_HEADER_LIST_PAIRS))
170
1
    set_max_header_pairs(buffer[IDX_OPTIONS_MAX_HEADER_LIST_PAIRS]);
171
172
  // The HTTP2 specification places no limits on the number of HTTP2
173
  // PING frames that can be sent. In order to prevent PINGS from being
174
  // abused as an attack vector, however, we place a strict upper limit
175
  // on the number of unacknowledged PINGS that can be sent at any given
176
  // time.
177
668
  if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_PINGS))
178
2
    set_max_outstanding_pings(buffer[IDX_OPTIONS_MAX_OUTSTANDING_PINGS]);
179
180
  // The HTTP2 specification places no limits on the number of HTTP2
181
  // SETTINGS frames that can be sent. In order to prevent PINGS from being
182
  // abused as an attack vector, however, we place a strict upper limit
183
  // on the number of unacknowledged SETTINGS that can be sent at any given
184
  // time.
185
668
  if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS))
186
2
    set_max_outstanding_settings(buffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS]);
187
188
  // The HTTP2 specification places no limits on the amount of memory
189
  // that a session can consume. In order to prevent abuse, we place a
190
  // cap on the amount of memory a session can consume at any given time.
191
  // this is a credit based system. Existing streams may cause the limit
192
  // to be temporarily exceeded but once over the limit, new streams cannot
193
  // created.
194
  // Important: The maxSessionMemory option in javascript is expressed in
195
  //            terms of MB increments (i.e. the value 1 == 1 MB)
196
668
  if (flags & (1 << IDX_OPTIONS_MAX_SESSION_MEMORY))
197
8
    set_max_session_memory(buffer[IDX_OPTIONS_MAX_SESSION_MEMORY] * 1000000);
198
199
668
  if (flags & (1 << IDX_OPTIONS_MAX_SETTINGS)) {
200
1
    nghttp2_option_set_max_settings(
201
        option,
202
2
        static_cast<size_t>(buffer[IDX_OPTIONS_MAX_SETTINGS]));
203
  }
204
668
}
205
206
#define GRABSETTING(entries, count, name)                                      \
207
  do {                                                                         \
208
    if (flags & (1 << IDX_SETTINGS_ ## name)) {                                \
209
      uint32_t val = buffer[IDX_SETTINGS_ ## name];                            \
210
      entries[count++] =                                                       \
211
          nghttp2_settings_entry {NGHTTP2_SETTINGS_ ## name, val};             \
212
    } } while (0)
213
214
695
size_t Http2Settings::Init(
215
    Http2State* http2_state,
216
    nghttp2_settings_entry* entries) {
217
695
  AliasedUint32Array& buffer = http2_state->settings_buffer;
218
695
  uint32_t flags = buffer[IDX_SETTINGS_COUNT];
219
220
695
  size_t count = 0;
221
222
#define V(name) GRABSETTING(entries, count, name);
223



695
  HTTP2_SETTINGS(V)
224
#undef V
225
226
695
  return count;
227
}
228
#undef GRABSETTING
229
230
// The Http2Settings class is used to configure a SETTINGS frame that is
231
// to be sent to the connected peer. The settings are set using a TypedArray
232
// that is shared with the JavaScript side.
233
678
Http2Settings::Http2Settings(Http2Session* session,
234
                             Local<Object> obj,
235
                             Local<Function> callback,
236
678
                             uint64_t start_time)
237
    : AsyncWrap(session->env(), obj, PROVIDER_HTTP2SETTINGS),
238
      session_(session),
239
678
      startTime_(start_time) {
240
678
  callback_.Reset(env()->isolate(), callback);
241
678
  count_ = Init(session->http2_state(), entries_);
242
678
}
243
244
542
Local<Function> Http2Settings::callback() const {
245
1084
  return callback_.Get(env()->isolate());
246
}
247
248
void Http2Settings::MemoryInfo(MemoryTracker* tracker) const {
249
  tracker->TrackField("callback", callback_);
250
}
251
252
// Generates a Buffer that contains the serialized payload of a SETTINGS
253
// frame. This can be used, for instance, to create the Base64-encoded
254
// content of an Http2-Settings header field.
255
Local<Value> Http2Settings::Pack() {
256
  return Pack(session_->env(), count_, entries_);
257
}
258
259
17
Local<Value> Http2Settings::Pack(Http2State* state) {
260
  nghttp2_settings_entry entries[IDX_SETTINGS_COUNT];
261
17
  size_t count = Init(state, entries);
262
17
  return Pack(state->env(), count, entries);
263
}
264
265
17
Local<Value> Http2Settings::Pack(
266
    Environment* env,
267
    size_t count,
268
    const nghttp2_settings_entry* entries) {
269
17
  EscapableHandleScope scope(env->isolate());
270
17
  const size_t size = count * 6;
271
17
  AllocatedBuffer buffer = AllocatedBuffer::AllocateManaged(env, size);
272
  ssize_t ret =
273
17
      nghttp2_pack_settings_payload(
274
17
          reinterpret_cast<uint8_t*>(buffer.data()),
275
          size,
276
          entries,
277
          count);
278
17
  Local<Value> buf = Undefined(env->isolate());
279
33
  if (ret >= 0) buf = buffer.ToBuffer().ToLocalChecked();
280
17
  return scope.Escape(buf);
281
}
282
283
// Updates the shared TypedArray with the current remote or local settings for
284
// the session.
285
565
void Http2Settings::Update(Http2Session* session, get_setting fn) {
286
565
  AliasedUint32Array& buffer = session->http2_state()->settings_buffer;
287
288
#define V(name)                                                                \
289
  buffer[IDX_SETTINGS_ ## name] =                                              \
290
      fn(session->session(), NGHTTP2_SETTINGS_ ## name);
291
565
  HTTP2_SETTINGS(V)
292
#undef V
293
565
}
294
295
// Initializes the shared TypedArray with the default settings values.
296
6
void Http2Settings::RefreshDefaults(Http2State* http2_state) {
297
6
  AliasedUint32Array& buffer = http2_state->settings_buffer;
298
6
  uint32_t flags = 0;
299
300
#define V(name)                                                            \
301
  do {                                                                     \
302
    buffer[IDX_SETTINGS_ ## name] = DEFAULT_SETTINGS_ ## name;             \
303
    flags |= 1 << IDX_SETTINGS_ ## name;                                   \
304
  } while (0);
305
6
  HTTP2_SETTINGS(V)
306
#undef V
307
308
6
  buffer[IDX_SETTINGS_COUNT] = flags;
309
6
}
310
311
312
676
void Http2Settings::Send() {
313
1352
  Http2Scope h2scope(session_.get());
314
676
  CHECK_EQ(nghttp2_submit_settings(
315
      session_->session(),
316
      NGHTTP2_FLAG_NONE,
317
      &entries_[0],
318
      count_), 0);
319
676
}
320
321
542
void Http2Settings::Done(bool ack) {
322
542
  uint64_t end = uv_hrtime();
323
542
  double duration = (end - startTime_) / 1e6;
324
325
  Local<Value> argv[] = {
326
542
    ack ? v8::True(env()->isolate()) : v8::False(env()->isolate()),
327
    Number::New(env()->isolate(), duration)
328
1084
  };
329
542
  MakeCallback(callback(), arraysize(argv), argv);
330
542
}
331
332
// The Http2Priority class initializes an appropriate nghttp2_priority_spec
333
// struct used when either creating a stream or updating its priority
334
// settings.
335
11768
Http2Priority::Http2Priority(Environment* env,
336
                             Local<Value> parent,
337
                             Local<Value> weight,
338
11768
                             Local<Value> exclusive) {
339
11768
  Local<Context> context = env->context();
340
23536
  int32_t parent_ = parent->Int32Value(context).ToChecked();
341
23536
  int32_t weight_ = weight->Int32Value(context).ToChecked();
342
11768
  bool exclusive_ = exclusive->IsTrue();
343
  Debug(env, DebugCategory::HTTP2STREAM,
344
        "Http2Priority: parent: %d, weight: %d, exclusive: %s\n",
345
11768
        parent_, weight_, exclusive_ ? "yes" : "no");
346
11768
  nghttp2_priority_spec_init(this, parent_, weight_, exclusive_ ? 1 : 0);
347
11768
}
348
349
350
118
const char* Http2Session::TypeName() const {
351
118
  switch (session_type_) {
352
59
    case NGHTTP2_SESSION_SERVER: return "server";
353
59
    case NGHTTP2_SESSION_CLIENT: return "client";
354
    default:
355
      // This should never happen
356
      ABORT();
357
  }
358
}
359
360
5
Origins::Origins(
361
    Environment* env,
362
    Local<String> origin_string,
363
5
    size_t origin_count)
364
5
    : count_(origin_count) {
365
5
  int origin_string_len = origin_string->Length();
366
5
  if (count_ == 0) {
367
    CHECK_EQ(origin_string_len, 0);
368
    return;
369
  }
370
371
5
  buf_ = AllocatedBuffer::AllocateManaged(
372
      env,
373
      (alignof(nghttp2_origin_entry) - 1) +
374
5
       count_ * sizeof(nghttp2_origin_entry) +
375
5
                              origin_string_len);
376
377
  // Make sure the start address is aligned appropriately for an nghttp2_nv*.
378
5
  char* start = AlignUp(buf_.data(), alignof(nghttp2_origin_entry));
379
5
  char* origin_contents = start + (count_ * sizeof(nghttp2_origin_entry));
380
5
  nghttp2_origin_entry* const nva =
381
      reinterpret_cast<nghttp2_origin_entry*>(start);
382
383
5
  CHECK_LE(origin_contents + origin_string_len, buf_.data() + buf_.size());
384
5
  CHECK_EQ(origin_string->WriteOneByte(
385
               env->isolate(),
386
               reinterpret_cast<uint8_t*>(origin_contents),
387
               0,
388
               origin_string_len,
389
               String::NO_NULL_TERMINATION),
390
           origin_string_len);
391
392
5
  size_t n = 0;
393
  char* p;
394
14
  for (p = origin_contents; p < origin_contents + origin_string_len; n++) {
395
9
    if (n >= count_) {
396
      static uint8_t zero = '\0';
397
      nva[0].origin = &zero;
398
      nva[0].origin_len = 1;
399
      count_ = 1;
400
      return;
401
    }
402
403
9
    nva[n].origin = reinterpret_cast<uint8_t*>(p);
404
9
    nva[n].origin_len = strlen(p);
405
9
    p += nva[n].origin_len + 1;
406
  }
407
}
408
409
// Sets the various callback functions that nghttp2 will use to notify us
410
// about significant events while processing http2 stuff.
411
9864
Http2Session::Callbacks::Callbacks(bool kHasGetPaddingCallback) {
412
  nghttp2_session_callbacks* callbacks_;
413
9864
  CHECK_EQ(nghttp2_session_callbacks_new(&callbacks_), 0);
414
9864
  callbacks.reset(callbacks_);
415
416
9864
  nghttp2_session_callbacks_set_on_begin_headers_callback(
417
    callbacks_, OnBeginHeadersCallback);
418
9864
  nghttp2_session_callbacks_set_on_header_callback2(
419
    callbacks_, OnHeaderCallback);
420
9864
  nghttp2_session_callbacks_set_on_frame_recv_callback(
421
    callbacks_, OnFrameReceive);
422
9864
  nghttp2_session_callbacks_set_on_stream_close_callback(
423
    callbacks_, OnStreamClose);
424
9864
  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
425
    callbacks_, OnDataChunkReceived);
426
9864
  nghttp2_session_callbacks_set_on_frame_not_send_callback(
427
    callbacks_, OnFrameNotSent);
428
9864
  nghttp2_session_callbacks_set_on_invalid_header_callback2(
429
    callbacks_, OnInvalidHeader);
430
9864
  nghttp2_session_callbacks_set_error_callback(
431
    callbacks_, OnNghttpError);
432
9864
  nghttp2_session_callbacks_set_send_data_callback(
433
    callbacks_, OnSendData);
434
9864
  nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
435
    callbacks_, OnInvalidFrame);
436
9864
  nghttp2_session_callbacks_set_on_frame_send_callback(
437
    callbacks_, OnFrameSent);
438
439
9864
  if (kHasGetPaddingCallback) {
440
4932
    nghttp2_session_callbacks_set_select_padding_callback(
441
      callbacks_, OnSelectPadding);
442
  }
443
9864
}
444
445
void Http2Session::StopTrackingRcbuf(nghttp2_rcbuf* buf) {
446
  StopTrackingMemory(buf);
447
}
448
449
202659
void Http2Session::CheckAllocatedSize(size_t previous_size) const {
450
202659
  CHECK_GE(current_nghttp2_memory_, previous_size);
451
202659
}
452
453
102286
void Http2Session::IncreaseAllocatedSize(size_t size) {
454
102286
  current_nghttp2_memory_ += size;
455
102286
}
456
457
125387
void Http2Session::DecreaseAllocatedSize(size_t size) {
458
125387
  current_nghttp2_memory_ -= size;
459
125387
}
460
461
668
Http2Session::Http2Session(Http2State* http2_state,
462
                           Local<Object> wrap,
463
668
                           SessionType type)
464
    : AsyncWrap(http2_state->env(), wrap, AsyncWrap::PROVIDER_HTTP2SESSION),
465
      js_fields_(http2_state->env()->isolate()),
466
      session_type_(type),
467
668
      http2_state_(http2_state) {
468
668
  MakeWeak();
469
668
  statistics_.session_type = type;
470
668
  statistics_.start_time = uv_hrtime();
471
472
  // Capture the configuration options for this session
473
668
  Http2Options opts(http2_state, type);
474
475
668
  max_session_memory_ = opts.max_session_memory();
476
477
668
  uint32_t maxHeaderPairs = opts.max_header_pairs();
478
1336
  max_header_pairs_ =
479
      type == NGHTTP2_SESSION_SERVER
480
342
          ? GetServerMaxHeaderPairs(maxHeaderPairs)
481
326
          : GetClientMaxHeaderPairs(maxHeaderPairs);
482
483
668
  max_outstanding_pings_ = opts.max_outstanding_pings();
484
668
  max_outstanding_settings_ = opts.max_outstanding_settings();
485
486
668
  padding_strategy_ = opts.padding_strategy();
487
488
668
  bool hasGetPaddingCallback =
489
668
      padding_strategy_ != PADDING_STRATEGY_NONE;
490
491
668
  auto fn = type == NGHTTP2_SESSION_SERVER ?
492
      nghttp2_session_server_new3 :
493
      nghttp2_session_client_new3;
494
495
668
  nghttp2_mem alloc_info = MakeAllocator();
496
497
  // This should fail only if the system is out of memory, which
498
  // is going to cause lots of other problems anyway, or if any
499
  // of the options are out of acceptable range, which we should
500
  // be catching before it gets this far. Either way, crash if this
501
  // fails.
502
  nghttp2_session* session;
503

668
  CHECK_EQ(fn(
504
      &session,
505
      callback_struct_saved[hasGetPaddingCallback ? 1 : 0].callbacks.get(),
506
      this,
507
      *opts,
508
      &alloc_info), 0);
509
668
  session_.reset(session);
510
511
668
  outgoing_storage_.reserve(1024);
512
668
  outgoing_buffers_.reserve(32);
513
514
  Local<Uint8Array> uint8_arr =
515
668
      Uint8Array::New(js_fields_.GetArrayBuffer(), 0, kSessionUint8FieldCount);
516
1336
  USE(wrap->Set(env()->context(), env()->fields_string(), uint8_arr));
517
668
}
518
519
4008
Http2Session::~Http2Session() {
520
1336
  CHECK(!is_in_scope());
521
1336
  Debug(this, "freeing nghttp2 session");
522
  // Explicitly reset session_ so the subsequent
523
  // current_nghttp2_memory_ check passes.
524
1336
  session_.reset();
525
1336
  CHECK_EQ(current_nghttp2_memory_, 0);
526
2672
}
527
528
void Http2Session::MemoryInfo(MemoryTracker* tracker) const {
529
  tracker->TrackField("streams", streams_);
530
  tracker->TrackField("outstanding_pings", outstanding_pings_);
531
  tracker->TrackField("outstanding_settings", outstanding_settings_);
532
  tracker->TrackField("outgoing_buffers", outgoing_buffers_);
533
  tracker->TrackFieldWithSize("stream_buf", stream_buf_.len);
534
  tracker->TrackFieldWithSize("outgoing_storage", outgoing_storage_.size());
535
  tracker->TrackFieldWithSize("pending_rst_streams",
536
                              pending_rst_streams_.size() * sizeof(int32_t));
537
  tracker->TrackFieldWithSize("nghttp2_memory", current_nghttp2_memory_);
538
}
539
540
118
std::string Http2Session::diagnostic_name() const {
541
236
  return std::string("Http2Session ") + TypeName() + " (" +
542
354
      std::to_string(static_cast<int64_t>(get_async_id())) + ")";
543
}
544
545
8
MaybeLocal<Object> Http2StreamPerformanceEntryTraits::GetDetails(
546
    Environment* env,
547
    const Http2StreamPerformanceEntry& entry) {
548
8
  Local<Object> obj = Object::New(env->isolate());
549
550
#define SET(name, val)                                                         \
551
  if (!obj->Set(                                                               \
552
          env->context(),                                                      \
553
          env->name(),                                                         \
554
          Number::New(                                                         \
555
            env->isolate(),                                                    \
556
            static_cast<double>(entry.details.val))).IsJust()) {               \
557
    return MaybeLocal<Object>();                                               \
558
  }
559
560
32
  SET(bytes_read_string, received_bytes)
561
32
  SET(bytes_written_string, sent_bytes)
562
32
  SET(id_string, id)
563
#undef SET
564
565
#define SET(name, val)                                                         \
566
  if (!obj->Set(                                                               \
567
          env->context(),                                                      \
568
          env->name(),                                                         \
569
          Number::New(                                                         \
570
              env->isolate(),                                                  \
571
              (entry.details.val - entry.details.start_time) / 1e6))           \
572
                  .IsJust()) {                                                 \
573
    return MaybeLocal<Object>();                                               \
574
  }
575
576
32
  SET(time_to_first_byte_string, first_byte)
577
32
  SET(time_to_first_byte_sent_string, first_byte_sent)
578
32
  SET(time_to_first_header_string, first_header)
579
#undef SET
580
581
8
  return obj;
582
}
583
584
7
MaybeLocal<Object> Http2SessionPerformanceEntryTraits::GetDetails(
585
    Environment* env,
586
    const Http2SessionPerformanceEntry& entry) {
587
7
  Local<Object> obj = Object::New(env->isolate());
588
589
#define SET(name, val)                                                         \
590
  if (!obj->Set(                                                               \
591
          env->context(),                                                      \
592
          env->name(),                                                         \
593
          Number::New(                                                         \
594
            env->isolate(),                                                    \
595
            static_cast<double>(entry.details.val))).IsJust()) {               \
596
    return MaybeLocal<Object>();                                               \
597
  }
598
599
28
  SET(bytes_written_string, data_sent)
600
28
  SET(bytes_read_string, data_received)
601
28
  SET(frames_received_string, frame_count)
602
28
  SET(frames_sent_string, frame_sent)
603
28
  SET(max_concurrent_streams_string, max_concurrent_streams)
604
28
  SET(ping_rtt_string, ping_rtt)
605
28
  SET(stream_average_duration_string, stream_average_duration)
606
28
  SET(stream_count_string, stream_count)
607
608
14
  if (!obj->Set(
609
          env->context(),
610
          env->type_string(),
611
          OneByteString(
612
              env->isolate(),
613
7
              (entry.details.session_type == NGHTTP2_SESSION_SERVER)
614

28
                  ? "server" : "client")).IsJust()) {
615
    return MaybeLocal<Object>();
616
  }
617
618
#undef SET
619
7
  return obj;
620
}
621
622
23632
void Http2Stream::EmitStatistics() {
623
23632
  CHECK_NOT_NULL(session());
624
23632
  if (LIKELY(!HasHttp2Observer(env())))
625
23624
    return;
626
627
8
  double start = statistics_.start_time / 1e6;
628
8
  double duration = (PERFORMANCE_NOW() / 1e6) - start;
629
630
  std::unique_ptr<Http2StreamPerformanceEntry> entry =
631
      std::make_unique<Http2StreamPerformanceEntry>(
632
          "Http2Stream",
633
          start,
634
          duration,
635
8
          statistics_);
636
637
8
  env()->SetImmediate([entry = move(entry)](Environment* env) {
638
8
    if (HasHttp2Observer(env))
639
8
      entry->Notify(env);
640
8
  });
641
}
642
643
649
void Http2Session::EmitStatistics() {
644
649
  if (LIKELY(!HasHttp2Observer(env())))
645
642
    return;
646
647
7
  double start = statistics_.start_time / 1e6;
648
7
  double duration = (PERFORMANCE_NOW() / 1e6) - start;
649
650
  std::unique_ptr<Http2SessionPerformanceEntry> entry =
651
      std::make_unique<Http2SessionPerformanceEntry>(
652
          "Http2Session",
653
          start,
654
          duration,
655
7
          statistics_);
656
657
7
  env()->SetImmediate([entry = std::move(entry)](Environment* env) {
658
7
    if (HasHttp2Observer(env))
659
7
      entry->Notify(env);
660
7
  });
661
}
662
663
// Closes the session and frees the associated resources
664
649
void Http2Session::Close(uint32_t code, bool socket_closed) {
665
649
  Debug(this, "closing session");
666
667
649
  if (is_closing())
668
    return;
669
649
  set_closing();
670
671
  // Stop reading on the i/o stream
672
649
  if (stream_ != nullptr) {
673
636
    set_reading_stopped();
674
636
    stream_->ReadStop();
675
  }
676
677
  // If the socket is not closed, then attempt to send a closing GOAWAY
678
  // frame. There is no guarantee that this GOAWAY will be received by
679
  // the peer but the HTTP/2 spec recommends sending it anyway. We'll
680
  // make a best effort.
681
649
  if (!socket_closed) {
682
605
    Debug(this, "terminating session with code %d", code);
683
605
    CHECK_EQ(nghttp2_session_terminate_session(session_.get(), code), 0);
684
605
    SendPendingData();
685
44
  } else if (stream_ != nullptr) {
686
31
    stream_->RemoveStreamListener(this);
687
  }
688
689
649
  set_destroyed();
690
691
  // If we are writing we will get to make the callback in OnStreamAfterWrite.
692
649
  if (!is_write_in_progress()) {
693
605
    Debug(this, "make done session callback");
694
1210
    HandleScope scope(env()->isolate());
695
605
    MakeCallback(env()->ondone_string(), 0, nullptr);
696
  }
697
698
  // If there are outstanding pings, those will need to be canceled, do
699
  // so on the next iteration of the event loop to avoid calling out into
700
  // javascript since this may be called during garbage collection.
701
650
  while (BaseObjectPtr<Http2Ping> ping = PopPing()) {
702
1
    ping->DetachFromSession();
703
2
    env()->SetImmediate(
704
1
        [ping = std::move(ping)](Environment* env) {
705
1
          ping->Done(false);
706
1
        });
707
1
  }
708
709
649
  statistics_.end_time = uv_hrtime();
710
649
  EmitStatistics();
711
}
712
713
// Locates an existing known stream by ID. nghttp2 has a similar method
714
// but this is faster and does not fail if the stream is not found.
715
245954
BaseObjectPtr<Http2Stream> Http2Session::FindStream(int32_t id) {
716
245954
  auto s = streams_.find(id);
717
245954
  return s != streams_.end() ? s->second : BaseObjectPtr<Http2Stream>();
718
}
719
720
11972
bool Http2Session::CanAddStream() {
721
  uint32_t maxConcurrentStreams =
722
11972
      nghttp2_session_get_local_settings(
723
          session_.get(), NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
724
  size_t maxSize =
725
11972
      std::min(streams_.max_size(), static_cast<size_t>(maxConcurrentStreams));
726
  // We can add a new stream so long as we are less than the current
727
  // maximum on concurrent streams and there's enough available memory
728

23944
  return streams_.size() < maxSize &&
729
23944
         has_available_session_memory(sizeof(Http2Stream));
730
}
731
732
23741
void Http2Session::AddStream(Http2Stream* stream) {
733
23741
  CHECK_GE(++statistics_.stream_count, 0);
734
23741
  streams_[stream->id()] = BaseObjectPtr<Http2Stream>(stream);
735
23741
  size_t size = streams_.size();
736
23741
  if (size > statistics_.max_concurrent_streams)
737
1424
    statistics_.max_concurrent_streams = size;
738
23741
  IncrementCurrentSessionMemory(sizeof(*stream));
739
23741
}
740
741
742
23632
BaseObjectPtr<Http2Stream> Http2Session::RemoveStream(int32_t id) {
743
23632
  BaseObjectPtr<Http2Stream> stream;
744
23632
  if (streams_.empty())
745
    return stream;
746
23632
  stream = FindStream(id);
747
23632
  if (stream) {
748
23632
    streams_.erase(id);
749
23632
    DecrementCurrentSessionMemory(sizeof(*stream));
750
  }
751
23632
  return stream;
752
}
753
754
// Used as one of the Padding Strategy functions. Will attempt to ensure
755
// that the total frame size, including header bytes, are 8-byte aligned.
756
// If maxPayloadLen is smaller than the number of bytes necessary to align,
757
// will return maxPayloadLen instead.
758
3
ssize_t Http2Session::OnDWordAlignedPadding(size_t frameLen,
759
                                            size_t maxPayloadLen) {
760
3
  size_t r = (frameLen + 9) % 8;
761
3
  if (r == 0) return frameLen;  // If already a multiple of 8, return.
762
763
3
  size_t pad = frameLen + (8 - r);
764
765
  // If maxPayloadLen happens to be less than the calculated pad length,
766
  // use the max instead, even tho this means the frame will not be
767
  // aligned.
768
3
  pad = std::min(maxPayloadLen, pad);
769
3
  Debug(this, "using frame size padding: %d", pad);
770
3
  return pad;
771
}
772
773
// Used as one of the Padding Strategy functions. Uses the maximum amount
774
// of padding allowed for the current frame.
775
ssize_t Http2Session::OnMaxFrameSizePadding(size_t frameLen,
776
                                            size_t maxPayloadLen) {
777
  Debug(this, "using max frame size padding: %d", maxPayloadLen);
778
  return maxPayloadLen;
779
}
780
781
// Write data received from the i/o stream to the underlying nghttp2_session.
782
// On each call to nghttp2_session_mem_recv, nghttp2 will begin calling the
783
// various callback functions. Each of these will typically result in a call
784
// out to JavaScript so this particular function is rather hot and can be
785
// quite expensive. This is a potential performance optimization target later.
786
31479
void Http2Session::ConsumeHTTP2Data() {
787
31479
  CHECK_NOT_NULL(stream_buf_.base);
788
31479
  CHECK_LE(stream_buf_offset_, stream_buf_.len);
789
31479
  size_t read_len = stream_buf_.len - stream_buf_offset_;
790
791
  // multiple side effects.
792
31479
  Debug(this, "receiving %d bytes [wants data? %d]",
793
        read_len,
794
31479
        nghttp2_session_want_read(session_.get()));
795
31479
  set_receive_paused(false);
796
31479
  custom_recv_error_code_ = nullptr;
797
  ssize_t ret =
798
31479
    nghttp2_session_mem_recv(session_.get(),
799
31479
                             reinterpret_cast<uint8_t*>(stream_buf_.base) +
800
31479
                                 stream_buf_offset_,
801
31479
                             read_len);
802
31479
  CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
803

31479
  CHECK_IMPLIES(custom_recv_error_code_ != nullptr, ret < 0);
804
805
31479
  if (is_receive_paused()) {
806
567
    CHECK(is_reading_stopped());
807
808
567
    CHECK_GT(ret, 0);
809
567
    CHECK_LE(static_cast<size_t>(ret), read_len);
810
811
    // Mark the remainder of the data as available for later consumption.
812
    // Even if all bytes were received, a paused stream may delay the
813
    // nghttp2_on_frame_recv_callback which may have an END_STREAM flag.
814
567
    stream_buf_offset_ += ret;
815
567
    goto done;
816
  }
817
818
  // We are done processing the current input chunk.
819
30912
  DecrementCurrentSessionMemory(stream_buf_.len);
820
30912
  stream_buf_offset_ = 0;
821
30912
  stream_buf_ab_.Reset();
822
30912
  stream_buf_allocation_.clear();
823
30912
  stream_buf_ = uv_buf_init(nullptr, 0);
824
825
  // Send any data that was queued up while processing the received data.
826

30912
  if (ret >= 0 && !is_destroyed()) {
827
30376
    SendPendingData();
828
  }
829
830
536
done:
831
31479
  if (UNLIKELY(ret < 0)) {
832
7
    Isolate* isolate = env()->isolate();
833
7
    Debug(this,
834
        "fatal error receiving data: %d (%s)",
835
        ret,
836
7
        custom_recv_error_code_ != nullptr ?
837
            custom_recv_error_code_ : "(no custom error code)");
838
    Local<Value> args[] = {
839
      Integer::New(isolate, static_cast<int32_t>(ret)),
840
      Null(isolate)
841
14
    };
842
7
    if (custom_recv_error_code_ != nullptr) {
843
3
      args[1] = String::NewFromUtf8(
844
          isolate,
845
          custom_recv_error_code_,
846
3
          NewStringType::kInternalized).ToLocalChecked();
847
    }
848
    MakeCallback(
849
        env()->http2session_on_error_function(),
850
7
        arraysize(args),
851
7
        args);
852
  }
853
31479
}
854
855
856
143086
int32_t GetFrameID(const nghttp2_frame* frame) {
857
  // If this is a push promise, we want to grab the id of the promised stream
858
143086
  return (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
859
      frame->push_promise.promised_stream_id :
860
143086
      frame->hd.stream_id;
861
}
862
863
864
// Called by nghttp2 at the start of receiving a HEADERS frame. We use this
865
// callback to determine if a new stream is being created or if we are simply
866
// adding a new block of headers to an existing stream. The header pairs
867
// themselves are set in the OnHeaderCallback
868
23656
int Http2Session::OnBeginHeadersCallback(nghttp2_session* handle,
869
                                         const nghttp2_frame* frame,
870
                                         void* user_data) {
871
23656
  Http2Session* session = static_cast<Http2Session*>(user_data);
872
23656
  int32_t id = GetFrameID(frame);
873
  Debug(session, "beginning headers for stream %d", id);
874
875
47312
  BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
876
  // The common case is that we're creating a new stream. The less likely
877
  // case is that we're receiving a set of trailers
878
23656
  if (LIKELY(!stream)) {
879
23943
    if (UNLIKELY(!session->CanAddStream() ||
880
                 Http2Stream::New(session, id, frame->headers.cat) ==
881

23943
                     nullptr)) {
882
2
      if (session->rejected_stream_count_++ >
883
1
          session->js_fields_->max_rejected_streams)
884
        return NGHTTP2_ERR_CALLBACK_FAILURE;
885
      // Too many concurrent streams being opened
886
1
      nghttp2_submit_rst_stream(
887
          session->session(),
888
          NGHTTP2_FLAG_NONE,
889
          id,
890
          NGHTTP2_ENHANCE_YOUR_CALM);
891
1
      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
892
    }
893
894
11971
    session->rejected_stream_count_ = 0;
895
11684
  } else if (!stream->is_destroyed()) {
896
11684
    stream->StartHeaders(frame->headers.cat);
897
  }
898
23655
  return 0;
899
}
900
901
// Called by nghttp2 for each header name/value pair in a HEADERS block.
902
// This had to have been preceded by a call to OnBeginHeadersCallback so
903
// the Http2Stream is guaranteed to already exist.
904
71783
int Http2Session::OnHeaderCallback(nghttp2_session* handle,
905
                                   const nghttp2_frame* frame,
906
                                   nghttp2_rcbuf* name,
907
                                   nghttp2_rcbuf* value,
908
                                   uint8_t flags,
909
                                   void* user_data) {
910
71783
  Http2Session* session = static_cast<Http2Session*>(user_data);
911
71783
  int32_t id = GetFrameID(frame);
912
143566
  BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
913
  // If stream is null at this point, either something odd has happened
914
  // or the stream was closed locally while header processing was occurring.
915
  // either way, do not proceed and close the stream.
916
71783
  if (UNLIKELY(!stream))
917
    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
918
919
  // If the stream has already been destroyed, ignore.
920

71783
  if (!stream->is_destroyed() && !stream->AddHeader(name, value, flags)) {
921
    // This will only happen if the connected peer sends us more
922
    // than the allowed number of header items at any given time
923
3
    stream->SubmitRstStream(NGHTTP2_ENHANCE_YOUR_CALM);
924
3
    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
925
  }
926
71780
  return 0;
927
}
928
929
930
// Called by nghttp2 when a complete HTTP2 frame has been received. There are
931
// only a handful of frame types that we care about handling here.
932
55723
int Http2Session::OnFrameReceive(nghttp2_session* handle,
933
                                 const nghttp2_frame* frame,
934
                                 void* user_data) {
935
55723
  Http2Session* session = static_cast<Http2Session*>(user_data);
936
55723
  session->statistics_.frame_count++;
937
  Debug(session, "complete frame received: type: %d",
938
55723
        frame->hd.type);
939


55723
  switch (frame->hd.type) {
940
24224
    case NGHTTP2_DATA:
941
24224
      return session->HandleDataFrame(frame);
942
23414
    case NGHTTP2_PUSH_PROMISE:
943
      // Intentional fall-through, handled just like headers frames
944
    case NGHTTP2_HEADERS:
945
23414
      session->HandleHeadersFrame(frame);
946
23414
      break;
947
2146
    case NGHTTP2_SETTINGS:
948
2146
      session->HandleSettingsFrame(frame);
949
2146
      break;
950
16
    case NGHTTP2_PRIORITY:
951
16
      session->HandlePriorityFrame(frame);
952
16
      break;
953
311
    case NGHTTP2_GOAWAY:
954
311
      session->HandleGoawayFrame(frame);
955
311
      break;
956
1020
    case NGHTTP2_PING:
957
1020
      session->HandlePingFrame(frame);
958
1020
      break;
959
4
    case NGHTTP2_ALTSVC:
960
4
      session->HandleAltSvcFrame(frame);
961
4
      break;
962
5
    case NGHTTP2_ORIGIN:
963
5
      session->HandleOriginFrame(frame);
964
5
      break;
965
4583
    default:
966
4583
      break;
967
  }
968
31499
  return 0;
969
}
970
971
242
int Http2Session::OnInvalidFrame(nghttp2_session* handle,
972
                                 const nghttp2_frame* frame,
973
                                 int lib_error_code,
974
                                 void* user_data) {
975
242
  Http2Session* session = static_cast<Http2Session*>(user_data);
976
242
  const uint32_t max_invalid_frames = session->js_fields_->max_invalid_frames;
977
978
  Debug(session,
979
        "invalid frame received (%u/%u), code: %d",
980
242
        session->invalid_frame_count_,
981
        max_invalid_frames,
982
        lib_error_code);
983
242
  if (session->invalid_frame_count_++ > max_invalid_frames) {
984
2
    session->custom_recv_error_code_ = "ERR_HTTP2_TOO_MANY_INVALID_FRAMES";
985
2
    return 1;
986
  }
987
988
  // If the error is fatal or if error code is ERR_STREAM_CLOSED... emit error
989

480
  if (nghttp2_is_fatal(lib_error_code) ||
990
240
      lib_error_code == NGHTTP2_ERR_STREAM_CLOSED) {
991
1
    Environment* env = session->env();
992
1
    Isolate* isolate = env->isolate();
993
2
    HandleScope scope(isolate);
994
1
    Local<Context> context = env->context();
995
1
    Context::Scope context_scope(context);
996
1
    Local<Value> arg = Integer::New(isolate, lib_error_code);
997
1
    session->MakeCallback(env->http2session_on_error_function(), 1, &arg);
998
  }
999
240
  return 0;
1000
}
1001
1002
// If nghttp2 is unable to send a queued up frame, it will call this callback
1003
// to let us know. If the failure occurred because we are in the process of
1004
// closing down the session or stream, we go ahead and ignore it. We don't
1005
// really care about those and there's nothing we can reasonably do about it
1006
// anyway. Other types of failures are reported up to JavaScript. This should
1007
// be exceedingly rare.
1008
2191
int Http2Session::OnFrameNotSent(nghttp2_session* handle,
1009
                                 const nghttp2_frame* frame,
1010
                                 int error_code,
1011
                                 void* user_data) {
1012
2191
  Http2Session* session = static_cast<Http2Session*>(user_data);
1013
2191
  Environment* env = session->env();
1014
  Debug(session, "frame type %d was not sent, code: %d",
1015
2191
        frame->hd.type, error_code);
1016
1017
  // Do not report if the frame was not sent due to the session closing
1018
4386
  if (error_code == NGHTTP2_ERR_SESSION_CLOSING ||
1019
4
      error_code == NGHTTP2_ERR_STREAM_CLOSED ||
1020

2197
      error_code == NGHTTP2_ERR_STREAM_CLOSING ||
1021
2
      session->js_fields_->frame_error_listener_count == 0) {
1022
2190
    return 0;
1023
  }
1024
1025
1
  Isolate* isolate = env->isolate();
1026
2
  HandleScope scope(isolate);
1027
1
  Local<Context> context = env->context();
1028
1
  Context::Scope context_scope(context);
1029
1030
  Local<Value> argv[3] = {
1031
1
    Integer::New(isolate, frame->hd.stream_id),
1032
1
    Integer::New(isolate, frame->hd.type),
1033
    Integer::New(isolate, error_code)
1034
3
  };
1035
  session->MakeCallback(
1036
      env->http2session_on_frame_error_function(),
1037
1
      arraysize(argv), argv);
1038
1
  return 0;
1039
}
1040
1041
55871
int Http2Session::OnFrameSent(nghttp2_session* handle,
1042
                              const nghttp2_frame* frame,
1043
                              void* user_data) {
1044
55871
  Http2Session* session = static_cast<Http2Session*>(user_data);
1045
55871
  session->statistics_.frame_sent += 1;
1046
55871
  return 0;
1047
}
1048
1049
// Called by nghttp2 when a stream closes.
1050
23605
int Http2Session::OnStreamClose(nghttp2_session* handle,
1051
                                int32_t id,
1052
                                uint32_t code,
1053
                                void* user_data) {
1054
23605
  Http2Session* session = static_cast<Http2Session*>(user_data);
1055
23605
  Environment* env = session->env();
1056
23605
  Isolate* isolate = env->isolate();
1057
47210
  HandleScope scope(isolate);
1058
23605
  Local<Context> context = env->context();
1059
23605
  Context::Scope context_scope(context);
1060
  Debug(session, "stream %d closed with code: %d", id, code);
1061
47210
  BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
1062
  // Intentionally ignore the callback if the stream does not exist or has
1063
  // already been destroyed
1064

23605
  if (!stream || stream->is_destroyed())
1065
53
    return 0;
1066
1067
23552
  stream->Close(code);
1068
1069
  // It is possible for the stream close to occur before the stream is
1070
  // ever passed on to the javascript side. If that happens, the callback
1071
  // will return false.
1072
23552
  Local<Value> arg = Integer::NewFromUnsigned(isolate, code);
1073
  MaybeLocal<Value> answer =
1074
23552
    stream->MakeCallback(env->http2session_on_stream_close_function(),
1075
23552
                          1, &arg);
1076

47104
  if (answer.IsEmpty() || answer.ToLocalChecked()->IsFalse()) {
1077
    // Skip to destroy
1078
138
    stream->Destroy();
1079
  }
1080
23552
  return 0;
1081
}
1082
1083
// Called by nghttp2 when an invalid header has been received. For now, we
1084
// ignore these. If this callback was not provided, nghttp2 would handle
1085
// invalid headers strictly and would shut down the stream. We are intentionally
1086
// being more lenient here although we may want to revisit this choice later.
1087
4
int Http2Session::OnInvalidHeader(nghttp2_session* session,
1088
                                  const nghttp2_frame* frame,
1089
                                  nghttp2_rcbuf* name,
1090
                                  nghttp2_rcbuf* value,
1091
                                  uint8_t flags,
1092
                                  void* user_data) {
1093
  // Ignore invalid header fields by default.
1094
4
  return 0;
1095
}
1096
1097
// When nghttp2 receives a DATA frame, it will deliver the data payload to
1098
// us in discrete chunks. We push these into a linked list stored in the
1099
// Http2Sttream which is flushed out to JavaScript as quickly as possible.
1100
// This can be a particularly hot path.
1101
13835
int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
1102
                                      uint8_t flags,
1103
                                      int32_t id,
1104
                                      const uint8_t* data,
1105
                                      size_t len,
1106
                                      void* user_data) {
1107
13835
  Http2Session* session = static_cast<Http2Session*>(user_data);
1108
  Debug(session, "buffering data chunk for stream %d, size: "
1109
        "%d, flags: %d", id, len, flags);
1110
13835
  Environment* env = session->env();
1111
27670
  HandleScope scope(env->isolate());
1112
1113
  // We should never actually get a 0-length chunk so this check is
1114
  // only a precaution at this point.
1115
13835
  if (len == 0)
1116
    return 0;
1117
1118
  // Notify nghttp2 that we've consumed a chunk of data on the connection
1119
  // so that it can send a WINDOW_UPDATE frame. This is a critical part of
1120
  // the flow control process in http2
1121
13835
  CHECK_EQ(nghttp2_session_consume_connection(handle, len), 0);
1122
27670
  BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
1123
1124
  // If the stream has been destroyed, ignore this chunk
1125

13835
  if (!stream || stream->is_destroyed())
1126
1
    return 0;
1127
1128
13834
  stream->statistics_.received_bytes += len;
1129
1130
  // Repeatedly ask the stream's owner for memory, and copy the read data
1131
  // into those buffers.
1132
  // The typical case is actually the exception here; Http2StreamListeners
1133
  // know about the HTTP2 session associated with this stream, so they know
1134
  // about the larger from-socket read buffer, so they do not require copying.
1135
  do {
1136
13834
    uv_buf_t buf = stream->EmitAlloc(len);
1137
13834
    ssize_t avail = len;
1138
13834
    if (static_cast<ssize_t>(buf.len) < avail)
1139
      avail = buf.len;
1140
1141
    // `buf.base == nullptr` is the default Http2StreamListener's way
1142
    // of saying that it wants a pointer to the raw original.
1143
    // Since it has access to the original socket buffer from which the data
1144
    // was read in the first place, it can use that to minimize ArrayBuffer
1145
    // allocations.
1146
13834
    if (LIKELY(buf.base == nullptr))
1147
13834
      buf.base = reinterpret_cast<char*>(const_cast<uint8_t*>(data));
1148
    else
1149
      memcpy(buf.base, data, avail);
1150
13834
    data += avail;
1151
13834
    len -= avail;
1152
13834
    stream->EmitRead(avail, buf);
1153
1154
    // If the stream owner (e.g. the JS Http2Stream) wants more data, just
1155
    // tell nghttp2 that all data has been consumed. Otherwise, defer until
1156
    // more data is being requested.
1157
13834
    if (stream->is_reading())
1158
12726
      nghttp2_session_consume_stream(handle, id, avail);
1159
    else
1160
1108
      stream->inbound_consumed_data_while_paused_ += avail;
1161
1162
    // If we have a gathered a lot of data for output, try sending it now.
1163

27665
    if (session->outgoing_length_ > 4096 ||
1164
13831
        stream->available_outbound_length_ > 4096) {
1165
10
      session->SendPendingData();
1166
    }
1167
13834
  } while (len != 0);
1168
1169
  // If we are currently waiting for a write operation to finish, we should
1170
  // tell nghttp2 that we want to wait before we process more input data.
1171
13834
  if (session->is_write_in_progress()) {
1172
567
    CHECK(session->is_reading_stopped());
1173
567
    session->set_receive_paused();
1174
    Debug(session, "receive paused");
1175
567
    return NGHTTP2_ERR_PAUSE;
1176
  }
1177
1178
13267
  return 0;
1179
}
1180
1181
// Called by nghttp2 when it needs to determine how much padding to use in
1182
// a DATA or HEADERS frame.
1183
3
ssize_t Http2Session::OnSelectPadding(nghttp2_session* handle,
1184
                                      const nghttp2_frame* frame,
1185
                                      size_t maxPayloadLen,
1186
                                      void* user_data) {
1187
3
  Http2Session* session = static_cast<Http2Session*>(user_data);
1188
3
  ssize_t padding = frame->hd.length;
1189
1190

3
  switch (session->padding_strategy_) {
1191
    case PADDING_STRATEGY_NONE:
1192
      // Fall-through
1193
      break;
1194
    case PADDING_STRATEGY_MAX:
1195
      padding = session->OnMaxFrameSizePadding(padding, maxPayloadLen);
1196
      break;
1197
3
    case PADDING_STRATEGY_ALIGNED:
1198
3
      padding = session->OnDWordAlignedPadding(padding, maxPayloadLen);
1199
3
      break;
1200
  }
1201
3
  return padding;
1202
}
1203
1204
#define BAD_PEER_MESSAGE "Remote peer returned unexpected data while we "     \
1205
                         "expected SETTINGS frame.  Perhaps, peer does not "  \
1206
                         "support HTTP/2 properly."
1207
1208
// We use this currently to determine when an attempt is made to use the http2
1209
// protocol with a non-http2 peer.
1210
241
int Http2Session::OnNghttpError(nghttp2_session* handle,
1211
                                const char* message,
1212
                                size_t len,
1213
                                void* user_data) {
1214
  // Unfortunately, this is currently the only way for us to know if
1215
  // the session errored because the peer is not an http2 peer.
1216
241
  Http2Session* session = static_cast<Http2Session*>(user_data);
1217
  Debug(session, "Error '%s'", message);
1218
241
  if (strncmp(message, BAD_PEER_MESSAGE, len) == 0) {
1219
1
    Environment* env = session->env();
1220
1
    Isolate* isolate = env->isolate();
1221
2
    HandleScope scope(isolate);
1222
1
    Local<Context> context = env->context();
1223
1
    Context::Scope context_scope(context);
1224
1
    Local<Value> arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1225
1
    session->MakeCallback(env->http2session_on_error_function(), 1, &arg);
1226
  }
1227
241
  return 0;
1228
}
1229
1230
13834
uv_buf_t Http2StreamListener::OnStreamAlloc(size_t size) {
1231
  // See the comments in Http2Session::OnDataChunkReceived
1232
  // (which is the only possible call site for this method).
1233
13834
  return uv_buf_init(nullptr, size);
1234
}
1235
1236
26496
void Http2StreamListener::OnStreamRead(ssize_t nread, const uv_buf_t& buf) {
1237
26496
  Http2Stream* stream = static_cast<Http2Stream*>(stream_);
1238
26496
  Http2Session* session = stream->session();
1239
26496
  Environment* env = stream->env();
1240
26496
  HandleScope handle_scope(env->isolate());
1241
26496
  Context::Scope context_scope(env->context());
1242
1243
26496
  if (nread < 0) {
1244
12662
    PassReadErrorToPreviousListener(nread);
1245
12662
    return;
1246
  }
1247
1248
  Local<ArrayBuffer> ab;
1249
13834
  if (session->stream_buf_ab_.IsEmpty()) {
1250
5621
    ab = session->stream_buf_allocation_.ToArrayBuffer();
1251
5621
    session->stream_buf_ab_.Reset(env->isolate(), ab);
1252
  } else {
1253
8213
    ab = PersistentToLocal::Strong(session->stream_buf_ab_);
1254
  }
1255
1256
  // There is a single large array buffer for the entire data read from the
1257
  // network; create a slice of that array buffer and emit it as the
1258
  // received data buffer.
1259
13834
  size_t offset = buf.base - session->stream_buf_.base;
1260
1261
  // Verify that the data offset is inside the current read buffer.
1262
13834
  CHECK_GE(offset, session->stream_buf_offset_);
1263
13834
  CHECK_LE(offset, session->stream_buf_.len);
1264
13834
  CHECK_LE(offset + buf.len, session->stream_buf_.len);
1265
1266
13834
  stream->CallJSOnreadMethod(nread, ab, offset);
1267
}
1268
1269
1270
// Called by OnFrameReceived to notify JavaScript land that a complete
1271
// HEADERS frame has been received and processed. This method converts the
1272
// received headers into a JavaScript array and pushes those out to JS.
1273
23414
void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
1274
23414
  Isolate* isolate = env()->isolate();
1275
23414
  HandleScope scope(isolate);
1276
23414
  Local<Context> context = env()->context();
1277
23414
  Context::Scope context_scope(context);
1278
1279
23414
  int32_t id = GetFrameID(frame);
1280
23414
  Debug(this, "handle headers frame for stream %d", id);
1281
23414
  BaseObjectPtr<Http2Stream> stream = FindStream(id);
1282
1283
  // If the stream has already been destroyed, ignore.
1284

23414
  if (!stream || stream->is_destroyed())
1285
    return;
1286
1287
  // The headers are stored as a vector of Http2Header instances.
1288
  // The following converts that into a JS array with the structure:
1289
  // [name1, value1, name2, value2, name3, value3, name3, value4] and so on.
1290
  // That array is passed up to the JS layer and converted into an Object form
1291
  // like {name1: value1, name2: value2, name3: [value3, value4]}. We do it
1292
  // this way for performance reasons (it's faster to generate and pass an
1293
  // array than it is to generate and pass the object).
1294
1295
46828
  MaybeStackBuffer<Local<Value>, 64> headers_v(stream->headers_count() * 2);
1296
46828
  MaybeStackBuffer<Local<Value>, 32> sensitive_v(stream->headers_count());
1297
23414
  size_t sensitive_count = 0;
1298
1299
23414
  stream->TransferHeaders([&](const Http2Header& header, size_t i) {
1300
143342
    headers_v[i * 2] = header.GetName(this).ToLocalChecked();
1301
143342
    headers_v[i * 2 + 1] = header.GetValue(this).ToLocalChecked();
1302
71671
    if (header.flags() & NGHTTP2_NV_FLAG_NO_INDEX)
1303
29
      sensitive_v[sensitive_count++] = headers_v[i * 2];
1304
71671
  });
1305
23414
  CHECK_EQ(stream->headers_count(), 0);
1306
1307
23414
  DecrementCurrentSessionMemory(stream->current_headers_length_);
1308
23414
  stream->current_headers_length_ = 0;
1309
1310
  Local<Value> args[] = {
1311
23414
    stream->object(),
1312
    Integer::New(isolate, id),
1313
23414
    Integer::New(isolate, stream->headers_category()),
1314
23414
    Integer::New(isolate, frame->hd.flags),
1315
    Array::New(isolate, headers_v.out(), headers_v.length()),
1316
    Array::New(isolate, sensitive_v.out(), sensitive_count),
1317
163898
  };
1318
  MakeCallback(env()->http2session_on_headers_function(),
1319
23414
               arraysize(args), args);
1320
}
1321
1322
1323
// Called by OnFrameReceived when a complete PRIORITY frame has been
1324
// received. Notifies JS land about the priority change. Note that priorities
1325
// are considered advisory only, so this has no real effect other than to
1326
// simply let user code know that the priority has changed.
1327
16
void Http2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
1328
16
  if (js_fields_->priority_listener_count == 0) return;
1329
5
  Isolate* isolate = env()->isolate();
1330
10
  HandleScope scope(isolate);
1331
5
  Local<Context> context = env()->context();
1332
5
  Context::Scope context_scope(context);
1333
1334
5
  nghttp2_priority priority_frame = frame->priority;
1335
5
  int32_t id = GetFrameID(frame);
1336
5
  Debug(this, "handle priority frame for stream %d", id);
1337
  // Priority frame stream ID should never be <= 0. nghttp2 handles this for us
1338
5
  nghttp2_priority_spec spec = priority_frame.pri_spec;
1339
1340
  Local<Value> argv[4] = {
1341
    Integer::New(isolate, id),
1342
    Integer::New(isolate, spec.stream_id),
1343
    Integer::New(isolate, spec.weight),
1344
5
    Boolean::New(isolate, spec.exclusive)
1345
20
  };
1346
  MakeCallback(env()->http2session_on_priority_function(),
1347
5
               arraysize(argv), argv);
1348
}
1349
1350
1351
// Called by OnFrameReceived when a complete DATA frame has been received.
1352
// If we know that this was the last DATA frame (because the END_STREAM flag
1353
// is set), then we'll terminate the readable side of the StreamBase.
1354
24224
int Http2Session::HandleDataFrame(const nghttp2_frame* frame) {
1355
24224
  int32_t id = GetFrameID(frame);
1356
24224
  Debug(this, "handling data frame for stream %d", id);
1357
48448
  BaseObjectPtr<Http2Stream> stream = FindStream(id);
1358
1359
48448
  if (stream &&
1360

48448
      !stream->is_destroyed() &&
1361
24224
      frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
1362
12662
    stream->EmitRead(UV_EOF);
1363
11562
  } else if (frame->hd.length == 0) {
1364
5
    if (invalid_frame_count_++ > js_fields_->max_invalid_frames) {
1365
1
      custom_recv_error_code_ = "ERR_HTTP2_TOO_MANY_INVALID_FRAMES";
1366
1
      Debug(this, "rejecting empty-frame-without-END_STREAM flood\n");
1367
      // Consider a flood of 0-length frames without END_STREAM an error.
1368
1
      return 1;
1369
    }
1370
  }
1371
24223
  return 0;
1372
}
1373
1374
1375
// Called by OnFrameReceived when a complete GOAWAY frame has been received.
1376
311
void Http2Session::HandleGoawayFrame(const nghttp2_frame* frame) {
1377
311
  Isolate* isolate = env()->isolate();
1378
622
  HandleScope scope(isolate);
1379
311
  Local<Context> context = env()->context();
1380
311
  Context::Scope context_scope(context);
1381
1382
311
  nghttp2_goaway goaway_frame = frame->goaway;
1383
311
  Debug(this, "handling goaway frame");
1384
1385
  Local<Value> argv[3] = {
1386
    Integer::NewFromUnsigned(isolate, goaway_frame.error_code),
1387
    Integer::New(isolate, goaway_frame.last_stream_id),
1388
    Undefined(isolate)
1389
933
  };
1390
1391
311
  size_t length = goaway_frame.opaque_data_len;
1392
311
  if (length > 0) {
1393
    // If the copy fails for any reason here, we just ignore it.
1394
    // The additional goaway data is completely optional and we
1395
    // shouldn't fail if we're not able to process it.
1396
3
    argv[2] = Buffer::Copy(isolate,
1397
3
                           reinterpret_cast<char*>(goaway_frame.opaque_data),
1398
3
                           length).ToLocalChecked();
1399
  }
1400
1401
  MakeCallback(env()->http2session_on_goaway_data_function(),
1402
311
               arraysize(argv), argv);
1403
311
}
1404
1405
// Called by OnFrameReceived when a complete ALTSVC frame has been received.
1406
4
void Http2Session::HandleAltSvcFrame(const nghttp2_frame* frame) {
1407
4
  if (!(js_fields_->bitfield & (1 << kSessionHasAltsvcListeners))) return;
1408
4
  Isolate* isolate = env()->isolate();
1409
8
  HandleScope scope(isolate);
1410
4
  Local<Context> context = env()->context();
1411
4
  Context::Scope context_scope(context);
1412
1413
4
  int32_t id = GetFrameID(frame);
1414
1415
4
  nghttp2_extension ext = frame->ext;
1416
4
  nghttp2_ext_altsvc* altsvc = static_cast<nghttp2_ext_altsvc*>(ext.payload);
1417
4
  Debug(this, "handling altsvc frame");
1418
1419
  Local<Value> argv[3] = {
1420
    Integer::New(isolate, id),
1421
8
    OneByteString(isolate, altsvc->origin, altsvc->origin_len),
1422
8
    OneByteString(isolate, altsvc->field_value, altsvc->field_value_len)
1423
12
  };
1424
1425
  MakeCallback(env()->http2session_on_altsvc_function(),
1426
4
               arraysize(argv), argv);
1427
}
1428
1429
5
void Http2Session::HandleOriginFrame(const nghttp2_frame* frame) {
1430
5
  Isolate* isolate = env()->isolate();
1431
10
  HandleScope scope(isolate);
1432
5
  Local<Context> context = env()->context();
1433
5
  Context::Scope context_scope(context);
1434
1435
5
  Debug(this, "handling origin frame");
1436
1437
5
  nghttp2_extension ext = frame->ext;
1438
5
  nghttp2_ext_origin* origin = static_cast<nghttp2_ext_origin*>(ext.payload);
1439
1440
5
  size_t nov = origin->nov;
1441
10
  std::vector<Local<Value>> origin_v(nov);
1442
1443
14
  for (size_t i = 0; i < nov; ++i) {
1444
9
    const nghttp2_origin_entry& entry = origin->ov[i];
1445
18
    origin_v[i] = OneByteString(isolate, entry.origin, entry.origin_len);
1446
  }
1447
5
  Local<Value> holder = Array::New(isolate, origin_v.data(), origin_v.size());
1448
5
  MakeCallback(env()->http2session_on_origin_function(), 1, &holder);
1449
5
}
1450
1451
// Called by OnFrameReceived when a complete PING frame has been received.
1452
1020
void Http2Session::HandlePingFrame(const nghttp2_frame* frame) {
1453
1020
  Isolate* isolate = env()->isolate();
1454
1020
  HandleScope scope(isolate);
1455
1020
  Local<Context> context = env()->context();
1456
1020
  Context::Scope context_scope(context);
1457
  Local<Value> arg;
1458
1020
  bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
1459
1020
  if (ack) {
1460
22
    BaseObjectPtr<Http2Ping> ping = PopPing();
1461
1462
11
    if (!ping) {
1463
      // PING Ack is unsolicited. Treat as a connection error. The HTTP/2
1464
      // spec does not require this, but there is no legitimate reason to
1465
      // receive an unsolicited PING ack on a connection. Either the peer
1466
      // is buggy or malicious, and we're not going to tolerate such
1467
      // nonsense.
1468
1
      arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1469
1
      MakeCallback(env()->http2session_on_error_function(), 1, &arg);
1470
1
      return;
1471
    }
1472
1473
10
    ping->Done(true, frame->ping.opaque_data);
1474
10
    return;
1475
  }
1476
1477
1009
  if (!(js_fields_->bitfield & (1 << kSessionHasPingListeners))) return;
1478
  // Notify the session that a ping occurred
1479
2
  arg = Buffer::Copy(
1480
      env(),
1481
2
      reinterpret_cast<const char*>(frame->ping.opaque_data),
1482
2
      8).ToLocalChecked();
1483
2
  MakeCallback(env()->http2session_on_ping_function(), 1, &arg);
1484
}
1485
1486
// Called by OnFrameReceived when a complete SETTINGS frame has been received.
1487
2146
void Http2Session::HandleSettingsFrame(const nghttp2_frame* frame) {
1488
2146
  bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
1489
2146
  if (!ack) {
1490
1606
    js_fields_->bitfield &= ~(1 << kSessionRemoteSettingsIsUpToDate);
1491
1606
    if (!(js_fields_->bitfield & (1 << kSessionHasRemoteSettingsListeners)))
1492
2146
      return;
1493
    // This is not a SETTINGS acknowledgement, notify and return
1494
13
    MakeCallback(env()->http2session_on_settings_function(), 0, nullptr);
1495
13
    return;
1496
  }
1497
1498
  // If this is an acknowledgement, we should have an Http2Settings
1499
  // object for it.
1500
540
  BaseObjectPtr<Http2Settings> settings = PopSettings();
1501
540
  if (settings) {
1502
540
    settings->Done(true);
1503
540
    return;
1504
  }
1505
  // SETTINGS Ack is unsolicited. Treat as a connection error. The HTTP/2
1506
  // spec does not require this, but there is no legitimate reason to
1507
  // receive an unsolicited SETTINGS ack on a connection. Either the peer
1508
  // is buggy or malicious, and we're not going to tolerate such
1509
  // nonsense.
1510
  // Note that nghttp2 currently prevents this from happening for SETTINGS
1511
  // frames, so this block is purely defensive just in case that behavior
1512
  // changes. Specifically, unlike unsolicited PING acks, unsolicited
1513
  // SETTINGS acks should *never* make it this far.
1514
  Isolate* isolate = env()->isolate();
1515
  HandleScope scope(isolate);
1516
  Local<Context> context = env()->context();
1517
  Context::Scope context_scope(context);
1518
  Local<Value> arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1519
  MakeCallback(env()->http2session_on_error_function(), 1, &arg);
1520
}
1521
1522
// Callback used when data has been written to the stream.
1523
1526
void Http2Session::OnStreamAfterWrite(WriteWrap* w, int status) {
1524
1526
  Debug(this, "write finished with status %d", status);
1525
1526
1526
  CHECK(is_write_in_progress());
1527
1526
  set_write_in_progress(false);
1528
1529
  // Inform all pending writes about their completion.
1530
1526
  ClearOutgoing(status);
1531
1532
1526
  if (is_reading_stopped() &&
1533


3046
      !is_write_in_progress() &&
1534
1520
      nghttp2_session_want_read(session_.get())) {
1535
1477
    set_reading_stopped(false);
1536
1477
    stream_->ReadStart();
1537
  }
1538
1539
1526
  if (is_destroyed()) {
1540
43
    HandleScope scope(env()->isolate());
1541
43
    MakeCallback(env()->ondone_string(), 0, nullptr);
1542
43
    return;
1543
  }
1544
1545
  // If there is more incoming data queued up, consume it.
1546
1483
  if (stream_buf_offset_ > 0) {
1547
303
    ConsumeHTTP2Data();
1548
  }
1549
1550

1483
  if (!is_write_scheduled() && !is_destroyed()) {
1551
    // Schedule a new write if nghttp2 wants to send data.
1552
1375
    MaybeScheduleWrite();
1553
  }
1554
}
1555
1556
// If the underlying nghttp2_session struct has data pending in its outbound
1557
// queue, MaybeScheduleWrite will schedule a SendPendingData() call to occur
1558
// on the next iteration of the Node.js event loop (using the SetImmediate
1559
// queue), but only if a write has not already been scheduled.
1560
34031
void Http2Session::MaybeScheduleWrite() {
1561
34031
  CHECK(!is_write_scheduled());
1562
34031
  if (UNLIKELY(!session_))
1563
    return;
1564
1565
34031
  if (nghttp2_session_want_write(session_.get())) {
1566
3468
    HandleScope handle_scope(env()->isolate());
1567
1734
    Debug(this, "scheduling write");
1568
1734
    set_write_scheduled();
1569
1734
    BaseObjectPtr<Http2Session> strong_ref{this};
1570
1734
    env()->SetImmediate([this, strong_ref](Environment* env) {
1571

1734
      if (!session_ || !is_write_scheduled()) {
1572
        // This can happen e.g. when a stream was reset before this turn
1573
        // of the event loop, in which case SendPendingData() is called early,
1574
        // or the session was destroyed in the meantime.
1575
299
        return;
1576
      }
1577
1578
      // Sending data may call arbitrary JS code, so keep track of
1579
      // async context.
1580
2870
      HandleScope handle_scope(env->isolate());
1581
2870
      InternalCallbackScope callback_scope(this);
1582
1435
      SendPendingData();
1583
    });
1584
  }
1585
}
1586
1587
62537
void Http2Session::MaybeStopReading() {
1588
62537
  if (is_reading_stopped()) return;
1589
59853
  int want_read = nghttp2_session_want_read(session_.get());
1590
59853
  Debug(this, "wants read? %d", want_read);
1591

59853
  if (want_read == 0 || is_write_in_progress()) {
1592
1489
    set_reading_stopped();
1593
1489
    stream_->ReadStop();
1594
  }
1595
}
1596
1597
// Unset the sending state, finish up all current writes, and reset
1598
// storage for data and metadata that was associated with these writes.
1599
32379
void Http2Session::ClearOutgoing(int status) {
1600
32379
  CHECK(is_sending());
1601
1602
32379
  set_sending(false);
1603
1604
32379
  if (!outgoing_buffers_.empty()) {
1605
31373
    outgoing_storage_.clear();
1606
31373
    outgoing_length_ = 0;
1607
1608
62746
    std::vector<NgHttp2StreamWrite> current_outgoing_buffers_;
1609
31373
    current_outgoing_buffers_.swap(outgoing_buffers_);
1610
125180
    for (const NgHttp2StreamWrite& wr : current_outgoing_buffers_) {
1611
187614
      BaseObjectPtr<AsyncWrap> wrap = std::move(wr.req_wrap);
1612
93807
      if (wrap) {
1613
        // TODO(addaleax): Pass `status` instead of 0, so that we actually error
1614
        // out with the error from the write to the underlying protocol,
1615
        // if one occurred.
1616
4006
        WriteWrap::FromObject(wrap)->Done(0);
1617
      }
1618
    }
1619
  }
1620
1621
  // Now that we've finished sending queued data, if there are any pending
1622
  // RstStreams we should try sending again and then flush them one by one.
1623
32379
  if (!pending_rst_streams_.empty()) {
1624
14
    std::vector<int32_t> current_pending_rst_streams;
1625
7
    pending_rst_streams_.swap(current_pending_rst_streams);
1626
1627
7
    SendPendingData();
1628
1629
16
    for (int32_t stream_id : current_pending_rst_streams) {
1630
18
      BaseObjectPtr<Http2Stream> stream = FindStream(stream_id);
1631
9
      if (LIKELY(stream))
1632
2
        stream->FlushRstStream();
1633
    }
1634
  }
1635
32379
}
1636
1637
93819
void Http2Session::PushOutgoingBuffer(NgHttp2StreamWrite&& write) {
1638
93819
  outgoing_length_ += write.buf.len;
1639
93819
  outgoing_buffers_.emplace_back(std::move(write));
1640
93819
}
1641
1642
// Queue a given block of data for sending. This always creates a copy,
1643
// so it is used for the cases in which nghttp2 requests sending of a
1644
// small chunk of data.
1645
56198
void Http2Session::CopyDataIntoOutgoing(const uint8_t* src, size_t src_length) {
1646
56198
  size_t offset = outgoing_storage_.size();
1647
56198
  outgoing_storage_.resize(offset + src_length);
1648
56198
  memcpy(&outgoing_storage_[offset], src, src_length);
1649
1650
  // Store with a base of `nullptr` initially, since future resizes
1651
  // of the outgoing_buffers_ vector may invalidate the pointer.
1652
  // The correct base pointers will be set later, before writing to the
1653
  // underlying socket.
1654
56198
  PushOutgoingBuffer(NgHttp2StreamWrite {
1655
    uv_buf_init(nullptr, src_length)
1656
  });
1657
56198
}
1658
1659
// Prompts nghttp2 to begin serializing it's pending data and pushes each
1660
// chunk out to the i/o socket to be sent. This is a particularly hot method
1661
// that will generally be called at least twice be event loop iteration.
1662
// This is a potential performance optimization target later.
1663
// Returns non-zero value if a write is already in progress.
1664
32555
uint8_t Http2Session::SendPendingData() {
1665
32555
  Debug(this, "sending pending data");
1666
  // Do not attempt to send data on the socket if the destroying flag has
1667
  // been set. That means everything is shutting down and the socket
1668
  // will not be usable.
1669
32555
  if (is_destroyed())
1670
36
    return 0;
1671
32519
  set_write_scheduled(false);
1672
1673
  // SendPendingData should not be called recursively.
1674
32519
  if (is_sending())
1675
136
    return 1;
1676
  // This is cleared by ClearOutgoing().
1677
32383
  set_sending();
1678
1679
  ssize_t src_length;
1680
  const uint8_t* src;
1681
1682
32383
  CHECK(outgoing_buffers_.empty());
1683
32383
  CHECK(outgoing_storage_.empty());
1684
1685
  // Part One: Gather data from nghttp2
1686
1687
74703
  while ((src_length = nghttp2_session_mem_send(session_.get(), &src)) > 0) {
1688
42320
    Debug(this, "nghttp2 has %d bytes to send", src_length);
1689
42320
    CopyDataIntoOutgoing(src, src_length);
1690
  }
1691
1692
32383
  CHECK_NE(src_length, NGHTTP2_ERR_NOMEM);
1693
1694
32383
  if (stream_ == nullptr) {
1695
    // It would seem nice to bail out earlier, but `nghttp2_session_mem_send()`
1696
    // does take care of things like closing the individual streams after
1697
    // a socket has been torn down, so we still need to call it.
1698
16
    ClearOutgoing(UV_ECANCELED);
1699
16
    return 0;
1700
  }
1701
1702
  // Part Two: Pass Data to the underlying stream
1703
1704
32367
  size_t count = outgoing_buffers_.size();
1705
32367
  if (count == 0) {
1706
1006
    ClearOutgoing(0);
1707
1006
    return 0;
1708
  }
1709
62722
  MaybeStackBuffer<uv_buf_t, 32> bufs;
1710
31361
  bufs.AllocateSufficientStorage(count);
1711
1712
  // Set the buffer base pointers for copied data that ended up in the
1713
  // sessions's own storage since it might have shifted around during gathering.
1714
  // (Those are marked by having .base == nullptr.)
1715
31361
  size_t offset = 0;
1716
31361
  size_t i = 0;
1717
125158
  for (const NgHttp2StreamWrite& write : outgoing_buffers_) {
1718
93797
    statistics_.data_sent += write.buf.len;
1719
93797
    if (write.buf.base == nullptr) {
1720
56176
      bufs[i++] = uv_buf_init(
1721
56176
          reinterpret_cast<char*>(outgoing_storage_.data() + offset),
1722
112352
          write.buf.len);
1723
56176
      offset += write.buf.len;
1724
    } else {
1725
37621
      bufs[i++] = write.buf;
1726
    }
1727
  }
1728
1729
31361
  chunks_sent_since_last_write_++;
1730
1731
31361
  CHECK(!is_write_in_progress());
1732
31361
  set_write_in_progress();
1733
31361
  StreamWriteResult res = underlying_stream()->Write(*bufs, count);
1734
31361
  if (!res.async) {
1735
29831
    set_write_in_progress(false);
1736
29831
    ClearOutgoing(res.err);
1737
  }
1738
1739
31361
  MaybeStopReading();
1740
1741
31361
  return 0;
1742
}
1743
1744
1745
// This callback is called from nghttp2 when it wants to send DATA frames for a
1746
// given Http2Stream, when we set the `NGHTTP2_DATA_FLAG_NO_COPY` flag earlier
1747
// in the Http2Stream::Provider::Stream::OnRead callback.
1748
// We take the write information directly out of the stream's data queue.
1749
13877
int Http2Session::OnSendData(
1750
      nghttp2_session* session_,
1751
      nghttp2_frame* frame,
1752
      const uint8_t* framehd,
1753
      size_t length,
1754
      nghttp2_data_source* source,
1755
      void* user_data) {
1756
13877
  Http2Session* session = static_cast<Http2Session*>(user_data);
1757
27754
  BaseObjectPtr<Http2Stream> stream = session->FindStream(frame->hd.stream_id);
1758
13877
  if (!stream) return 0;
1759
1760
  // Send the frame header + a byte that indicates padding length.
1761
13877
  session->CopyDataIntoOutgoing(framehd, 9);
1762
13877
  if (frame->data.padlen > 0) {
1763
1
    uint8_t padding_byte = frame->data.padlen - 1;
1764
1
    CHECK_EQ(padding_byte, frame->data.padlen - 1);
1765
1
    session->CopyDataIntoOutgoing(&padding_byte, 1);
1766
  }
1767
1768
  Debug(session, "nghttp2 has %d bytes to send directly", length);
1769
41628
  while (length > 0) {
1770
    // nghttp2 thinks that there is data available (length > 0), which means
1771
    // we told it so, which means that we *should* have data available.
1772
37620
    CHECK(!stream->queue_.empty());
1773
1774
37620
    NgHttp2StreamWrite& write = stream->queue_.front();
1775
37620
    if (write.buf.len <= length) {
1776
      // This write does not suffice by itself, so we can consume it completely.
1777
27751
      length -= write.buf.len;
1778
27751
      session->PushOutgoingBuffer(std::move(write));
1779
27751
      stream->queue_.pop();
1780
27751
      continue;
1781
    }
1782
1783
    // Slice off `length` bytes of the first write in the queue.
1784
9869
    session->PushOutgoingBuffer(NgHttp2StreamWrite {
1785
      uv_buf_init(write.buf.base, length)
1786
    });
1787
9869
    write.buf.base += length;
1788
9869
    write.buf.len -= length;
1789
9869
    break;
1790
  }
1791
1792
13877
  if (frame->data.padlen > 0) {
1793
    // Send padding if that was requested.
1794
1
    session->PushOutgoingBuffer(NgHttp2StreamWrite {
1795
1
      uv_buf_init(const_cast<char*>(zero_bytes_256), frame->data.padlen - 1)
1796
    });
1797
  }
1798
1799
13877
  return 0;
1800
}
1801
1802
// Creates a new Http2Stream and submits a new http2 request.
1803
11762
Http2Stream* Http2Session::SubmitRequest(
1804
    const Http2Priority& priority,
1805
    const Http2Headers& headers,
1806
    int32_t* ret,
1807
    int options) {
1808
11762
  Debug(this, "submitting request");
1809
23524
  Http2Scope h2scope(this);
1810
11762
  Http2Stream* stream = nullptr;
1811
11762
  Http2Stream::Provider::Stream prov(options);
1812
11762
  *ret = nghttp2_submit_request(
1813
      session_.get(),
1814
      &priority,
1815
      headers.data(),
1816
      headers.length(),
1817
11762
      *prov,
1818
      nullptr);
1819
11762
  CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
1820
11762
  if (LIKELY(*ret > 0))
1821
11761
    stream = Http2Stream::New(this, *ret, NGHTTP2_HCAT_HEADERS, options);
1822
11762
  return stream;
1823
}
1824
1825
31200
uv_buf_t Http2Session::OnStreamAlloc(size_t suggested_size) {
1826
31200
  return AllocatedBuffer::AllocateManaged(env(), suggested_size).release();
1827
}
1828
1829
// Callback used to receive inbound data from the i/o stream
1830
31223
void Http2Session::OnStreamRead(ssize_t nread, const uv_buf_t& buf_) {
1831
31223
  HandleScope handle_scope(env()->isolate());
1832
31223
  Context::Scope context_scope(env()->context());
1833
31223
  Http2Scope h2scope(this);
1834
31223
  CHECK_NOT_NULL(stream_);
1835
31223
  Debug(this, "receiving %d bytes, offset %d", nread, stream_buf_offset_);
1836
31223
  AllocatedBuffer buf(env(), buf_);
1837
1838
  // Only pass data on if nread > 0
1839
31223
  if (nread <= 0) {
1840
47
    if (nread < 0) {
1841
47
      PassReadErrorToPreviousListener(nread);
1842
    }
1843
47
    return;
1844
  }
1845
1846
31176
  statistics_.data_received += nread;
1847
1848
31176
  if (LIKELY(stream_buf_offset_ == 0)) {
1849
    // Shrink to the actual amount of used data.
1850
30913
    buf.Resize(nread);
1851
  } else {
1852
    // This is a very unlikely case, and should only happen if the ReadStart()
1853
    // call in OnStreamAfterWrite() immediately provides data. If that does
1854
    // happen, we concatenate the data we received with the already-stored
1855
    // pending input data, slicing off the already processed part.
1856
263
    size_t pending_len = stream_buf_.len - stream_buf_offset_;
1857
    AllocatedBuffer new_buf =
1858
526
        AllocatedBuffer::AllocateManaged(env(), pending_len + nread);
1859
263
    memcpy(new_buf.data(), stream_buf_.base + stream_buf_offset_, pending_len);
1860
263
    memcpy(new_buf.data() + pending_len, buf.data(), nread);
1861
1862
263
    buf = std::move(new_buf);
1863
263
    nread = buf.size();
1864
263
    stream_buf_offset_ = 0;
1865
263
    stream_buf_ab_.Reset();
1866
1867
    // We have now fully processed the stream_buf_ input chunk (by moving the
1868
    // remaining part into buf, which will be accounted for below).
1869
263
    DecrementCurrentSessionMemory(stream_buf_.len);
1870
  }
1871
1872
31176
  IncrementCurrentSessionMemory(nread);
1873
1874
  // Remember the current buffer, so that OnDataChunkReceived knows the
1875
  // offset of a DATA frame's data into the socket read buffer.
1876
31176
  stream_buf_ = uv_buf_init(buf.data(), static_cast<unsigned int>(nread));
1877
1878
  // Store this so we can create an ArrayBuffer for read data from it.
1879
  // DATA frames will be emitted as slices of that ArrayBuffer to avoid having
1880
  // to copy memory.
1881
31176
  stream_buf_allocation_ = std::move(buf);
1882
1883
31176
  ConsumeHTTP2Data();
1884
1885
31176
  MaybeStopReading();
1886
}
1887
1888
23618
bool Http2Session::HasWritesOnSocketForStream(Http2Stream* stream) {
1889
23710
  for (const NgHttp2StreamWrite& wr : outgoing_buffers_) {
1890


93
    if (wr.req_wrap && WriteWrap::FromObject(wr.req_wrap)->stream() == stream)
1891
1
      return true;
1892
  }
1893
23617
  return false;
1894
}
1895
1896
// Every Http2Session session is tightly bound to a single i/o StreamBase
1897
// (typically a net.Socket or tls.TLSSocket). The lifecycle of the two is
1898
// tightly coupled with all data transfer between the two happening at the
1899
// C++ layer via the StreamBase API.
1900
668
void Http2Session::Consume(Local<Object> stream_obj) {
1901
668
  StreamBase* stream = StreamBase::FromObject(stream_obj);
1902
668
  stream->PushStreamListener(this);
1903
668
  Debug(this, "i/o stream consumed");
1904
668
}
1905
1906
// Allow injecting of data from JS
1907
// This is used when the socket has already some data received
1908
// before our listener was attached
1909
// https://github.com/nodejs/node/issues/35475
1910
2
void Http2Session::Receive(const FunctionCallbackInfo<Value>& args) {
1911
  Http2Session* session;
1912
2
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
1913
2
  CHECK(args[0]->IsObject());
1914
1915
2
  ArrayBufferViewContents<char> buffer(args[0]);
1916
2
  const char* data = buffer.data();
1917
2
  size_t len = buffer.length();
1918
2
  Debug(session, "Receiving %zu bytes injected from JS", len);
1919
1920
  // Copy given buffer
1921
4
  while (len > 0) {
1922
2
    uv_buf_t buf = session->OnStreamAlloc(len);
1923
2
    size_t copy = buf.len > len ? len : buf.len;
1924
2
    memcpy(buf.base, data, copy);
1925
2
    buf.len = copy;
1926
2
    session->OnStreamRead(copy, buf);
1927
1928
2
    data += copy;
1929
2
    len -= copy;
1930
  }
1931
}
1932
1933
23741
Http2Stream* Http2Stream::New(Http2Session* session,
1934
                              int32_t id,
1935
                              nghttp2_headers_category category,
1936
                              int options) {
1937
  Local<Object> obj;
1938
47482
  if (!session->env()
1939
23741
           ->http2stream_constructor_template()
1940
23741
           ->NewInstance(session->env()->context())
1941
23741
           .ToLocal(&obj)) {
1942
    return nullptr;
1943
  }
1944
23741
  return new Http2Stream(session, obj, id, category, options);
1945
}
1946
1947
23741
Http2Stream::Http2Stream(Http2Session* session,
1948
                         Local<Object> obj,
1949
                         int32_t id,
1950
                         nghttp2_headers_category category,
1951
23741
                         int options)
1952
    : AsyncWrap(session->env(), obj, AsyncWrap::PROVIDER_HTTP2STREAM),
1953
      StreamBase(session->env()),
1954
      session_(session),
1955
      id_(id),
1956
23741
      current_headers_category_(category) {
1957
23741
  MakeWeak();
1958
23741
  StreamBase::AttachToObject(GetObject());
1959
23741
  statistics_.id = id;
1960
23741
  statistics_.start_time = uv_hrtime();
1961
1962
  // Limit the number of header pairs
1963
23741
  max_header_pairs_ = session->max_header_pairs();
1964
23741
  if (max_header_pairs_ == 0) {
1965
    max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
1966
  }
1967
23741
  current_headers_.reserve(std::min(max_header_pairs_, 12u));
1968
1969
  // Limit the number of header octets
1970
23741
  max_header_length_ =
1971
23741
      std::min(
1972
23741
        nghttp2_session_get_local_settings(
1973
          session->session(),
1974
          NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE),
1975
47482
      MAX_MAX_HEADER_LIST_SIZE);
1976
1977
23741
  if (options & STREAM_OPTION_GET_TRAILERS)
1978
2
    set_has_trailers();
1979
1980
23741
  PushStreamListener(&stream_listener_);
1981
1982
23741
  if (options & STREAM_OPTION_EMPTY_PAYLOAD)
1983
1172
    Shutdown();
1984
23741
  session->AddStream(this);
1985
23741
}
1986
1987
94964
Http2Stream::~Http2Stream() {
1988
47482
  Debug(this, "tearing down stream");
1989
94964
}
1990
1991
void Http2Stream::MemoryInfo(MemoryTracker* tracker) const {
1992
  tracker->TrackField("current_headers", current_headers_);
1993
  tracker->TrackField("queue", queue_);
1994
}
1995
1996
18
std::string Http2Stream::diagnostic_name() const {
1997
36
  return "HttpStream " + std::to_string(id()) + " (" +
1998
72
      std::to_string(static_cast<int64_t>(get_async_id())) + ") [" +
1999
54
      session()->diagnostic_name() + "]";
2000
}
2001
2002
// Notify the Http2Stream that a new block of HEADERS is being processed.
2003
11684
void Http2Stream::StartHeaders(nghttp2_headers_category category) {
2004
11684
  Debug(this, "starting headers, category: %d", category);
2005
11684
  CHECK(!this->is_destroyed());
2006
11684
  session_->DecrementCurrentSessionMemory(current_headers_length_);
2007
11684
  current_headers_length_ = 0;
2008
11684
  current_headers_.clear();
2009
11684
  current_headers_category_ = category;
2010
11684
}
2011
2012
2013
nghttp2_stream* Http2Stream::operator*() const { return stream(); }
2014
2015
11
nghttp2_stream* Http2Stream::stream() const {
2016
11
  return nghttp2_session_find_stream(session_->session(), id_);
2017
}
2018
2019
23552
void Http2Stream::Close(int32_t code) {
2020
23552
  CHECK(!this->is_destroyed());
2021
23552
  set_closed();
2022
23552
  code_ = code;
2023
23552
  Debug(this, "closed with code %d", code);
2024
23552
}
2025
2026
24033
ShutdownWrap* Http2Stream::CreateShutdownWrap(v8::Local<v8::Object> object) {
2027
  // DoShutdown() always finishes synchronously, so there's no need to create
2028
  // a structure to store asynchronous context.
2029
24033
  return nullptr;
2030
}
2031
2032
24033
int Http2Stream::DoShutdown(ShutdownWrap* req_wrap) {
2033
24033
  if (is_destroyed())
2034
    return UV_EPIPE;
2035
2036
  {
2037
48066
    Http2Scope h2scope(this);
2038
24033
    set_not_writable();
2039
24033
    CHECK_NE(nghttp2_session_resume_data(
2040
        session_->session(), id_),
2041
        NGHTTP2_ERR_NOMEM);
2042
24033
    Debug(this, "writable side shutdown");
2043
  }
2044
24033
  return 1;
2045
}
2046
2047
// Destroy the Http2Stream and render it unusable. Actual resources for the
2048
// Stream will not be freed until the next tick of the Node.js event loop
2049
// using the SetImmediate queue.
2050
23633
void Http2Stream::Destroy() {
2051
  // Do nothing if this stream instance is already destroyed
2052
23633
  if (is_destroyed())
2053
1
    return;
2054
23632
  if (session_->has_pending_rststream(id_))
2055
7
    FlushRstStream();
2056
23632
  set_destroyed();
2057
2058
23632
  Debug(this, "destroying stream");
2059
2060
  // Wait until the start of the next loop to delete because there
2061
  // may still be some pending operations queued for this stream.
2062
47264
  BaseObjectPtr<Http2Stream> strong_ref = session_->RemoveStream(id_);
2063
23632
  if (strong_ref) {
2064
23632
    env()->SetImmediate([this, strong_ref = std::move(strong_ref)](
2065
118152
        Environment* env) {
2066
      // Free any remaining outgoing data chunks here. This should be done
2067
      // here because it's possible for destroy to have been called while
2068
      // we still have queued outbound writes.
2069
23639
      while (!queue_.empty()) {
2070
7
        NgHttp2StreamWrite& head = queue_.front();
2071
7
        if (head.req_wrap)
2072
7
          WriteWrap::FromObject(head.req_wrap)->Done(UV_ECANCELED);
2073
7
        queue_.pop();
2074
      }
2075
2076
      // We can destroy the stream now if there are no writes for it
2077
      // already on the socket. Otherwise, we'll wait for the garbage collector
2078
      // to take care of cleaning up.
2079

47250
      if (session() == nullptr ||
2080
23618
          !session()->HasWritesOnSocketForStream(this)) {
2081
        // Delete once strong_ref goes out of scope.
2082
23631
        Detach();
2083
      }
2084
23632
    });
2085
  }
2086
2087
23632
  statistics_.end_time = uv_hrtime();
2088
47264
  session_->statistics_.stream_average_duration =
2089
47264
      ((statistics_.end_time - statistics_.start_time) /
2090
23632
          session_->statistics_.stream_count) / 1e6;
2091
23632
  EmitStatistics();
2092
}
2093
2094
2095
// Initiates a response on the Http2Stream using data provided via the
2096
// StreamBase Streams API.
2097
11685
int Http2Stream::SubmitResponse(const Http2Headers& headers, int options) {
2098
11685
  CHECK(!this->is_destroyed());
2099
23370
  Http2Scope h2scope(this);
2100
11685
  Debug(this, "submitting response");
2101
11685
  if (options & STREAM_OPTION_GET_TRAILERS)
2102
149
    set_has_trailers();
2103
2104
11685
  if (!is_writable())
2105
10123
    options |= STREAM_OPTION_EMPTY_PAYLOAD;
2106
2107
11685
  Http2Stream::Provider::Stream prov(this, options);
2108
11685
  int ret = nghttp2_submit_response(
2109
      session_->session(),
2110
      id_,
2111
      headers.data(),
2112
      headers.length(),
2113
11685
      *prov);
2114
11685
  CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2115
11685
  return ret;
2116
}
2117
2118
2119
// Submit informational headers for a stream.
2120
5
int Http2Stream::SubmitInfo(const Http2Headers& headers) {
2121
5
  CHECK(!this->is_destroyed());
2122
5
  Http2Scope h2scope(this);
2123
5
  Debug(this, "sending %d informational headers", headers.length());
2124
5
  int ret = nghttp2_submit_headers(
2125
      session_->session(),
2126
      NGHTTP2_FLAG_NONE,
2127
      id_,
2128
      nullptr,
2129
      headers.data(),
2130
      headers.length(),
2131
5
      nullptr);
2132
5
  CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2133
5
  return ret;
2134
}
2135
2136
37
void Http2Stream::OnTrailers() {
2137
37
  Debug(this, "let javascript know we are ready for trailers");
2138
37
  CHECK(!this->is_destroyed());
2139
37
  Isolate* isolate = env()->isolate();
2140
74
  HandleScope scope(isolate);
2141
37
  Local<Context> context = env()->context();
2142
37
  Context::Scope context_scope(context);
2143
37
  set_has_trailers(false);
2144
37
  MakeCallback(env()->http2session_on_stream_trailers_function(), 0, nullptr);
2145
37
}
2146
2147
// Submit informational headers for a stream.
2148
31
int Http2Stream::SubmitTrailers(const Http2Headers& headers) {
2149
31
  CHECK(!this->is_destroyed());
2150
31
  Http2Scope h2scope(this);
2151
31
  Debug(this, "sending %d trailers", headers.length());
2152
  int ret;
2153
  // Sending an empty trailers frame poses problems in Safari, Edge & IE.
2154
  // Instead we can just send an empty data frame with NGHTTP2_FLAG_END_STREAM
2155
  // to indicate that the stream is ready to be closed.
2156
31
  if (headers.length() == 0) {
2157
26
    Http2Stream::Provider::Stream prov(this, 0);
2158
26
    ret = nghttp2_submit_data(
2159
        session_->session(),
2160
        NGHTTP2_FLAG_END_STREAM,
2161
        id_,
2162
26
        *prov);
2163
  } else {
2164
5
    ret = nghttp2_submit_trailer(
2165
        session_->session(),
2166
        id_,
2167
        headers.data(),
2168
        headers.length());
2169
  }
2170
31
  CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2171
31
  return ret;
2172
}
2173
2174
// Submit a PRIORITY frame to the connected peer.
2175
6
int Http2Stream::SubmitPriority(const Http2Priority& priority,
2176
                                bool silent) {
2177
6
  CHECK(!this->is_destroyed());
2178
6
  Http2Scope h2scope(this);
2179
6
  Debug(this, "sending priority spec");
2180
6
  int ret = silent ?
2181
      nghttp2_session_change_stream_priority(
2182
          session_->session(),
2183
          id_,
2184
          &priority) :
2185
6
      nghttp2_submit_priority(
2186
          session_->session(),
2187
          NGHTTP2_FLAG_NONE,
2188
6
          id_, &priority);
2189
6
  CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2190
6
  return ret;
2191
}
2192
2193
// Closes the Http2Stream by submitting an RST_STREAM frame to the connected
2194
// peer.
2195
123
void Http2Stream::SubmitRstStream(const uint32_t code) {
2196
123
  CHECK(!this->is_destroyed());
2197
123
  code_ = code;
2198
2199
70
  auto is_stream_cancel = [](const uint32_t code) {
2200
70
    return code == NGHTTP2_CANCEL;
2201
  };
2202
2203
  // If RST_STREAM frame is received with error code NGHTTP2_CANCEL,
2204
  // add it to the pending list and don't force purge the data. It is
2205
  // to avoids the double free error due to unwanted behavior of nghttp2.
2206
2207
  // Add stream to the pending list only if it is received with scope
2208
  // below in the stack. The pending list may not get processed
2209
  // if RST_STREAM received is not in scope and added to the list
2210
  // causing endpoint to hang.
2211

123
  if (session_->is_in_scope() && is_stream_cancel(code)) {
2212
1
      session_->AddPendingRstStream(id_);
2213
9
      return;
2214
  }
2215
2216
2217
  // If possible, force a purge of any currently pending data here to make sure
2218
  // it is sent before closing the stream. If it returns non-zero then we need
2219
  // to wait until the current write finishes and try again to avoid nghttp2
2220
  // behaviour where it prioritizes RstStream over everything else.
2221
122
  if (session_->SendPendingData() != 0) {
2222
8
    session_->AddPendingRstStream(id_);
2223
8
    return;
2224
  }
2225
2226
114
  FlushRstStream();
2227
}
2228
2229
123
void Http2Stream::FlushRstStream() {
2230
123
  if (is_destroyed())
2231
1
    return;
2232
244
  Http2Scope h2scope(this);
2233
122
  CHECK_EQ(nghttp2_submit_rst_stream(
2234
      session_->session(),
2235
      NGHTTP2_FLAG_NONE,
2236
      id_,
2237
      code_), 0);
2238
}
2239
2240
2241
// Submit a push promise and create the associated Http2Stream if successful.
2242
9
Http2Stream* Http2Stream::SubmitPushPromise(const Http2Headers& headers,
2243
                                            int32_t* ret,
2244
                                            int options) {
2245
9
  CHECK(!this->is_destroyed());
2246
9
  Http2Scope h2scope(this);
2247
9
  Debug(this, "sending push promise");
2248
9
  *ret = nghttp2_submit_push_promise(
2249
      session_->session(),
2250
      NGHTTP2_FLAG_NONE,
2251
      id_,
2252
      headers.data(),
2253
      headers.length(),
2254
      nullptr);
2255
9
  CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
2256
9
  Http2Stream* stream = nullptr;
2257
9
  if (*ret > 0) {
2258
9
    stream = Http2Stream::New(
2259
        session_.get(), *ret, NGHTTP2_HCAT_HEADERS, options);
2260
  }
2261
2262
9
  return stream;
2263
}
2264
2265
// Switch the StreamBase into flowing mode to begin pushing chunks of data
2266
// out to JS land.
2267
23486
int Http2Stream::ReadStart() {
2268
23486
  Http2Scope h2scope(this);
2269
23486
  CHECK(!this->is_destroyed());
2270
23486
  set_reading();
2271
2272
23486
  Debug(this, "reading starting");
2273
2274
  // Tell nghttp2 about our consumption of the data that was handed
2275
  // off to JS land.
2276
23486
  nghttp2_session_consume_stream(
2277
      session_->session(),
2278
      id_,
2279
      inbound_consumed_data_while_paused_);
2280
23486
  inbound_consumed_data_while_paused_ = 0;
2281
2282
23486
  return 0;
2283
}
2284
2285
// Switch the StreamBase into paused mode.
2286
17757
int Http2Stream::ReadStop() {
2287
17757
  CHECK(!this->is_destroyed());
2288
17757
  if (!is_reading())
2289
2235
    return 0;
2290
15522
  set_paused();
2291
15522
  Debug(this, "reading stopped");
2292
15522
  return 0;
2293
}
2294
2295
// The Http2Stream class is a subclass of StreamBase. The DoWrite method
2296
// receives outbound chunks of data to send as outbound DATA frames. These
2297
// are queued in an internal linked list of uv_buf_t structs that are sent
2298
// when nghttp2 is ready to serialize the data frame.
2299
//
2300
// Queue the given set of uv_but_t handles for writing to an
2301
// nghttp2_stream. The WriteWrap's Done callback will be invoked once the
2302
// chunks of data have been flushed to the underlying nghttp2_session.
2303
// Note that this does *not* mean that the data has been flushed
2304
// to the socket yet.
2305
4017
int Http2Stream::DoWrite(WriteWrap* req_wrap,
2306
                         uv_buf_t* bufs,
2307
                         size_t nbufs,
2308
                         uv_stream_t* send_handle) {
2309
4017
  CHECK_NULL(send_handle);
2310
8034
  Http2Scope h2scope(this);
2311

4017
  if (!is_writable() || is_destroyed()) {
2312
    req_wrap->Done(UV_EOF);
2313
    return 0;
2314
  }
2315
4017
  Debug(this, "queuing %d buffers to send", nbufs);
2316
31781
  for (size_t i = 0; i < nbufs; ++i) {
2317
    // Store the req_wrap on the last write info in the queue, so that it is
2318
    // only marked as finished once all buffers associated with it are finished.
2319
27764
    queue_.emplace(NgHttp2StreamWrite {
2320
55528
      BaseObjectPtr<AsyncWrap>(
2321
27764
          i == nbufs - 1 ? req_wrap->GetAsyncWrap() : nullptr),
2322
27764
      bufs[i]
2323
    });
2324
27764
    IncrementAvailableOutboundLength(bufs[i].len);
2325
  }
2326
4017
  CHECK_NE(nghttp2_session_resume_data(
2327
      session_->session(),
2328
      id_), NGHTTP2_ERR_NOMEM);
2329
4017
  return 0;
2330
}
2331
2332
// Ads a header to the Http2Stream. Note that the header name and value are
2333
// provided using a buffer structure provided by nghttp2 that allows us to
2334
// avoid unnecessary memcpy's. Those buffers are ref counted. The ref count
2335
// is incremented here and are decremented when the header name and values
2336
// are garbage collected later.
2337
71783
bool Http2Stream::AddHeader(nghttp2_rcbuf* name,
2338
                            nghttp2_rcbuf* value,
2339
                            uint8_t flags) {
2340
71783
  CHECK(!this->is_destroyed());
2341
2342
71783
  if (Http2RcBufferPointer::IsZeroLength(name))
2343
    return true;  // Ignore empty headers.
2344
2345
143566
  Http2Header header(env(), name, value, flags);
2346
71783
  size_t length = header.length() + 32;
2347
  // A header can only be added if we have not exceeded the maximum number
2348
  // of headers and the session has memory available for it.
2349
71783
  if (!session_->has_available_session_memory(length) ||
2350

143565
      current_headers_.size() == max_header_pairs_ ||
2351
71782
      current_headers_length_ + length > max_header_length_) {
2352
3
    return false;
2353
  }
2354
2355
71780
  if (statistics_.first_header == 0)
2356
23433
    statistics_.first_header = uv_hrtime();
2357
2358
71780
  current_headers_.push_back(std::move(header));
2359
2360
71780
  current_headers_length_ += length;
2361
71780
  session_->IncrementCurrentSessionMemory(length);
2362
71780
  return true;
2363
}
2364
2365
// A Provider is the thing that provides outbound DATA frame data.
2366
11711
Http2Stream::Provider::Provider(Http2Stream* stream, int options) {
2367
11711
  CHECK(!stream->is_destroyed());
2368
11711
  provider_.source.ptr = stream;
2369
11711
  empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
2370
11711
}
2371
2372
11762
Http2Stream::Provider::Provider(int options) {
2373
11762
  provider_.source.ptr = nullptr;
2374
11762
  empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
2375
11762
}
2376
2377
93892
Http2Stream::Provider::~Provider() {
2378
46946
  provider_.source.ptr = nullptr;
2379
}
2380
2381
// The Stream Provider pulls data from a linked list of uv_buf_t structs
2382
// built via the StreamBase API and the Streams js API.
2383
11762
Http2Stream::Provider::Stream::Stream(int options)
2384
11762
    : Http2Stream::Provider(options) {
2385
11762
  provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
2386
11762
}
2387
2388
11711
Http2Stream::Provider::Stream::Stream(Http2Stream* stream, int options)
2389
11711
    : Http2Stream::Provider(stream, options) {
2390
11711
  provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
2391
11711
}
2392
2393
27919
ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
2394
                                              int32_t id,
2395
                                              uint8_t* buf,
2396
                                              size_t length,
2397
                                              uint32_t* flags,
2398
                                              nghttp2_data_source* source,
2399
                                              void* user_data) {
2400
27919
  Http2Session* session = static_cast<Http2Session*>(user_data);
2401
  Debug(session, "reading outbound data for stream %d", id);
2402
55838
  BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
2403
27919
  if (!stream) return 0;
2404
27919
  if (stream->statistics_.first_byte_sent == 0)
2405
12736
    stream->statistics_.first_byte_sent = uv_hrtime();
2406
27919
  CHECK_EQ(id, stream->id());
2407
2408
27919
  size_t amount = 0;          // amount of data being sent in this data frame.
2409
2410
  // Remove all empty chunks from the head of the queue.
2411
  // This is done here so that .write('', cb) is still a meaningful way to
2412
  // find out when the HTTP2 stream wants to consume data, and because the
2413
  // StreamBase API allows empty input chunks.
2414

27924
  while (!stream->queue_.empty() && stream->queue_.front().buf.len == 0) {
2415
    BaseObjectPtr<AsyncWrap> finished =
2416
10
        std::move(stream->queue_.front().req_wrap);
2417
5
    stream->queue_.pop();
2418
5
    if (finished)
2419
3
      WriteWrap::FromObject(finished)->Done(0);
2420
  }
2421
2422
27919
  if (!stream->queue_.empty()) {
2423
    Debug(session, "stream %d has pending outbound data", id);
2424
13877
    amount = std::min(stream->available_outbound_length_, length);
2425
    Debug(session, "sending %d bytes for data frame on stream %d", amount, id);
2426
13877
    if (amount > 0) {
2427
      // Just return the length, let Http2Session::OnSendData take care of
2428
      // actually taking the buffers out of the queue.
2429
13877
      *flags |= NGHTTP2_DATA_FLAG_NO_COPY;
2430
13877
      stream->DecrementAvailableOutboundLength(amount);
2431
    }
2432
  }
2433
2434

27919
  if (amount == 0 && stream->is_writable()) {
2435
2678
    CHECK(stream->queue_.empty());
2436
    Debug(session, "deferring stream %d", id);
2437
2678
    stream->EmitWantsWrite(length);
2438

2678
    if (stream->available_outbound_length_ > 0 || !stream->is_writable()) {
2439
      // EmitWantsWrite() did something interesting synchronously, restart:
2440
      return OnRead(handle, id, buf, length, flags, source, user_data);
2441
    }
2442
2678
    return NGHTTP2_ERR_DEFERRED;
2443
  }
2444
2445

25241
  if (stream->available_outbound_length_ == 0 && !stream->is_writable()) {
2446
    Debug(session, "no more data for stream %d", id);
2447
12724
    *flags |= NGHTTP2_DATA_FLAG_EOF;
2448
12724
    if (stream->has_trailers()) {
2449
37
      *flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
2450
37
      stream->OnTrailers();
2451
    }
2452
  }
2453
2454
25241
  stream->statistics_.sent_bytes += amount;
2455
25241
  return amount;
2456
}
2457
2458
27764
void Http2Stream::IncrementAvailableOutboundLength(size_t amount) {
2459
27764
  available_outbound_length_ += amount;
2460
27764
  session_->IncrementCurrentSessionMemory(amount);
2461
27764
}
2462
2463
13877
void Http2Stream::DecrementAvailableOutboundLength(size_t amount) {
2464
13877
  available_outbound_length_ -= amount;
2465
13877
  session_->DecrementCurrentSessionMemory(amount);
2466
13877
}
2467
2468
2469
// Implementation of the JavaScript API
2470
2471
// Fetches the string description of a nghttp2 error code and passes that
2472
// back to JS land
2473
62
void HttpErrorString(const FunctionCallbackInfo<Value>& args) {
2474
62
  Environment* env = Environment::GetCurrent(args);
2475
124
  uint32_t val = args[0]->Uint32Value(env->context()).ToChecked();
2476
62
  args.GetReturnValue().Set(
2477
      OneByteString(
2478
          env->isolate(),
2479
62
          reinterpret_cast<const uint8_t*>(nghttp2_strerror(val))));
2480
62
}
2481
2482
2483
// Serializes the settings object into a Buffer instance that
2484
// would be suitable, for instance, for creating the Base64
2485
// output for an HTTP2-Settings header field.
2486
17
void PackSettings(const FunctionCallbackInfo<Value>& args) {
2487
17
  Http2State* state = Environment::GetBindingData<Http2State>(args);
2488
17
  args.GetReturnValue().Set(Http2Settings::Pack(state));
2489
17
}
2490
2491
// A TypedArray instance is shared between C++ and JS land to contain the
2492
// default SETTINGS. RefreshDefaultSettings updates that TypedArray with the
2493
// default values.
2494
6
void RefreshDefaultSettings(const FunctionCallbackInfo<Value>& args) {
2495
6
  Http2State* state = Environment::GetBindingData<Http2State>(args);
2496
6
  Http2Settings::RefreshDefaults(state);
2497
6
}
2498
2499
// Sets the next stream ID the Http2Session. If successful, returns true.
2500
1
void Http2Session::SetNextStreamID(const FunctionCallbackInfo<Value>& args) {
2501
1
  Environment* env = Environment::GetCurrent(args);
2502
  Http2Session* session;
2503
1
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2504
1
  int32_t id = args[0]->Int32Value(env->context()).ToChecked();
2505
1
  if (nghttp2_session_set_next_stream_id(session->session(), id) < 0) {
2506
    Debug(session, "failed to set next stream id to %d", id);
2507
    return args.GetReturnValue().Set(false);
2508
  }
2509
1
  args.GetReturnValue().Set(true);
2510
1
  Debug(session, "set next stream id to %d", id);
2511
}
2512
2513
// Set local window size (local endpoints's window size) to the given
2514
// window_size for the stream denoted by 0.
2515
// This function returns 0 if it succeeds, or one of a negative codes
2516
3
void Http2Session::SetLocalWindowSize(
2517
    const FunctionCallbackInfo<Value>& args) {
2518
3
  Environment* env = Environment::GetCurrent(args);
2519
  Http2Session* session;
2520
3
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2521
2522
3
  int32_t window_size = args[0]->Int32Value(env->context()).ToChecked();
2523
2524
3
  int result = nghttp2_session_set_local_window_size(
2525
      session->session(), NGHTTP2_FLAG_NONE, 0, window_size);
2526
2527
3
  args.GetReturnValue().Set(result);
2528
2529
3
  Debug(session, "set local window size to %d", window_size);
2530
}
2531
2532
// A TypedArray instance is shared between C++ and JS land to contain the
2533
// SETTINGS (either remote or local). RefreshSettings updates the current
2534
// values established for each of the settings so those can be read in JS land.
2535
template <get_setting fn>
2536
1130
void Http2Session::RefreshSettings(const FunctionCallbackInfo<Value>& args) {
2537
  Http2Session* session;
2538
1130
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2539
1130
  Http2Settings::Update(session, fn);
2540
1130
  Debug(session, "settings refreshed for session");
2541
}
2542
2543
// A TypedArray instance is shared between C++ and JS land to contain state
2544
// information of the current Http2Session. This updates the values in the
2545
// TypedArray so those can be read in JS land.
2546
12
void Http2Session::RefreshState(const FunctionCallbackInfo<Value>& args) {
2547
  Http2Session* session;
2548
12
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2549
12
  Debug(session, "refreshing state");
2550
2551
12
  AliasedFloat64Array& buffer = session->http2_state()->session_state_buffer;
2552
2553
12
  nghttp2_session* s = session->session();
2554
2555
  buffer[IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE] =
2556
12
      nghttp2_session_get_effective_local_window_size(s);
2557
  buffer[IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH] =
2558
12
      nghttp2_session_get_effective_recv_data_length(s);
2559
  buffer[IDX_SESSION_STATE_NEXT_STREAM_ID] =
2560
12
      nghttp2_session_get_next_stream_id(s);
2561
  buffer[IDX_SESSION_STATE_LOCAL_WINDOW_SIZE] =
2562
12
      nghttp2_session_get_local_window_size(s);
2563
  buffer[IDX_SESSION_STATE_LAST_PROC_STREAM_ID] =
2564
12
      nghttp2_session_get_last_proc_stream_id(s);
2565
  buffer[IDX_SESSION_STATE_REMOTE_WINDOW_SIZE] =
2566
12
      nghttp2_session_get_remote_window_size(s);
2567
  buffer[IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE] =
2568
12
      static_cast<double>(nghttp2_session_get_outbound_queue_size(s));
2569
  buffer[IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE] =
2570
12
      static_cast<double>(nghttp2_session_get_hd_deflate_dynamic_table_size(s));
2571
  buffer[IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE] =
2572
12
      static_cast<double>(nghttp2_session_get_hd_inflate_dynamic_table_size(s));
2573
}
2574
2575
2576
// Constructor for new Http2Session instances.
2577
668
void Http2Session::New(const FunctionCallbackInfo<Value>& args) {
2578
668
  Http2State* state = Environment::GetBindingData<Http2State>(args);
2579
668
  Environment* env = state->env();
2580
668
  CHECK(args.IsConstructCall());
2581
  SessionType type =
2582
      static_cast<SessionType>(
2583
1336
          args[0]->Int32Value(env->context()).ToChecked());
2584
668
  Http2Session* session = new Http2Session(state, args.This(), type);
2585
668
  session->get_async_id();  // avoid compiler warning
2586
  Debug(session, "session created");
2587
668
}
2588
2589
2590
// Binds the Http2Session with a StreamBase used for i/o
2591
668
void Http2Session::Consume(const FunctionCallbackInfo<Value>& args) {
2592
  Http2Session* session;
2593
668
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2594
668
  CHECK(args[0]->IsObject());
2595
2004
  session->Consume(args[0].As<Object>());
2596
}
2597
2598
// Destroys the Http2Session instance and renders it unusable
2599
649
void Http2Session::Destroy(const FunctionCallbackInfo<Value>& args) {
2600
  Http2Session* session;
2601
649
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2602
649
  Debug(session, "destroying session");
2603
649
  Environment* env = Environment::GetCurrent(args);
2604
649
  Local<Context> context = env->context();
2605
2606
649
  uint32_t code = args[0]->Uint32Value(context).ToChecked();
2607
1298
  session->Close(code, args[1]->IsTrue());
2608
}
2609
2610
// Submits a new request on the Http2Session and returns either an error code
2611
// or the Http2Stream object.
2612
11762
void Http2Session::Request(const FunctionCallbackInfo<Value>& args) {
2613
  Http2Session* session;
2614
11763
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2615
11762
  Environment* env = session->env();
2616
2617
23524
  Local<Array> headers = args[0].As<Array>();
2618
11762
  int32_t options = args[1]->Int32Value(env->context()).ToChecked();
2619
2620
11762
  Debug(session, "request submitted");
2621
2622
11762
  int32_t ret = 0;
2623
  Http2Stream* stream =
2624
11762
      session->Http2Session::SubmitRequest(
2625
11762
          Http2Priority(env, args[2], args[3], args[4]),
2626
23524
          Http2Headers(env, headers),
2627
          &ret,
2628
          static_cast<int>(options));
2629
2630

11762
  if (ret <= 0 || stream == nullptr) {
2631
2
    Debug(session, "could not submit request: %s", nghttp2_strerror(ret));
2632
2
    return args.GetReturnValue().Set(ret);
2633
  }
2634
2635
23522
  Debug(session, "request submitted, new stream id %d", stream->id());
2636
23522
  args.GetReturnValue().Set(stream->object());
2637
}
2638
2639
// Submits a GOAWAY frame to signal that the Http2Session is in the process
2640
// of shutting down. Note that this function does not actually alter the
2641
// state of the Http2Session, it's simply a notification.
2642
589
void Http2Session::Goaway(uint32_t code,
2643
                          int32_t lastStreamID,
2644
                          const uint8_t* data,
2645
                          size_t len) {
2646
589
  if (is_destroyed())
2647
    return;
2648
2649
1178
  Http2Scope h2scope(this);
2650
  // the last proc stream id is the most recently created Http2Stream.
2651
589
  if (lastStreamID <= 0)
2652
589
    lastStreamID = nghttp2_session_get_last_proc_stream_id(session_.get());
2653
589
  Debug(this, "submitting goaway");
2654
589
  nghttp2_submit_goaway(session_.get(), NGHTTP2_FLAG_NONE,
2655
                        lastStreamID, code, data, len);
2656
}
2657
2658
// Submits a GOAWAY frame to signal that the Http2Session is in the process
2659
// of shutting down. The opaque data argument is an optional TypedArray that
2660
// can be used to send debugging data to the connected peer.
2661
589
void Http2Session::Goaway(const FunctionCallbackInfo<Value>& args) {
2662
589
  Environment* env = Environment::GetCurrent(args);
2663
589
  Local<Context> context = env->context();
2664
  Http2Session* session;
2665
589
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2666
2667
1178
  uint32_t code = args[0]->Uint32Value(context).ToChecked();
2668
589
  int32_t lastStreamID = args[1]->Int32Value(context).ToChecked();
2669
589
  ArrayBufferViewContents<uint8_t> opaque_data;
2670
2671
589
  if (args[2]->IsArrayBufferView()) {
2672
2
    opaque_data.Read(args[2].As<ArrayBufferView>());
2673
  }
2674
2675
589
  session->Goaway(code, lastStreamID, opaque_data.data(), opaque_data.length());
2676
}
2677
2678
// Update accounting of data chunks. This is used primarily to manage timeout
2679
// logic when using the FD Provider.
2680
10
void Http2Session::UpdateChunksSent(const FunctionCallbackInfo<Value>& args) {
2681
10
  Environment* env = Environment::GetCurrent(args);
2682
10
  Isolate* isolate = env->isolate();
2683
10
  HandleScope scope(isolate);
2684
  Http2Session* session;
2685
10
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2686
2687
10
  uint32_t length = session->chunks_sent_since_last_write_;
2688
2689
10
  session->object()->Set(env->context(),
2690
                         env->chunks_sent_since_last_write_string(),
2691
40
                         Integer::NewFromUnsigned(isolate, length)).Check();
2692
2693
20
  args.GetReturnValue().Set(length);
2694
}
2695
2696
// Submits an RST_STREAM frame effectively closing the Http2Stream. Note that
2697
// this *WILL* alter the state of the stream, causing the OnStreamClose
2698
// callback to the triggered.
2699
120
void Http2Stream::RstStream(const FunctionCallbackInfo<Value>& args) {
2700
120
  Environment* env = Environment::GetCurrent(args);
2701
120
  Local<Context> context = env->context();
2702
  Http2Stream* stream;
2703
120
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2704
120
  uint32_t code = args[0]->Uint32Value(context).ToChecked();
2705
120
  Debug(stream, "sending rst_stream with code %d", code);
2706
120
  stream->SubmitRstStream(code);
2707
}
2708
2709
// Initiates a response on the Http2Stream using the StreamBase API to provide
2710
// outbound DATA frames.
2711
11685
void Http2Stream::Respond(const FunctionCallbackInfo<Value>& args) {
2712
11685
  Environment* env = Environment::GetCurrent(args);
2713
  Http2Stream* stream;
2714
11685
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2715
2716
23370
  Local<Array> headers = args[0].As<Array>();
2717
23370
  int32_t options = args[1]->Int32Value(env->context()).ToChecked();
2718
2719
11685
  args.GetReturnValue().Set(
2720
      stream->SubmitResponse(
2721
23370
          Http2Headers(env, headers),
2722
          static_cast<int>(options)));
2723
11685
  Debug(stream, "response submitted");
2724
}
2725
2726
2727
// Submits informational headers on the Http2Stream
2728
5
void Http2Stream::Info(const FunctionCallbackInfo<Value>& args) {
2729
5
  Environment* env = Environment::GetCurrent(args);
2730
  Http2Stream* stream;
2731
5
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2732
2733
10
  Local<Array> headers = args[0].As<Array>();
2734
2735
10
  args.GetReturnValue().Set(stream->SubmitInfo(Http2Headers(env, headers)));
2736
}
2737
2738
// Submits trailing headers on the Http2Stream
2739
31
void Http2Stream::Trailers(const FunctionCallbackInfo<Value>& args) {
2740
31
  Environment* env = Environment::GetCurrent(args);
2741
  Http2Stream* stream;
2742
31
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2743
2744
62
  Local<Array> headers = args[0].As<Array>();
2745
2746
31
  args.GetReturnValue().Set(
2747
62
      stream->SubmitTrailers(Http2Headers(env, headers)));
2748
}
2749
2750
// Grab the numeric id of the Http2Stream
2751
11770
void Http2Stream::GetID(const FunctionCallbackInfo<Value>& args) {
2752
  Http2Stream* stream;
2753
11770
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2754
23540
  args.GetReturnValue().Set(stream->id());
2755
}
2756
2757
// Destroy the Http2Stream, rendering it no longer usable
2758
23495
void Http2Stream::Destroy(const FunctionCallbackInfo<Value>& args) {
2759
  Http2Stream* stream;
2760
23495
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2761
23495
  Debug(stream, "destroying stream");
2762
23495
  stream->Destroy();
2763
}
2764
2765
// Initiate a Push Promise and create the associated Http2Stream
2766
9
void Http2Stream::PushPromise(const FunctionCallbackInfo<Value>& args) {
2767
9
  Environment* env = Environment::GetCurrent(args);
2768
  Http2Stream* parent;
2769
9
  ASSIGN_OR_RETURN_UNWRAP(&parent, args.Holder());
2770
2771
18
  Local<Array> headers = args[0].As<Array>();
2772
9
  int32_t options = args[1]->Int32Value(env->context()).ToChecked();
2773
2774
9
  Debug(parent, "creating push promise");
2775
2776
9
  int32_t ret = 0;
2777
  Http2Stream* stream =
2778
9
      parent->SubmitPushPromise(
2779
18
          Http2Headers(env, headers),
2780
          &ret,
2781
          static_cast<int>(options));
2782
2783

9
  if (ret <= 0 || stream == nullptr) {
2784
    Debug(parent, "failed to create push stream: %d", ret);
2785
    return args.GetReturnValue().Set(ret);
2786
  }
2787
18
  Debug(parent, "push stream %d created", stream->id());
2788
18
  args.GetReturnValue().Set(stream->object());
2789
}
2790
2791
// Send a PRIORITY frame
2792
6
void Http2Stream::Priority(const FunctionCallbackInfo<Value>& args) {
2793
6
  Environment* env = Environment::GetCurrent(args);
2794
  Http2Stream* stream;
2795
6
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2796
2797

18
  CHECK_EQ(stream->SubmitPriority(
2798
      Http2Priority(env, args[0], args[1], args[2]),
2799
      args[3]->IsTrue()), 0);
2800
6
  Debug(stream, "priority submitted");
2801
}
2802
2803
// A TypedArray shared by C++ and JS land is used to communicate state
2804
// information about the Http2Stream. This updates the values in that
2805
// TypedArray so that the state can be read by JS.
2806
11
void Http2Stream::RefreshState(const FunctionCallbackInfo<Value>& args) {
2807
  Http2Stream* stream;
2808
11
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2809
2810
11
  Debug(stream, "refreshing state");
2811
2812
11
  CHECK_NOT_NULL(stream->session());
2813
  AliasedFloat64Array& buffer =
2814
11
      stream->session()->http2_state()->stream_state_buffer;
2815
2816
11
  nghttp2_stream* str = stream->stream();
2817
11
  nghttp2_session* s = stream->session()->session();
2818
2819
11
  if (str == nullptr) {
2820
1
    buffer[IDX_STREAM_STATE] = NGHTTP2_STREAM_STATE_IDLE;
2821
    buffer[IDX_STREAM_STATE_WEIGHT] =
2822
        buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2823
        buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2824
        buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2825
1
        buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] = 0;
2826
  } else {
2827
    buffer[IDX_STREAM_STATE] =
2828
10
        nghttp2_stream_get_state(str);
2829
    buffer[IDX_STREAM_STATE_WEIGHT] =
2830
10
        nghttp2_stream_get_weight(str);
2831
    buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2832
10
        nghttp2_stream_get_sum_dependency_weight(str);
2833
    buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2834
10
        nghttp2_session_get_stream_local_close(s, stream->id());
2835
    buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2836
10
        nghttp2_session_get_stream_remote_close(s, stream->id());
2837
    buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] =
2838
10
        nghttp2_session_get_stream_local_window_size(s, stream->id());
2839
  }
2840
}
2841
2842
5
void Http2Session::AltSvc(int32_t id,
2843
                          uint8_t* origin,
2844
                          size_t origin_len,
2845
                          uint8_t* value,
2846
                          size_t value_len) {
2847
10
  Http2Scope h2scope(this);
2848
5
  CHECK_EQ(nghttp2_submit_altsvc(session_.get(), NGHTTP2_FLAG_NONE, id,
2849
                                 origin, origin_len, value, value_len), 0);
2850
5
}
2851
2852
5
void Http2Session::Origin(const Origins& origins) {
2853
10
  Http2Scope h2scope(this);
2854
5
  CHECK_EQ(nghttp2_submit_origin(
2855
      session_.get(),
2856
      NGHTTP2_FLAG_NONE,
2857
      *origins,
2858
      origins.length()), 0);
2859
5
}
2860
2861
// Submits an AltSvc frame to be sent to the connected peer.
2862
5
void Http2Session::AltSvc(const FunctionCallbackInfo<Value>& args) {
2863
5
  Environment* env = Environment::GetCurrent(args);
2864
  Http2Session* session;
2865
5
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2866
2867
10
  int32_t id = args[0]->Int32Value(env->context()).ToChecked();
2868
2869
  // origin and value are both required to be ASCII, handle them as such.
2870
10
  Local<String> origin_str = args[1]->ToString(env->context()).ToLocalChecked();
2871
10
  Local<String> value_str = args[2]->ToString(env->context()).ToLocalChecked();
2872
2873

10
  if (origin_str.IsEmpty() || value_str.IsEmpty())
2874
    return;
2875
2876
5
  size_t origin_len = origin_str->Length();
2877
5
  size_t value_len = value_str->Length();
2878
2879
5
  CHECK_LE(origin_len + value_len, 16382);  // Max permitted for ALTSVC
2880
  // Verify that origin len != 0 if stream id == 0, or
2881
  // that origin len == 0 if stream id != 0
2882



5
  CHECK((origin_len != 0 && id == 0) || (origin_len == 0 && id != 0));
2883
2884
10
  MaybeStackBuffer<uint8_t> origin(origin_len);
2885
10
  MaybeStackBuffer<uint8_t> value(value_len);
2886
5
  origin_str->WriteOneByte(env->isolate(), *origin);
2887
5
  value_str->WriteOneByte(env->isolate(), *value);
2888
2889
5
  session->AltSvc(id, *origin, origin_len, *value, value_len);
2890
}
2891
2892
5
void Http2Session::Origin(const FunctionCallbackInfo<Value>& args) {
2893
5
  Environment* env = Environment::GetCurrent(args);
2894
5
  Local<Context> context = env->context();
2895
  Http2Session* session;
2896
5
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2897
2898
10
  Local<String> origin_string = args[0].As<String>();
2899
5
  size_t count = args[1]->Int32Value(context).ToChecked();
2900
2901
5
  session->Origin(Origins(env, origin_string, count));
2902
}
2903
2904
// Submits a PING frame to be sent to the connected peer.
2905
13
void Http2Session::Ping(const FunctionCallbackInfo<Value>& args) {
2906
  Http2Session* session;
2907
13
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2908
2909
  // A PING frame may have exactly 8 bytes of payload data. If not provided,
2910
  // then the current hrtime will be used as the payload.
2911
13
  ArrayBufferViewContents<uint8_t, 8> payload;
2912
13
  if (args[0]->IsArrayBufferView()) {
2913
12
    payload.Read(args[0].As<ArrayBufferView>());
2914
6
    CHECK_EQ(payload.length(), 8);
2915
  }
2916
2917
13
  CHECK(args[1]->IsFunction());
2918
13
  args.GetReturnValue().Set(
2919
52
      session->AddPing(payload.data(), args[1].As<Function>()));
2920
}
2921
2922
// Submits a SETTINGS frame for the Http2Session
2923
678
void Http2Session::Settings(const FunctionCallbackInfo<Value>& args) {
2924
  Http2Session* session;
2925
678
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2926
678
  CHECK(args[0]->IsFunction());
2927

2712
  args.GetReturnValue().Set(session->AddSettings(args[0].As<Function>()));
2928
}
2929
2930
661
BaseObjectPtr<Http2Ping> Http2Session::PopPing() {
2931
661
  BaseObjectPtr<Http2Ping> ping;
2932
661
  if (!outstanding_pings_.empty()) {
2933
11
    ping = std::move(outstanding_pings_.front());
2934
11
    outstanding_pings_.pop();
2935
11
    DecrementCurrentSessionMemory(sizeof(*ping));
2936
  }
2937
661
  return ping;
2938
}
2939
2940
13
bool Http2Session::AddPing(const uint8_t* payload, Local<Function> callback) {
2941
  Local<Object> obj;
2942
13
  if (!env()->http2ping_constructor_template()
2943
13
          ->NewInstance(env()->context())
2944
13
              .ToLocal(&obj)) {
2945
    return false;
2946
  }
2947
2948
  BaseObjectPtr<Http2Ping> ping =
2949
26
      MakeDetachedBaseObject<Http2Ping>(this, obj, callback);
2950
13
  if (!ping)
2951
    return false;
2952
2953
13
  if (outstanding_pings_.size() == max_outstanding_pings_) {
2954
2
    ping->Done(false);
2955
2
    return false;
2956
  }
2957
2958
11
  IncrementCurrentSessionMemory(sizeof(*ping));
2959
  // The Ping itself is an Async resource. When the acknowledgement is received,
2960
  // the callback will be invoked and a notification sent out to JS land. The
2961
  // notification will include the duration of the ping, allowing the round
2962
  // trip to be measured.
2963
11
  ping->Send(payload);
2964
2965
11
  outstanding_pings_.emplace(std::move(ping));
2966
11
  return true;
2967
}
2968
2969
540
BaseObjectPtr<Http2Settings> Http2Session::PopSettings() {
2970
540
  BaseObjectPtr<Http2Settings> settings;
2971
540
  if (!outstanding_settings_.empty()) {
2972
540
    settings = std::move(outstanding_settings_.front());
2973
540
    outstanding_settings_.pop();
2974
540
    DecrementCurrentSessionMemory(sizeof(*settings));
2975
  }
2976
540
  return settings;
2977
}
2978
2979
678
bool Http2Session::AddSettings(Local<Function> callback) {
2980
  Local<Object> obj;
2981
678
  if (!env()->http2settings_constructor_template()
2982
678
          ->NewInstance(env()->context())
2983
678
              .ToLocal(&obj)) {
2984
    return false;
2985
  }
2986
2987
  BaseObjectPtr<Http2Settings> settings =
2988
1356
      MakeDetachedBaseObject<Http2Settings>(this, obj, callback, 0);
2989
678
  if (!settings)
2990
    return false;
2991
2992
678
  if (outstanding_settings_.size() == max_outstanding_settings_) {
2993
2
    settings->Done(false);
2994
2
    return false;
2995
  }
2996
2997
676
  IncrementCurrentSessionMemory(sizeof(*settings));
2998
676
  settings->Send();
2999
676
  outstanding_settings_.emplace(std::move(settings));
3000
676
  return true;
3001
}
3002
3003
13
Http2Ping::Http2Ping(
3004
    Http2Session* session,
3005
    Local<Object> obj,
3006
13
    Local<Function> callback)
3007
    : AsyncWrap(session->env(), obj, AsyncWrap::PROVIDER_HTTP2PING),
3008
      session_(session),
3009
13
      startTime_(uv_hrtime()) {
3010
13
  callback_.Reset(env()->isolate(), callback);
3011
13
}
3012
3013
void Http2Ping::MemoryInfo(MemoryTracker* tracker) const {
3014
  tracker->TrackField("callback", callback_);
3015
}
3016
3017
13
Local<Function> Http2Ping::callback() const {
3018
26
  return callback_.Get(env()->isolate());
3019
}
3020
3021
11
void Http2Ping::Send(const uint8_t* payload) {
3022
11
  CHECK(session_);
3023
  uint8_t data[8];
3024
11
  if (payload == nullptr) {
3025
5
    memcpy(&data, &startTime_, arraysize(data));
3026
5
    payload = data;
3027
  }
3028
22
  Http2Scope h2scope(session_.get());
3029
11
  CHECK_EQ(nghttp2_submit_ping(
3030
      session_->session(),
3031
      NGHTTP2_FLAG_NONE,
3032
      payload), 0);
3033
11
}
3034
3035
13
void Http2Ping::Done(bool ack, const uint8_t* payload) {
3036
13
  uint64_t duration_ns = uv_hrtime() - startTime_;
3037
13
  double duration_ms = duration_ns / 1e6;
3038
13
  if (session_) session_->statistics_.ping_rtt = duration_ns;
3039
3040
13
  Isolate* isolate = env()->isolate();
3041
26
  HandleScope handle_scope(isolate);
3042
26
  Context::Scope context_scope(env()->context());
3043
3044
13
  Local<Value> buf = Undefined(isolate);
3045
13
  if (payload != nullptr) {
3046
10
    buf = Buffer::Copy(isolate,
3047
                       reinterpret_cast<const char*>(payload),
3048
10
                       8).ToLocalChecked();
3049
  }
3050
3051
  Local<Value> argv[] = {
3052
    ack ? v8::True(isolate) : v8::False(isolate),
3053
    Number::New(isolate, duration_ms),
3054
    buf
3055
26
  };
3056
13
  MakeCallback(callback(), arraysize(argv), argv);
3057
13
}
3058
3059
1
void Http2Ping::DetachFromSession() {
3060
1
  session_.reset();
3061
1
}
3062
3063
void NgHttp2StreamWrite::MemoryInfo(MemoryTracker* tracker) const {
3064
  if (req_wrap)
3065
    tracker->TrackField("req_wrap", req_wrap);
3066
  tracker->TrackField("buf", buf);
3067
}
3068
3069
248
void SetCallbackFunctions(const FunctionCallbackInfo<Value>& args) {
3070
248
  Environment* env = Environment::GetCurrent(args);
3071
248
  CHECK_EQ(args.Length(), 11);
3072
3073
#define SET_FUNCTION(arg, name)                                               \
3074
  CHECK(args[arg]->IsFunction());                                             \
3075
  env->set_http2session_on_ ## name ## _function(args[arg].As<Function>());
3076
3077

744
  SET_FUNCTION(0, error)
3078

744
  SET_FUNCTION(1, priority)
3079

744
  SET_FUNCTION(2, settings)
3080

744
  SET_FUNCTION(3, ping)
3081

744
  SET_FUNCTION(4, headers)
3082

744
  SET_FUNCTION(5, frame_error)
3083

744
  SET_FUNCTION(6, goaway_data)
3084

744
  SET_FUNCTION(7, altsvc)
3085

744
  SET_FUNCTION(8, origin)
3086

744
  SET_FUNCTION(9, stream_trailers)
3087
744
  SET_FUNCTION(10, stream_close)
3088
3089
#undef SET_FUNCTION
3090
248
}
3091
3092
2
void Http2State::MemoryInfo(MemoryTracker* tracker) const {
3093
2
  tracker->TrackField("root_buffer", root_buffer);
3094
2
}
3095
3096
// TODO(addaleax): Remove once we're on C++17.
3097
constexpr FastStringKey Http2State::type_name;
3098
3099
// Set up the process.binding('http2') binding.
3100
253
void Initialize(Local<Object> target,
3101
                Local<Value> unused,
3102
                Local<Context> context,
3103
                void* priv) {
3104
253
  Environment* env = Environment::GetCurrent(context);
3105
253
  Isolate* isolate = env->isolate();
3106
253
  HandleScope handle_scope(isolate);
3107
3108
253
  Http2State* const state = env->AddBindingData<Http2State>(context, target);
3109
253
  if (state == nullptr) return;
3110
3111
#define SET_STATE_TYPEDARRAY(name, field)             \
3112
  target->Set(context,                                \
3113
              FIXED_ONE_BYTE_STRING(isolate, (name)), \
3114
              (field)).FromJust()
3115
3116
  // Initialize the buffer used to store the session state
3117
1012
  SET_STATE_TYPEDARRAY(
3118
    "sessionState", state->session_state_buffer.GetJSArray());
3119
  // Initialize the buffer used to store the stream state
3120
1012
  SET_STATE_TYPEDARRAY(
3121
    "streamState", state->stream_state_buffer.GetJSArray());
3122
1012
  SET_STATE_TYPEDARRAY(
3123
    "settingsBuffer", state->settings_buffer.GetJSArray());
3124
1012
  SET_STATE_TYPEDARRAY(
3125
    "optionsBuffer", state->options_buffer.GetJSArray());
3126
1012
  SET_STATE_TYPEDARRAY(
3127
    "streamStats", state->stream_stats_buffer.GetJSArray());
3128
1012
  SET_STATE_TYPEDARRAY(
3129
    "sessionStats", state->session_stats_buffer.GetJSArray());
3130
#undef SET_STATE_TYPEDARRAY
3131
3132
759
  NODE_DEFINE_CONSTANT(target, kBitfield);
3133
759
  NODE_DEFINE_CONSTANT(target, kSessionPriorityListenerCount);
3134
759
  NODE_DEFINE_CONSTANT(target, kSessionFrameErrorListenerCount);
3135
759
  NODE_DEFINE_CONSTANT(target, kSessionMaxInvalidFrames);
3136
759
  NODE_DEFINE_CONSTANT(target, kSessionMaxRejectedStreams);
3137
759
  NODE_DEFINE_CONSTANT(target, kSessionUint8FieldCount);
3138
3139
759
  NODE_DEFINE_CONSTANT(target, kSessionHasRemoteSettingsListeners);
3140
759
  NODE_DEFINE_CONSTANT(target, kSessionRemoteSettingsIsUpToDate);
3141
759
  NODE_DEFINE_CONSTANT(target, kSessionHasPingListeners);
3142
506
  NODE_DEFINE_CONSTANT(target, kSessionHasAltsvcListeners);
3143
3144
  // Method to fetch the nghttp2 string description of an nghttp2 error code
3145
253
  env->SetMethod(target, "nghttp2ErrorString", HttpErrorString);
3146
253
  env->SetMethod(target, "refreshDefaultSettings", RefreshDefaultSettings);
3147
253
  env->SetMethod(target, "packSettings", PackSettings);
3148
253
  env->SetMethod(target, "setCallbackFunctions", SetCallbackFunctions);
3149
3150
253
  Local<FunctionTemplate> ping = FunctionTemplate::New(env->isolate());
3151
253
  ping->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Ping"));
3152
253
  ping->Inherit(AsyncWrap::GetConstructorTemplate(env));
3153
253
  Local<ObjectTemplate> pingt = ping->InstanceTemplate();
3154
253
  pingt->SetInternalFieldCount(Http2Ping::kInternalFieldCount);
3155
253
  env->set_http2ping_constructor_template(pingt);
3156
3157
253
  Local<FunctionTemplate> setting = FunctionTemplate::New(env->isolate());
3158
253
  setting->Inherit(AsyncWrap::GetConstructorTemplate(env));
3159
253
  Local<ObjectTemplate> settingt = setting->InstanceTemplate();
3160
253
  settingt->SetInternalFieldCount(AsyncWrap::kInternalFieldCount);
3161
253
  env->set_http2settings_constructor_template(settingt);
3162
3163
253
  Local<FunctionTemplate> stream = FunctionTemplate::New(env->isolate());
3164
253
  env->SetProtoMethod(stream, "id", Http2Stream::GetID);
3165
253
  env->SetProtoMethod(stream, "destroy", Http2Stream::Destroy);
3166
253
  env->SetProtoMethod(stream, "priority", Http2Stream::Priority);
3167
253
  env->SetProtoMethod(stream, "pushPromise", Http2Stream::PushPromise);
3168
253
  env->SetProtoMethod(stream, "info", Http2Stream::Info);
3169
253
  env->SetProtoMethod(stream, "trailers", Http2Stream::Trailers);
3170
253
  env->SetProtoMethod(stream, "respond", Http2Stream::Respond);
3171
253
  env->SetProtoMethod(stream, "rstStream", Http2Stream::RstStream);
3172
253
  env->SetProtoMethod(stream, "refreshState", Http2Stream::RefreshState);
3173
253
  stream->Inherit(AsyncWrap::GetConstructorTemplate(env));
3174
253
  StreamBase::AddMethods(env, stream);
3175
253
  Local<ObjectTemplate> streamt = stream->InstanceTemplate();
3176
253
  streamt->SetInternalFieldCount(StreamBase::kInternalFieldCount);
3177
253
  env->set_http2stream_constructor_template(streamt);
3178
253
  env->SetConstructorFunction(target, "Http2Stream", stream);
3179
3180
  Local<FunctionTemplate> session =
3181
253
      env->NewFunctionTemplate(Http2Session::New);
3182
506
  session->InstanceTemplate()->SetInternalFieldCount(
3183
      Http2Session::kInternalFieldCount);
3184
253
  session->Inherit(AsyncWrap::GetConstructorTemplate(env));
3185
253
  env->SetProtoMethod(session, "origin", Http2Session::Origin);
3186
253
  env->SetProtoMethod(session, "altsvc", Http2Session::AltSvc);
3187
253
  env->SetProtoMethod(session, "ping", Http2Session::Ping);
3188
253
  env->SetProtoMethod(session, "consume", Http2Session::Consume);
3189
253
  env->SetProtoMethod(session, "receive", Http2Session::Receive);
3190
253
  env->SetProtoMethod(session, "destroy", Http2Session::Destroy);
3191
253
  env->SetProtoMethod(session, "goaway", Http2Session::Goaway);
3192
253
  env->SetProtoMethod(session, "settings", Http2Session::Settings);
3193
253
  env->SetProtoMethod(session, "request", Http2Session::Request);
3194
253
  env->SetProtoMethod(session, "setNextStreamID",
3195
                      Http2Session::SetNextStreamID);
3196
253
  env->SetProtoMethod(session, "setLocalWindowSize",
3197
                      Http2Session::SetLocalWindowSize);
3198
253
  env->SetProtoMethod(session, "updateChunksSent",
3199
                      Http2Session::UpdateChunksSent);
3200
253
  env->SetProtoMethod(session, "refreshState", Http2Session::RefreshState);
3201
253
  env->SetProtoMethod(
3202
      session, "localSettings",
3203
      Http2Session::RefreshSettings<nghttp2_session_get_local_settings>);
3204
253
  env->SetProtoMethod(
3205
      session, "remoteSettings",
3206
      Http2Session::RefreshSettings<nghttp2_session_get_remote_settings>);
3207
253
  env->SetConstructorFunction(target, "Http2Session", session);
3208
3209
253
  Local<Object> constants = Object::New(isolate);
3210
3211
  // This does allocate one more slot than needed but it's not used.
3212
#define V(name) FIXED_ONE_BYTE_STRING(isolate, #name),
3213
  Local<Value> error_code_names[] = {
3214
    HTTP2_ERROR_CODES(V)
3215
3542
  };
3216
#undef V
3217
3218
  Local<Array> name_for_error_code =
3219
      Array::New(
3220
          isolate,
3221
          error_code_names,
3222
253
          arraysize(error_code_names));
3223
3224
253
  target->Set(context,
3225
              FIXED_ONE_BYTE_STRING(isolate, "nameForErrorCode"),
3226
759
              name_for_error_code).Check();
3227
3228
#define V(constant) NODE_DEFINE_HIDDEN_CONSTANT(constants, constant);
3229
6831
  HTTP2_HIDDEN_CONSTANTS(V)
3230
#undef V
3231
3232
#define V(constant) NODE_DEFINE_CONSTANT(constants, constant);
3233
26059
  HTTP2_CONSTANTS(V)
3234
#undef V
3235
3236
  // NGHTTP2_DEFAULT_WEIGHT is a macro and not a regular define
3237
  // it won't be set properly on the constants object if included
3238
  // in the HTTP2_CONSTANTS macro.
3239
759
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_DEFAULT_WEIGHT);
3240
3241
#define V(NAME, VALUE)                                          \
3242
  NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_HEADER_" # NAME, VALUE);
3243
64768
  HTTP_KNOWN_HEADERS(V)
3244
#undef V
3245
3246
#define V(NAME, VALUE)                                          \
3247
  NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_METHOD_" # NAME, VALUE);
3248
29854
  HTTP_KNOWN_METHODS(V)
3249
#undef V
3250
3251
#define V(name, _) NODE_DEFINE_CONSTANT(constants, HTTP_STATUS_##name);
3252
32131
  HTTP_STATUS_CODES(V)
3253
#undef V
3254
3255
759
  target->Set(context, env->constants_string(), constants).Check();
3256
}
3257
}  // namespace http2
3258
}  // namespace node
3259
3260
4926
NODE_MODULE_CONTEXT_AWARE_INTERNAL(http2, node::http2::Initialize)