GCC Code Coverage Report
Directory: ../ Exec Total Coverage
File: /home/iojs/build/workspace/node-test-commit-linux-coverage-daily/nodes/benchmark/out/../src/node_http2.cc Lines: 1571 1636 96.0 %
Date: 2021-05-04 04:12:26 Branches: 586 806 72.7 %

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
24320
bool HasHttp2Observer(Environment* env) {
51
24320
  AliasedUint32Array& observers = env->performance_state()->observers;
52
24320
  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
9660
const Http2Session::Callbacks Http2Session::callback_struct_saved[2] = {
61
    Callbacks(false),
62
4830
    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
63492
Http2Scope::Http2Scope(Http2Stream* stream) : Http2Scope(stream->session()) {}
76
77
107808
Http2Scope::Http2Scope(Http2Session* session) : session_(session) {
78
107808
  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

107808
  if (session_->is_in_scope() || session_->is_write_scheduled()) {
83
75080
    session_.reset();
84
75080
    return;
85
  }
86
32728
  session_->set_in_scope();
87
}
88
89
215616
Http2Scope::~Http2Scope() {
90
107808
  if (!session_) return;
91
32728
  session_->set_in_scope(false);
92
32728
  if (!session_->is_write_scheduled())
93
32728
    session_->MaybeScheduleWrite();
94
107808
}
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
666
Http2Options::Http2Options(Http2State* http2_state, SessionType type) {
101
  nghttp2_option* option;
102
666
  CHECK_EQ(nghttp2_option_new(&option), 0);
103
666
  CHECK_NOT_NULL(option);
104
666
  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
666
  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
666
  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
666
  if (type == NGHTTP2_SESSION_CLIENT) {
121
325
    nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ALTSVC);
122
325
    nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ORIGIN);
123
  }
124
125
666
  AliasedUint32Array& buffer = http2_state->options_buffer;
126
666
  uint32_t flags = buffer[IDX_OPTIONS_FLAGS];
127
128
666
  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
666
  if (flags & (1 << IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS)) {
135
2
    nghttp2_option_set_max_reserved_remote_streams(
136
        option,
137
1
        buffer[IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS]);
138
  }
139
140
666
  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
666
  nghttp2_option_set_peer_max_concurrent_streams(option, 100);
148
666
  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
666
  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
666
  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
666
  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
666
  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
666
  if (flags & (1 << IDX_OPTIONS_MAX_SESSION_MEMORY))
197
8
    set_max_session_memory(buffer[IDX_OPTIONS_MAX_SESSION_MEMORY] * 1000000);
198
199
666
  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
666
}
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
693
size_t Http2Settings::Init(
215
    Http2State* http2_state,
216
    nghttp2_settings_entry* entries) {
217
693
  AliasedUint32Array& buffer = http2_state->settings_buffer;
218
693
  uint32_t flags = buffer[IDX_SETTINGS_COUNT];
219
220
693
  size_t count = 0;
221
222
#define V(name) GRABSETTING(entries, count, name);
223
693
  HTTP2_SETTINGS(V)
224
6
#undef V
225
12
226
705
  return count;
227
8
}
228
693
#undef GRABSETTING
229
18
230
6
// The Http2Settings class is used to configure a SETTINGS frame that is
231
6
// to be sent to the connected peer. The settings are set using a TypedArray
232
10
// that is shared with the JavaScript side.
233
1369
Http2Settings::Http2Settings(Http2Session* session,
234
6
                             Local<Object> obj,
235
                             Local<Function> callback,
236
676
                             uint64_t start_time)
237
    : AsyncWrap(session->env(), obj, PROVIDER_HTTP2SETTINGS),
238
      session_(session),
239
1352
      startTime_(start_time) {
240
676
  callback_.Reset(env()->isolate(), callback);
241
676
  count_ = Init(session->http2_state(), entries_);
242
676
}
243
244
542
Local<Function> Http2Settings::callback() const {
245
1084
  return callback_.Get(env()->isolate());
246
}
247
248
4
void Http2Settings::MemoryInfo(MemoryTracker* tracker) const {
249
4
  tracker->TrackField("callback", callback_);
250
4
}
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
34
  AllocatedBuffer buffer = AllocatedBuffer::AllocateManaged(env, size);
272
  ssize_t ret =
273
      nghttp2_pack_settings_payload(
274
17
          reinterpret_cast<uint8_t*>(buffer.data()),
275
          size,
276
          entries,
277
17
          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
1130
#undef V
293
2260
}
294
1130
295
565
// Initializes the shared TypedArray with the default settings values.
296
571
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
6
#undef V
307
6
308
12
  buffer[IDX_SETTINGS_COUNT] = flags;
309
18
}
310
18
311
12
312
680
void Http2Settings::Send() {
313
1348
  Http2Scope h2scope(session_.get());
314
674
  CHECK_EQ(nghttp2_submit_settings(
315
      session_->session(),
316
      NGHTTP2_FLAG_NONE,
317
      &entries_[0],
318
      count_), 0);
319
674
}
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
1626
  };
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
11781
Http2Priority::Http2Priority(Environment* env,
336
                             Local<Value> parent,
337
                             Local<Value> weight,
338
11781
                             Local<Value> exclusive) {
339
11781
  Local<Context> context = env->context();
340
23562
  int32_t parent_ = parent->Int32Value(context).ToChecked();
341
23562
  int32_t weight_ = weight->Int32Value(context).ToChecked();
342
11781
  bool exclusive_ = exclusive->IsTrue();
343
  Debug(env, DebugCategory::HTTP2STREAM,
344
        "Http2Priority: parent: %d, weight: %d, exclusive: %s\n",
345
23562
        parent_, weight_, exclusive_ ? "yes" : "no");
346
11781
  nghttp2_priority_spec_init(this, parent_, weight_, exclusive_ ? 1 : 0);
347
11781
}
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
10
  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
  nghttp2_origin_entry* const nva =
381
5
      reinterpret_cast<nghttp2_origin_entry*>(start);
382
383
5
  CHECK_LE(origin_contents + origin_string_len, buf_.data() + buf_.size());
384
10
  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
9660
Http2Session::Callbacks::Callbacks(bool kHasGetPaddingCallback) {
412
  nghttp2_session_callbacks* callbacks_;
413
9660
  CHECK_EQ(nghttp2_session_callbacks_new(&callbacks_), 0);
414
9660
  callbacks.reset(callbacks_);
415
416
  nghttp2_session_callbacks_set_on_begin_headers_callback(
417
9660
    callbacks_, OnBeginHeadersCallback);
418
  nghttp2_session_callbacks_set_on_header_callback2(
419
9660
    callbacks_, OnHeaderCallback);
420
  nghttp2_session_callbacks_set_on_frame_recv_callback(
421
9660
    callbacks_, OnFrameReceive);
422
  nghttp2_session_callbacks_set_on_stream_close_callback(
423
9660
    callbacks_, OnStreamClose);
424
  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
425
9660
    callbacks_, OnDataChunkReceived);
426
  nghttp2_session_callbacks_set_on_frame_not_send_callback(
427
9660
    callbacks_, OnFrameNotSent);
428
  nghttp2_session_callbacks_set_on_invalid_header_callback2(
429
9660
    callbacks_, OnInvalidHeader);
430
  nghttp2_session_callbacks_set_error_callback(
431
9660
    callbacks_, OnNghttpError);
432
  nghttp2_session_callbacks_set_send_data_callback(
433
9660
    callbacks_, OnSendData);
434
  nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
435
9660
    callbacks_, OnInvalidFrame);
436
  nghttp2_session_callbacks_set_on_frame_send_callback(
437
9660
    callbacks_, OnFrameSent);
438
439
9660
  if (kHasGetPaddingCallback) {
440
    nghttp2_session_callbacks_set_select_padding_callback(
441
4830
      callbacks_, OnSelectPadding);
442
  }
443
9660
}
444
445
void Http2Session::StopTrackingRcbuf(nghttp2_rcbuf* buf) {
446
  StopTrackingMemory(buf);
447
}
448
449
202738
void Http2Session::CheckAllocatedSize(size_t previous_size) const {
450
202738
  CHECK_GE(current_nghttp2_memory_, previous_size);
451
202738
}
452
453
102331
void Http2Session::IncreaseAllocatedSize(size_t size) {
454
102331
  current_nghttp2_memory_ += size;
455
102331
}
456
457
125562
void Http2Session::DecreaseAllocatedSize(size_t size) {
458
125562
  current_nghttp2_memory_ -= size;
459
125562
}
460
461
666
Http2Session::Http2Session(Http2State* http2_state,
462
                           Local<Object> wrap,
463
666
                           SessionType type)
464
    : AsyncWrap(http2_state->env(), wrap, AsyncWrap::PROVIDER_HTTP2SESSION),
465
      js_fields_(http2_state->env()->isolate()),
466
      session_type_(type),
467
1332
      http2_state_(http2_state) {
468
666
  MakeWeak();
469
666
  statistics_.session_type = type;
470
666
  statistics_.start_time = uv_hrtime();
471
472
  // Capture the configuration options for this session
473
1332
  Http2Options opts(http2_state, type);
474
475
666
  max_session_memory_ = opts.max_session_memory();
476
477
666
  uint32_t maxHeaderPairs = opts.max_header_pairs();
478
666
  max_header_pairs_ =
479
      type == NGHTTP2_SESSION_SERVER
480
341
          ? GetServerMaxHeaderPairs(maxHeaderPairs)
481
1007
          : GetClientMaxHeaderPairs(maxHeaderPairs);
482
483
666
  max_outstanding_pings_ = opts.max_outstanding_pings();
484
666
  max_outstanding_settings_ = opts.max_outstanding_settings();
485
486
666
  padding_strategy_ = opts.padding_strategy();
487
488
  bool hasGetPaddingCallback =
489
666
      padding_strategy_ != PADDING_STRATEGY_NONE;
490
491
666
  auto fn = type == NGHTTP2_SESSION_SERVER ?
492
      nghttp2_session_server_new3 :
493
666
      nghttp2_session_client_new3;
494
495
666
  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

666
  CHECK_EQ(fn(
504
      &session,
505
      callback_struct_saved[hasGetPaddingCallback ? 1 : 0].callbacks.get(),
506
      this,
507
      *opts,
508
      &alloc_info), 0);
509
666
  session_.reset(session);
510
511
666
  outgoing_storage_.reserve(1024);
512
666
  outgoing_buffers_.reserve(32);
513
514
  Local<Uint8Array> uint8_arr =
515
666
      Uint8Array::New(js_fields_.GetArrayBuffer(), 0, kSessionUint8FieldCount);
516
1998
  USE(wrap->Set(env()->context(), env()->fields_string(), uint8_arr));
517
666
}
518
519
2664
Http2Session::~Http2Session() {
520
666
  CHECK(!is_in_scope());
521
666
  Debug(this, "freeing nghttp2 session");
522
  // Explicitly reset session_ so the subsequent
523
  // current_nghttp2_memory_ check passes.
524
666
  session_.reset();
525
666
  CHECK_EQ(current_nghttp2_memory_, 0);
526
1332
}
527
528
4
void Http2Session::MemoryInfo(MemoryTracker* tracker) const {
529
4
  tracker->TrackField("streams", streams_);
530
4
  tracker->TrackField("outstanding_pings", outstanding_pings_);
531
4
  tracker->TrackField("outstanding_settings", outstanding_settings_);
532
4
  tracker->TrackField("outgoing_buffers", outgoing_buffers_);
533
4
  tracker->TrackFieldWithSize("stream_buf", stream_buf_.len);
534
4
  tracker->TrackFieldWithSize("outgoing_storage", outgoing_storage_.size());
535
4
  tracker->TrackFieldWithSize("pending_rst_streams",
536
8
                              pending_rst_streams_.size() * sizeof(int32_t));
537
4
  tracker->TrackFieldWithSize("nghttp2_memory", current_nghttp2_memory_);
538
4
}
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
40
  SET(bytes_read_string, received_bytes)
561
40
  SET(bytes_written_string, sent_bytes)
562
40
  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
40
  SET(time_to_first_byte_string, first_byte)
577
40
  SET(time_to_first_byte_sent_string, first_byte_sent)
578
40
  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
35
  SET(bytes_written_string, data_sent)
600
35
  SET(bytes_read_string, data_received)
601
35
  SET(frames_received_string, frame_count)
602
35
  SET(frames_sent_string, frame_sent)
603
35
  SET(max_concurrent_streams_string, max_concurrent_streams)
604
35
  SET(ping_rtt_string, ping_rtt)
605
35
  SET(stream_average_duration_string, stream_average_duration)
606
35
  SET(stream_count_string, stream_count)
607
608
21
  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
42
                  ? "server" : "client")).IsJust()) {
615
    return MaybeLocal<Object>();
616
  }
617
618
#undef SET
619
7
  return obj;
620
}
621
622
23658
void Http2Stream::EmitStatistics() {
623
23658
  CHECK_NOT_NULL(session());
624
23658
  if (LIKELY(!HasHttp2Observer(env())))
625
23650
    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
16
          statistics_);
636
637
48
  env()->SetImmediate([entry = move(entry)](Environment* env) {
638
8
    if (HasHttp2Observer(env))
639
8
      entry->Notify(env);
640
16
  });
641
}
642
643
647
void Http2Session::EmitStatistics() {
644
647
  if (LIKELY(!HasHttp2Observer(env())))
645
640
    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
14
          statistics_);
656
657
42
  env()->SetImmediate([entry = std::move(entry)](Environment* env) {
658
7
    if (HasHttp2Observer(env))
659
7
      entry->Notify(env);
660
14
  });
661
}
662
663
// Closes the session and frees the associated resources
664
647
void Http2Session::Close(uint32_t code, bool socket_closed) {
665
647
  Debug(this, "closing session");
666
667
647
  if (is_closing())
668
    return;
669
647
  set_closing();
670
671
  // Stop reading on the i/o stream
672
647
  if (stream_ != nullptr) {
673
637
    set_reading_stopped();
674
637
    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
647
  if (!socket_closed) {
682
603
    Debug(this, "terminating session with code %d", code);
683
603
    CHECK_EQ(nghttp2_session_terminate_session(session_.get(), code), 0);
684
603
    SendPendingData();
685
44
  } else if (stream_ != nullptr) {
686
34
    stream_->RemoveStreamListener(this);
687
  }
688
689
647
  set_destroyed();
690
691
  // If we are writing we will get to make the callback in OnStreamAfterWrite.
692
647
  if (!is_write_in_progress()) {
693
604
    Debug(this, "make done session callback");
694
1208
    HandleScope scope(env()->isolate());
695
604
    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
649
  while (BaseObjectPtr<Http2Ping> ping = PopPing()) {
702
1
    ping->DetachFromSession();
703
2
    env()->SetImmediate(
704
5
        [ping = std::move(ping)](Environment* env) {
705
1
          ping->Done(false);
706
2
        });
707
1
  }
708
709
647
  statistics_.end_time = uv_hrtime();
710
647
  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
246448
BaseObjectPtr<Http2Stream> Http2Session::FindStream(int32_t id) {
716
246448
  auto s = streams_.find(id);
717
246448
  return s != streams_.end() ? s->second : BaseObjectPtr<Http2Stream>();
718
}
719
720
11985
bool Http2Session::CanAddStream() {
721
  uint32_t maxConcurrentStreams =
722
11985
      nghttp2_session_get_local_settings(
723
11985
          session_.get(), NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
724
  size_t maxSize =
725
11985
      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

23970
  return streams_.size() < maxSize &&
729
23970
         has_available_session_memory(sizeof(Http2Stream));
730
}
731
732
23767
void Http2Session::AddStream(Http2Stream* stream) {
733
23767
  CHECK_GE(++statistics_.stream_count, 0);
734
23767
  streams_[stream->id()] = BaseObjectPtr<Http2Stream>(stream);
735
23767
  size_t size = streams_.size();
736
23767
  if (size > statistics_.max_concurrent_streams)
737
1423
    statistics_.max_concurrent_streams = size;
738
23767
  IncrementCurrentSessionMemory(sizeof(*stream));
739
23767
}
740
741
742
23658
BaseObjectPtr<Http2Stream> Http2Session::RemoveStream(int32_t id) {
743
23658
  BaseObjectPtr<Http2Stream> stream;
744
23658
  if (streams_.empty())
745
    return stream;
746
23658
  stream = FindStream(id);
747
23658
  if (stream) {
748
23658
    streams_.erase(id);
749
23658
    DecrementCurrentSessionMemory(sizeof(*stream));
750
  }
751
23658
  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
31513
void Http2Session::ConsumeHTTP2Data() {
787
31513
  CHECK_NOT_NULL(stream_buf_.base);
788
31513
  CHECK_LE(stream_buf_offset_, stream_buf_.len);
789
31513
  size_t read_len = stream_buf_.len - stream_buf_offset_;
790
791
  // multiple side effects.
792
31513
  Debug(this, "receiving %d bytes [wants data? %d]",
793
        read_len,
794
63026
        nghttp2_session_want_read(session_.get()));
795
31513
  set_receive_paused(false);
796
31513
  custom_recv_error_code_ = nullptr;
797
  ssize_t ret =
798
63026
    nghttp2_session_mem_recv(session_.get(),
799
31513
                             reinterpret_cast<uint8_t*>(stream_buf_.base) +
800
31513
                                 stream_buf_offset_,
801
31513
                             read_len);
802
31513
  CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
803

31513
  CHECK_IMPLIES(custom_recv_error_code_ != nullptr, ret < 0);
804
805
31513
  if (is_receive_paused()) {
806
560
    CHECK(is_reading_stopped());
807
808
560
    CHECK_GT(ret, 0);
809
560
    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
560
    stream_buf_offset_ += ret;
815
560
    goto done;
816
  }
817
818
  // We are done processing the current input chunk.
819
30953
  DecrementCurrentSessionMemory(stream_buf_.len);
820
30953
  stream_buf_offset_ = 0;
821
30953
  stream_buf_ab_.Reset();
822
30953
  stream_buf_allocation_.clear();
823
30953
  stream_buf_ = uv_buf_init(nullptr, 0);
824
825
  // Send any data that was queued up while processing the received data.
826

30953
  if (ret >= 0 && !is_destroyed()) {
827
30418
    SendPendingData();
828
  }
829
830
done:
831
31513
  if (UNLIKELY(ret < 0)) {
832
7
    Isolate* isolate = env()->isolate();
833
7
    Debug(this,
834
        "fatal error receiving data: %d (%s)",
835
        ret,
836
14
        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
21
    };
842
7
    if (custom_recv_error_code_ != nullptr) {
843
9
      args[1] = String::NewFromUtf8(
844
          isolate,
845
          custom_recv_error_code_,
846
6
          NewStringType::kInternalized).ToLocalChecked();
847
    }
848
    MakeCallback(
849
        env()->http2session_on_error_function(),
850
7
        arraysize(args),
851
7
        args);
852
  }
853
31513
}
854
855
856
143229
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
143229
  return (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
859
      frame->push_promise.promised_stream_id :
860
143229
      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
23683
int Http2Session::OnBeginHeadersCallback(nghttp2_session* handle,
869
                                         const nghttp2_frame* frame,
870
                                         void* user_data) {
871
23683
  Http2Session* session = static_cast<Http2Session*>(user_data);
872
23683
  int32_t id = GetFrameID(frame);
873
  Debug(session, "beginning headers for stream %d", id);
874
875
47366
  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
23683
  if (LIKELY(!stream)) {
879

11985
    if (UNLIKELY(!session->CanAddStream() ||
880
                 Http2Stream::New(session, id, frame->headers.cat) ==
881
                     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
2
          NGHTTP2_ENHANCE_YOUR_CALM);
891
1
      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
892
    }
893
894
11984
    session->rejected_stream_count_ = 0;
895
11698
  } else if (!stream->is_destroyed()) {
896
11698
    stream->StartHeaders(frame->headers.cat);
897
  }
898
23682
  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
71866
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
71866
  Http2Session* session = static_cast<Http2Session*>(user_data);
911
71866
  int32_t id = GetFrameID(frame);
912
143732
  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
71866
  if (UNLIKELY(!stream))
917
    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
918
919
  // If the stream has already been destroyed, ignore.
920

71866
  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
71863
  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
55753
int Http2Session::OnFrameReceive(nghttp2_session* handle,
933
                                 const nghttp2_frame* frame,
934
                                 void* user_data) {
935
55753
  Http2Session* session = static_cast<Http2Session*>(user_data);
936
55753
  session->statistics_.frame_count++;
937
55753
  Debug(session, "complete frame received: type: %d",
938
        frame->hd.type);
939


55753
  switch (frame->hd.type) {
940
    case NGHTTP2_DATA:
941
24230
      return session->HandleDataFrame(frame);
942
    case NGHTTP2_PUSH_PROMISE:
943
      // Intentional fall-through, handled just like headers frames
944
    case NGHTTP2_HEADERS:
945
23441
      session->HandleHeadersFrame(frame);
946
23441
      break;
947
    case NGHTTP2_SETTINGS:
948
2145
      session->HandleSettingsFrame(frame);
949
2145
      break;
950
    case NGHTTP2_PRIORITY:
951
16
      session->HandlePriorityFrame(frame);
952
16
      break;
953
    case NGHTTP2_GOAWAY:
954
310
      session->HandleGoawayFrame(frame);
955
310
      break;
956
    case NGHTTP2_PING:
957
1020
      session->HandlePingFrame(frame);
958
1020
      break;
959
    case NGHTTP2_ALTSVC:
960
4
      session->HandleAltSvcFrame(frame);
961
4
      break;
962
    case NGHTTP2_ORIGIN:
963
5
      session->HandleOriginFrame(frame);
964
5
      break;
965
    default:
966
4582
      break;
967
  }
968
31523
  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
242
  Debug(session,
979
        "invalid frame received (%u/%u), code: %d",
980
        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
    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
2191
  Debug(session, "frame type %d was not sent, code: %d",
1015
        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
7
      error_code == NGHTTP2_ERR_STREAM_CLOSED ||
1020

2196
      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
  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
6
  };
1035
  session->MakeCallback(
1036
      env->http2session_on_frame_error_function(),
1037
1
      arraysize(argv), argv);
1038
1
  return 0;
1039
}
1040
1041
56013
int Http2Session::OnFrameSent(nghttp2_session* handle,
1042
                              const nghttp2_frame* frame,
1043
                              void* user_data) {
1044
56013
  Http2Session* session = static_cast<Http2Session*>(user_data);
1045
56013
  session->statistics_.frame_sent += 1;
1046
56013
  return 0;
1047
}
1048
1049
// Called by nghttp2 when a stream closes.
1050
23627
int Http2Session::OnStreamClose(nghttp2_session* handle,
1051
                                int32_t id,
1052
                                uint32_t code,
1053
                                void* user_data) {
1054
23627
  Http2Session* session = static_cast<Http2Session*>(user_data);
1055
23627
  Environment* env = session->env();
1056
23627
  Isolate* isolate = env->isolate();
1057
47254
  HandleScope scope(isolate);
1058
23627
  Local<Context> context = env->context();
1059
  Context::Scope context_scope(context);
1060
  Debug(session, "stream %d closed with code: %d", id, code);
1061
47254
  BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
1062
  // Intentionally ignore the callback if the stream does not exist or has
1063
  // already been destroyed
1064

23627
  if (!stream || stream->is_destroyed())
1065
55
    return 0;
1066
1067
23572
  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
23572
  Local<Value> arg = Integer::NewFromUnsigned(isolate, code);
1073
  MaybeLocal<Value> answer =
1074
23572
    stream->MakeCallback(env->http2session_on_stream_close_function(),
1075
47144
                          1, &arg);
1076

70716
  if (answer.IsEmpty() || answer.ToLocalChecked()->IsFalse()) {
1077
    // Skip to destroy
1078
138
    stream->Destroy();
1079
  }
1080
23572
  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
13837
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
13837
  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
13837
  Environment* env = session->env();
1111
27674
  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
13837
  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
13837
  CHECK_EQ(nghttp2_session_consume_connection(handle, len), 0);
1122
27674
  BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
1123
1124
  // If the stream has been destroyed, ignore this chunk
1125

13837
  if (!stream || stream->is_destroyed())
1126
1
    return 0;
1127
1128
13836
  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
13836
    uv_buf_t buf = stream->EmitAlloc(len);
1137
13836
    ssize_t avail = len;
1138
13836
    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
13836
    if (LIKELY(buf.base == nullptr))
1147
13836
      buf.base = reinterpret_cast<char*>(const_cast<uint8_t*>(data));
1148
    else
1149
      memcpy(buf.base, data, avail);
1150
13836
    data += avail;
1151
13836
    len -= avail;
1152
13836
    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
13836
    if (stream->is_reading())
1158
12726
      nghttp2_session_consume_stream(handle, id, avail);
1159
    else
1160
1110
      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

27672
    if (session->outgoing_length_ > 4096 ||
1164
13836
        stream->available_outbound_length_ > 4096) {
1165
4
      session->SendPendingData();
1166
    }
1167
13836
  } 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
13836
  if (session->is_write_in_progress()) {
1172
560
    CHECK(session->is_reading_stopped());
1173
560
    session->set_receive_paused();
1174
    Debug(session, "receive paused");
1175
560
    return NGHTTP2_ERR_PAUSE;
1176
  }
1177
1178
13276
  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
    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
239
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
239
  Http2Session* session = static_cast<Http2Session*>(user_data);
1217
  Debug(session, "Error '%s'", message);
1218
239
  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
    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
239
  return 0;
1228
}
1229
1230
13836
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
13836
  return uv_buf_init(nullptr, size);
1234
}
1235
1236
26499
void Http2StreamListener::OnStreamRead(ssize_t nread, const uv_buf_t& buf) {
1237
26499
  Http2Stream* stream = static_cast<Http2Stream*>(stream_);
1238
26499
  Http2Session* session = stream->session();
1239
26499
  Environment* env = stream->env();
1240
40335
  HandleScope handle_scope(env->isolate());
1241
40335
  Context::Scope context_scope(env->context());
1242
1243
26499
  if (nread < 0) {
1244
12663
    PassReadErrorToPreviousListener(nread);
1245
12663
    return;
1246
  }
1247
1248
  Local<ArrayBuffer> ab;
1249
27672
  if (session->stream_buf_ab_.IsEmpty()) {
1250
5629
    ab = session->stream_buf_allocation_.ToArrayBuffer();
1251
5629
    session->stream_buf_ab_.Reset(env->isolate(), ab);
1252
  } else {
1253
8207
    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
13836
  size_t offset = buf.base - session->stream_buf_.base;
1260
1261
  // Verify that the data offset is inside the current read buffer.
1262
13836
  CHECK_GE(offset, session->stream_buf_offset_);
1263
13836
  CHECK_LE(offset, session->stream_buf_.len);
1264
13836
  CHECK_LE(offset + buf.len, session->stream_buf_.len);
1265
1266
13836
  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
23441
void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
1274
23441
  Isolate* isolate = env()->isolate();
1275
46882
  HandleScope scope(isolate);
1276
23441
  Local<Context> context = env()->context();
1277
23441
  Context::Scope context_scope(context);
1278
1279
23441
  int32_t id = GetFrameID(frame);
1280
23441
  Debug(this, "handle headers frame for stream %d", id);
1281
46882
  BaseObjectPtr<Http2Stream> stream = FindStream(id);
1282
1283
  // If the stream has already been destroyed, ignore.
1284

23441
  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
46882
  MaybeStackBuffer<Local<Value>, 64> headers_v(stream->headers_count() * 2);
1296
46882
  MaybeStackBuffer<Local<Value>, 32> sensitive_v(stream->headers_count());
1297
23441
  size_t sensitive_count = 0;
1298
1299
118634
  stream->TransferHeaders([&](const Http2Header& header, size_t i) {
1300
287037
    headers_v[i * 2] = header.GetName(this).ToLocalChecked();
1301
287008
    headers_v[i * 2 + 1] = header.GetValue(this).ToLocalChecked();
1302
71752
    if (header.flags() & NGHTTP2_NV_FLAG_NO_INDEX)
1303
58
      sensitive_v[sensitive_count++] = headers_v[i * 2];
1304
95193
  });
1305
23441
  CHECK_EQ(stream->headers_count(), 0);
1306
1307
23441
  DecrementCurrentSessionMemory(stream->current_headers_length_);
1308
23441
  stream->current_headers_length_ = 0;
1309
1310
  Local<Value> args[] = {
1311
23441
    stream->object(),
1312
    Integer::New(isolate, id),
1313
23441
    Integer::New(isolate, stream->headers_category()),
1314
23441
    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
234410
  };
1318
  MakeCallback(env()->http2session_on_headers_function(),
1319
23441
               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
  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
30
  };
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
24230
int Http2Session::HandleDataFrame(const nghttp2_frame* frame) {
1355
24230
  int32_t id = GetFrameID(frame);
1356
24230
  Debug(this, "handling data frame for stream %d", id);
1357
48460
  BaseObjectPtr<Http2Stream> stream = FindStream(id);
1358
1359

48460
  if (stream &&
1360

48460
      !stream->is_destroyed() &&
1361
24230
      frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
1362
12663
    stream->EmitRead(UV_EOF);
1363
11567
  } 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
24229
  return 0;
1372
}
1373
1374
1375
// Called by OnFrameReceived when a complete GOAWAY frame has been received.
1376
310
void Http2Session::HandleGoawayFrame(const nghttp2_frame* frame) {
1377
310
  Isolate* isolate = env()->isolate();
1378
620
  HandleScope scope(isolate);
1379
310
  Local<Context> context = env()->context();
1380
  Context::Scope context_scope(context);
1381
1382
310
  nghttp2_goaway goaway_frame = frame->goaway;
1383
310
  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
1240
  };
1390
1391
310
  size_t length = goaway_frame.opaque_data_len;
1392
310
  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
9
    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
310
               arraysize(argv), argv);
1403
310
}
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
  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
32
  };
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
  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
1022
  HandleScope scope(isolate);
1455
1020
  Local<Context> context = env()->context();
1456
2
  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
2
      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
6
  arg = Buffer::Copy(
1480
      env(),
1481
      reinterpret_cast<const char*>(frame->ping.opaque_data),
1482
4
      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
2145
void Http2Session::HandleSettingsFrame(const nghttp2_frame* frame) {
1488
2145
  bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
1489
2145
  if (!ack) {
1490
1605
    js_fields_->bitfield &= ~(1 << kSessionRemoteSettingsIsUpToDate);
1491
1605
    if (!(js_fields_->bitfield & (1 << kSessionHasRemoteSettingsListeners)))
1492
3737
      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
1552
void Http2Session::OnStreamAfterWrite(WriteWrap* w, int status) {
1524
1552
  Debug(this, "write finished with status %d", status);
1525
1526
1552
  CHECK(is_write_in_progress());
1527
1552
  set_write_in_progress(false);
1528
1529
  // Inform all pending writes about their completion.
1530
1552
  ClearOutgoing(status);
1531
1532

4652
  if (is_reading_stopped() &&
1533

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

1510
  if (!is_write_scheduled() && !is_destroyed()) {
1551
    // Schedule a new write if nghttp2 wants to send data.
1552
1401
    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
34129
void Http2Session::MaybeScheduleWrite() {
1561
34129
  CHECK(!is_write_scheduled());
1562
34129
  if (UNLIKELY(!session_))
1563
    return;
1564
1565
34129
  if (nghttp2_session_want_write(session_.get())) {
1566
3534
    HandleScope handle_scope(env()->isolate());
1567
1767
    Debug(this, "scheduling write");
1568
1767
    set_write_scheduled();
1569
3534
    BaseObjectPtr<Http2Session> strong_ref{this};
1570
15311
    env()->SetImmediate([this, strong_ref](Environment* env) {
1571

3534
      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
296
        return;
1576
      }
1577
1578
      // Sending data may call arbitrary JS code, so keep track of
1579
      // async context.
1580
2942
      HandleScope handle_scope(env->isolate());
1581
2942
      InternalCallbackScope callback_scope(this);
1582
1471
      SendPendingData();
1583
1767
    });
1584
  }
1585
}
1586
1587
62646
void Http2Session::MaybeStopReading() {
1588
62646
  if (is_reading_stopped()) return;
1589
59973
  int want_read = nghttp2_session_want_read(session_.get());
1590
59973
  Debug(this, "wants read? %d", want_read);
1591

59973
  if (want_read == 0 || is_write_in_progress()) {
1592
1514
    set_reading_stopped();
1593
1514
    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
32449
void Http2Session::ClearOutgoing(int status) {
1600
32449
  CHECK(is_sending());
1601
1602
32449
  set_sending(false);
1603
1604
32449
  if (!outgoing_buffers_.empty()) {
1605
31442
    outgoing_storage_.clear();
1606
31442
    outgoing_length_ = 0;
1607
1608
62884
    std::vector<NgHttp2StreamWrite> current_outgoing_buffers_;
1609
31442
    current_outgoing_buffers_.swap(outgoing_buffers_);
1610
125514
    for (const NgHttp2StreamWrite& wr : current_outgoing_buffers_) {
1611
188144
      BaseObjectPtr<AsyncWrap> wrap = std::move(wr.req_wrap);
1612
94072
      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
4045
        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
32449
  if (!pending_rst_streams_.empty()) {
1624
12
    std::vector<int32_t> current_pending_rst_streams;
1625
6
    pending_rst_streams_.swap(current_pending_rst_streams);
1626
1627
6
    SendPendingData();
1628
1629
14
    for (int32_t stream_id : current_pending_rst_streams) {
1630
16
      BaseObjectPtr<Http2Stream> stream = FindStream(stream_id);
1631
8
      if (LIKELY(stream))
1632
1
        stream->FlushRstStream();
1633
    }
1634
  }
1635
32449
}
1636
1637
94084
void Http2Session::PushOutgoingBuffer(NgHttp2StreamWrite&& write) {
1638
94084
  outgoing_length_ += write.buf.len;
1639
94084
  outgoing_buffers_.emplace_back(std::move(write));
1640
94084
}
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
56339
void Http2Session::CopyDataIntoOutgoing(const uint8_t* src, size_t src_length) {
1646
56339
  size_t offset = outgoing_storage_.size();
1647
56339
  outgoing_storage_.resize(offset + src_length);
1648
56339
  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
112678
  PushOutgoingBuffer(NgHttp2StreamWrite {
1655
    uv_buf_init(nullptr, src_length)
1656
56339
  });
1657
56339
}
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
32623
uint8_t Http2Session::SendPendingData() {
1665
32623
  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
32623
  if (is_destroyed())
1670
40
    return 0;
1671
32583
  set_write_scheduled(false);
1672
1673
  // SendPendingData should not be called recursively.
1674
32583
  if (is_sending())
1675
130
    return 1;
1676
  // This is cleared by ClearOutgoing().
1677
32453
  set_sending();
1678
1679
  ssize_t src_length;
1680
  const uint8_t* src;
1681
1682
32453
  CHECK(outgoing_buffers_.empty());
1683
32453
  CHECK(outgoing_storage_.empty());
1684
1685
  // Part One: Gather data from nghttp2
1686
1687
42330
  while ((src_length = nghttp2_session_mem_send(session_.get(), &src)) > 0) {
1688
42330
    Debug(this, "nghttp2 has %d bytes to send", src_length);
1689
42330
    CopyDataIntoOutgoing(src, src_length);
1690
  }
1691
1692
32453
  CHECK_NE(src_length, NGHTTP2_ERR_NOMEM);
1693
1694
32453
  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
12
    ClearOutgoing(UV_ECANCELED);
1699
12
    return 0;
1700
  }
1701
1702
  // Part Two: Pass Data to the underlying stream
1703
1704
32441
  size_t count = outgoing_buffers_.size();
1705
32441
  if (count == 0) {
1706
1007
    ClearOutgoing(0);
1707
1007
    return 0;
1708
  }
1709
62868
  MaybeStackBuffer<uv_buf_t, 32> bufs;
1710
31434
  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
31434
  size_t offset = 0;
1716
31434
  size_t i = 0;
1717
125501
  for (const NgHttp2StreamWrite& write : outgoing_buffers_) {
1718
94067
    statistics_.data_sent += write.buf.len;
1719
94067
    if (write.buf.base == nullptr) {
1720
56322
      bufs[i++] = uv_buf_init(
1721
56322
          reinterpret_cast<char*>(outgoing_storage_.data() + offset),
1722
112644
          write.buf.len);
1723
56322
      offset += write.buf.len;
1724
    } else {
1725
37745
      bufs[i++] = write.buf;
1726
    }
1727
  }
1728
1729
31434
  chunks_sent_since_last_write_++;
1730
1731
31434
  CHECK(!is_write_in_progress());
1732
31434
  set_write_in_progress();
1733
62868
  StreamWriteResult res = underlying_stream()->Write(*bufs, count);
1734
31434
  if (!res.async) {
1735
29878
    set_write_in_progress(false);
1736
29878
    ClearOutgoing(res.err);
1737
  }
1738
1739
31434
  MaybeStopReading();
1740
1741
31434
  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
14008
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
14008
  Http2Session* session = static_cast<Http2Session*>(user_data);
1757
28016
  BaseObjectPtr<Http2Stream> stream = session->FindStream(frame->hd.stream_id);
1758
14008
  if (!stream) return 0;
1759
1760
  // Send the frame header + a byte that indicates padding length.
1761
14008
  session->CopyDataIntoOutgoing(framehd, 9);
1762
14008
  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
69574
  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
37744
    CHECK(!stream->queue_.empty());
1773
1774
37744
    NgHttp2StreamWrite& write = stream->queue_.front();
1775
37744
    if (write.buf.len <= length) {
1776
      // This write does not suffice by itself, so we can consume it completely.
1777
27783
      length -= write.buf.len;
1778
27783
      session->PushOutgoingBuffer(std::move(write));
1779
27783
      stream->queue_.pop();
1780
27783
      continue;
1781
    }
1782
1783
    // Slice off `length` bytes of the first write in the queue.
1784
19922
    session->PushOutgoingBuffer(NgHttp2StreamWrite {
1785
      uv_buf_init(write.buf.base, length)
1786
9961
    });
1787
9961
    write.buf.base += length;
1788
9961
    write.buf.len -= length;
1789
9961
    break;
1790
  }
1791
1792
14008
  if (frame->data.padlen > 0) {
1793
    // Send padding if that was requested.
1794
2
    session->PushOutgoingBuffer(NgHttp2StreamWrite {
1795
1
      uv_buf_init(const_cast<char*>(zero_bytes_256), frame->data.padlen - 1)
1796
1
    });
1797
  }
1798
1799
14008
  return 0;
1800
}
1801
1802
// Creates a new Http2Stream and submits a new http2 request.
1803
11775
Http2Stream* Http2Session::SubmitRequest(
1804
    const Http2Priority& priority,
1805
    const Http2Headers& headers,
1806
    int32_t* ret,
1807
    int options) {
1808
11775
  Debug(this, "submitting request");
1809
23550
  Http2Scope h2scope(this);
1810
11775
  Http2Stream* stream = nullptr;
1811
23550
  Http2Stream::Provider::Stream prov(options);
1812
11775
  *ret = nghttp2_submit_request(
1813
      session_.get(),
1814
      &priority,
1815
      headers.data(),
1816
      headers.length(),
1817
11775
      *prov,
1818
      nullptr);
1819
11775
  CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
1820
11775
  if (LIKELY(*ret > 0))
1821
11774
    stream = Http2Stream::New(this, *ret, NGHTTP2_HCAT_HEADERS, options);
1822
23550
  return stream;
1823
}
1824
1825
31234
uv_buf_t Http2Session::OnStreamAlloc(size_t suggested_size) {
1826
31234
  return AllocatedBuffer::AllocateManaged(env(), suggested_size).release();
1827
}
1828
1829
// Callback used to receive inbound data from the i/o stream
1830
31259
void Http2Session::OnStreamRead(ssize_t nread, const uv_buf_t& buf_) {
1831
62471
  HandleScope handle_scope(env()->isolate());
1832
62471
  Context::Scope context_scope(env()->context());
1833
62471
  Http2Scope h2scope(this);
1834
31259
  CHECK_NOT_NULL(stream_);
1835
31259
  Debug(this, "receiving %d bytes, offset %d", nread, stream_buf_offset_);
1836
62471
  AllocatedBuffer buf(env(), buf_);
1837
1838
  // Only pass data on if nread > 0
1839
31259
  if (nread <= 0) {
1840
47
    if (nread < 0) {
1841
47
      PassReadErrorToPreviousListener(nread);
1842
    }
1843
47
    return;
1844
  }
1845
1846
31212
  statistics_.data_received += nread;
1847
1848
31212
  if (LIKELY(stream_buf_offset_ == 0)) {
1849
    // Shrink to the actual amount of used data.
1850
30954
    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
258
    size_t pending_len = stream_buf_.len - stream_buf_offset_;
1857
    AllocatedBuffer new_buf =
1858
516
        AllocatedBuffer::AllocateManaged(env(), pending_len + nread);
1859
258
    memcpy(new_buf.data(), stream_buf_.base + stream_buf_offset_, pending_len);
1860
258
    memcpy(new_buf.data() + pending_len, buf.data(), nread);
1861
1862
258
    buf = std::move(new_buf);
1863
258
    nread = buf.size();
1864
258
    stream_buf_offset_ = 0;
1865
258
    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
258
    DecrementCurrentSessionMemory(stream_buf_.len);
1870
  }
1871
1872
31212
  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
31212
  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
31212
  stream_buf_allocation_ = std::move(buf);
1882
1883
31212
  ConsumeHTTP2Data();
1884
1885
31212
  MaybeStopReading();
1886
}
1887
1888
23646
bool Http2Session::HasWritesOnSocketForStream(Http2Stream* stream) {
1889
23920
  for (const NgHttp2StreamWrite& wr : outgoing_buffers_) {
1890


335
    if (wr.req_wrap && WriteWrap::FromObject(wr.req_wrap)->stream() == stream)
1891
61
      return true;
1892
  }
1893
23585
  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
666
void Http2Session::Consume(Local<Object> stream_obj) {
1901
666
  StreamBase* stream = StreamBase::FromObject(stream_obj);
1902
666
  stream->PushStreamListener(this);
1903
666
  Debug(this, "i/o stream consumed");
1904
666
}
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
4
  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
6
  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
23767
Http2Stream* Http2Stream::New(Http2Session* session,
1934
                              int32_t id,
1935
                              nghttp2_headers_category category,
1936
                              int options) {
1937
  Local<Object> obj;
1938
47534
  if (!session->env()
1939
47534
           ->http2stream_constructor_template()
1940
71301
           ->NewInstance(session->env()->context())
1941
23767
           .ToLocal(&obj)) {
1942
    return nullptr;
1943
  }
1944
23767
  return new Http2Stream(session, obj, id, category, options);
1945
}
1946
1947
23767
Http2Stream::Http2Stream(Http2Session* session,
1948
                         Local<Object> obj,
1949
                         int32_t id,
1950
                         nghttp2_headers_category category,
1951
23767
                         int options)
1952
    : AsyncWrap(session->env(), obj, AsyncWrap::PROVIDER_HTTP2STREAM),
1953
      StreamBase(session->env()),
1954
      session_(session),
1955
      id_(id),
1956
23767
      current_headers_category_(category) {
1957
23767
  MakeWeak();
1958
23767
  StreamBase::AttachToObject(GetObject());
1959
23767
  statistics_.id = id;
1960
23767
  statistics_.start_time = uv_hrtime();
1961
1962
  // Limit the number of header pairs
1963
23767
  max_header_pairs_ = session->max_header_pairs();
1964
23767
  if (max_header_pairs_ == 0) {
1965
    max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
1966
  }
1967
23767
  current_headers_.reserve(std::min(max_header_pairs_, 12u));
1968
1969
  // Limit the number of header octets
1970
23767
  max_header_length_ =
1971
23767
      std::min(
1972
47534
        nghttp2_session_get_local_settings(
1973
          session->session(),
1974
          NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE),
1975
71301
      MAX_MAX_HEADER_LIST_SIZE);
1976
1977
23767
  if (options & STREAM_OPTION_GET_TRAILERS)
1978
2
    set_has_trailers();
1979
1980
23767
  PushStreamListener(&stream_listener_);
1981
1982
23767
  if (options & STREAM_OPTION_EMPTY_PAYLOAD)
1983
600
    Shutdown();
1984
23767
  session->AddStream(this);
1985
23767
}
1986
1987
71301
Http2Stream::~Http2Stream() {
1988
23767
  Debug(this, "tearing down stream");
1989
47534
}
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
11698
void Http2Stream::StartHeaders(nghttp2_headers_category category) {
2004
11698
  Debug(this, "starting headers, category: %d", category);
2005
11698
  CHECK(!this->is_destroyed());
2006
11698
  session_->DecrementCurrentSessionMemory(current_headers_length_);
2007
11698
  current_headers_length_ = 0;
2008
11698
  current_headers_.clear();
2009
11698
  current_headers_category_ = category;
2010
11698
}
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
23572
void Http2Stream::Close(int32_t code) {
2020
23572
  CHECK(!this->is_destroyed());
2021
23572
  set_closed();
2022
23572
  code_ = code;
2023
23572
  Debug(this, "closed with code %d", code);
2024
23572
}
2025
2026
24075
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
24075
  return nullptr;
2030
}
2031
2032
24075
int Http2Stream::DoShutdown(ShutdownWrap* req_wrap) {
2033
24075
  if (is_destroyed())
2034
    return UV_EPIPE;
2035
2036
  {
2037
48150
    Http2Scope h2scope(this);
2038
24075
    set_not_writable();
2039
24075
    CHECK_NE(nghttp2_session_resume_data(
2040
        session_->session(), id_),
2041
        NGHTTP2_ERR_NOMEM);
2042
24075
    Debug(this, "writable side shutdown");
2043
  }
2044
24075
  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
23659
void Http2Stream::Destroy() {
2051
  // Do nothing if this stream instance is already destroyed
2052
23659
  if (is_destroyed())
2053
1
    return;
2054
23658
  if (session_->has_pending_rststream(id_))
2055
7
    FlushRstStream();
2056
23658
  set_destroyed();
2057
2058
23658
  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
47316
  BaseObjectPtr<Http2Stream> strong_ref = session_->RemoveStream(id_);
2063
23658
  if (strong_ref) {
2064
118290
    env()->SetImmediate([this, strong_ref = std::move(strong_ref)](
2065
94577
        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
23670
      while (!queue_.empty()) {
2070
6
        NgHttp2StreamWrite& head = queue_.front();
2071
6
        if (head.req_wrap)
2072
6
          WriteWrap::FromObject(head.req_wrap)->Done(UV_ECANCELED);
2073
6
        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

47304
      if (session() == nullptr ||
2080
23646
          !session()->HasWritesOnSocketForStream(this)) {
2081
        // Delete once strong_ref goes out of scope.
2082
23597
        Detach();
2083
      }
2084
47316
    });
2085
  }
2086
2087
23658
  statistics_.end_time = uv_hrtime();
2088
47316
  session_->statistics_.stream_average_duration =
2089
47316
      ((statistics_.end_time - statistics_.start_time) /
2090
47316
          session_->statistics_.stream_count) / 1e6;
2091
23658
  EmitStatistics();
2092
}
2093
2094
2095
// Initiates a response on the Http2Stream using data provided via the
2096
// StreamBase Streams API.
2097
11698
int Http2Stream::SubmitResponse(const Http2Headers& headers, int options) {
2098
11698
  CHECK(!this->is_destroyed());
2099
23396
  Http2Scope h2scope(this);
2100
11698
  Debug(this, "submitting response");
2101
11698
  if (options & STREAM_OPTION_GET_TRAILERS)
2102
162
    set_has_trailers();
2103
2104
11698
  if (!is_writable())
2105
10136
    options |= STREAM_OPTION_EMPTY_PAYLOAD;
2106
2107
23396
  Http2Stream::Provider::Stream prov(this, options);
2108
11698
  int ret = nghttp2_submit_response(
2109
      session_->session(),
2110
      id_,
2111
      headers.data(),
2112
      headers.length(),
2113
23396
      *prov);
2114
11698
  CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2115
23396
  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
10
  Http2Scope h2scope(this);
2123
10
  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
10
  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
  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
62
  Http2Scope h2scope(this);
2151
62
  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
52
    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
62
  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
12
  Http2Scope h2scope(this);
2179
6
  Debug(this, "sending priority spec");
2180
12
  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
12
  return ret;
2191
}
2192
2193
// Closes the Http2Stream by submitting an RST_STREAM frame to the connected
2194
// peer.
2195
121
void Http2Stream::SubmitRstStream(const uint32_t code) {
2196
121
  CHECK(!this->is_destroyed());
2197
121
  code_ = code;
2198
  // If possible, force a purge of any currently pending data here to make sure
2199
  // it is sent before closing the stream. If it returns non-zero then we need
2200
  // to wait until the current write finishes and try again to avoid nghttp2
2201
  // behaviour where it prioritizes RstStream over everything else.
2202
121
  if (session_->SendPendingData() != 0) {
2203
8
    session_->AddPendingRstStream(id_);
2204
8
    return;
2205
  }
2206
2207
113
  FlushRstStream();
2208
}
2209
2210
121
void Http2Stream::FlushRstStream() {
2211
121
  if (is_destroyed())
2212
5
    return;
2213
232
  Http2Scope h2scope(this);
2214
116
  CHECK_EQ(nghttp2_submit_rst_stream(
2215
      session_->session(),
2216
      NGHTTP2_FLAG_NONE,
2217
      id_,
2218
      code_), 0);
2219
}
2220
2221
2222
// Submit a push promise and create the associated Http2Stream if successful.
2223
9
Http2Stream* Http2Stream::SubmitPushPromise(const Http2Headers& headers,
2224
                                            int32_t* ret,
2225
                                            int options) {
2226
9
  CHECK(!this->is_destroyed());
2227
18
  Http2Scope h2scope(this);
2228
9
  Debug(this, "sending push promise");
2229
9
  *ret = nghttp2_submit_push_promise(
2230
      session_->session(),
2231
      NGHTTP2_FLAG_NONE,
2232
      id_,
2233
      headers.data(),
2234
      headers.length(),
2235
      nullptr);
2236
9
  CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
2237
9
  Http2Stream* stream = nullptr;
2238
9
  if (*ret > 0) {
2239
9
    stream = Http2Stream::New(
2240
9
        session_.get(), *ret, NGHTTP2_HCAT_HEADERS, options);
2241
  }
2242
2243
18
  return stream;
2244
}
2245
2246
// Switch the StreamBase into flowing mode to begin pushing chunks of data
2247
// out to JS land.
2248
23497
int Http2Stream::ReadStart() {
2249
46994
  Http2Scope h2scope(this);
2250
23497
  CHECK(!this->is_destroyed());
2251
23497
  set_reading();
2252
2253
23497
  Debug(this, "reading starting");
2254
2255
  // Tell nghttp2 about our consumption of the data that was handed
2256
  // off to JS land.
2257
23497
  nghttp2_session_consume_stream(
2258
      session_->session(),
2259
      id_,
2260
23497
      inbound_consumed_data_while_paused_);
2261
23497
  inbound_consumed_data_while_paused_ = 0;
2262
2263
46994
  return 0;
2264
}
2265
2266
// Switch the StreamBase into paused mode.
2267
17759
int Http2Stream::ReadStop() {
2268
17759
  CHECK(!this->is_destroyed());
2269
17759
  if (!is_reading())
2270
2239
    return 0;
2271
15520
  set_paused();
2272
15520
  Debug(this, "reading stopped");
2273
15520
  return 0;
2274
}
2275
2276
// The Http2Stream class is a subclass of StreamBase. The DoWrite method
2277
// receives outbound chunks of data to send as outbound DATA frames. These
2278
// are queued in an internal linked list of uv_buf_t structs that are sent
2279
// when nghttp2 is ready to serialize the data frame.
2280
//
2281
// Queue the given set of uv_but_t handles for writing to an
2282
// nghttp2_stream. The WriteWrap's Done callback will be invoked once the
2283
// chunks of data have been flushed to the underlying nghttp2_session.
2284
// Note that this does *not* mean that the data has been flushed
2285
// to the socket yet.
2286
4055
int Http2Stream::DoWrite(WriteWrap* req_wrap,
2287
                         uv_buf_t* bufs,
2288
                         size_t nbufs,
2289
                         uv_stream_t* send_handle) {
2290
4055
  CHECK_NULL(send_handle);
2291
8110
  Http2Scope h2scope(this);
2292

4055
  if (!is_writable() || is_destroyed()) {
2293
    req_wrap->Done(UV_EOF);
2294
    return 0;
2295
  }
2296
4055
  Debug(this, "queuing %d buffers to send", nbufs);
2297
31850
  for (size_t i = 0; i < nbufs; ++i) {
2298
    // Store the req_wrap on the last write info in the queue, so that it is
2299
    // only marked as finished once all buffers associated with it are finished.
2300
55590
    queue_.emplace(NgHttp2StreamWrite {
2301
59645
      BaseObjectPtr<AsyncWrap>(
2302
31850
          i == nbufs - 1 ? req_wrap->GetAsyncWrap() : nullptr),
2303
27795
      bufs[i]
2304
27795
    });
2305
27795
    IncrementAvailableOutboundLength(bufs[i].len);
2306
  }
2307
4055
  CHECK_NE(nghttp2_session_resume_data(
2308
      session_->session(),
2309
      id_), NGHTTP2_ERR_NOMEM);
2310
4055
  return 0;
2311
}
2312
2313
// Ads a header to the Http2Stream. Note that the header name and value are
2314
// provided using a buffer structure provided by nghttp2 that allows us to
2315
// avoid unnecessary memcpy's. Those buffers are ref counted. The ref count
2316
// is incremented here and are decremented when the header name and values
2317
// are garbage collected later.
2318
71866
bool Http2Stream::AddHeader(nghttp2_rcbuf* name,
2319
                            nghttp2_rcbuf* value,
2320
                            uint8_t flags) {
2321
71866
  CHECK(!this->is_destroyed());
2322
2323
71866
  if (Http2RcBufferPointer::IsZeroLength(name))
2324
    return true;  // Ignore empty headers.
2325
2326
143732
  Http2Header header(env(), name, value, flags);
2327
71866
  size_t length = header.length() + 32;
2328
  // A header can only be added if we have not exceeded the maximum number
2329
  // of headers and the session has memory available for it.
2330

215598
  if (!session_->has_available_session_memory(length) ||
2331

143731
      current_headers_.size() == max_header_pairs_ ||
2332
71865
      current_headers_length_ + length > max_header_length_) {
2333
3
    return false;
2334
  }
2335
2336
71863
  if (statistics_.first_header == 0)
2337
23460
    statistics_.first_header = uv_hrtime();
2338
2339
71863
  current_headers_.push_back(std::move(header));
2340
2341
71863
  current_headers_length_ += length;
2342
71863
  session_->IncrementCurrentSessionMemory(length);
2343
71863
  return true;
2344
}
2345
2346
// A Provider is the thing that provides outbound DATA frame data.
2347
11724
Http2Stream::Provider::Provider(Http2Stream* stream, int options) {
2348
11724
  CHECK(!stream->is_destroyed());
2349
11724
  provider_.source.ptr = stream;
2350
11724
  empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
2351
11724
}
2352
2353
11775
Http2Stream::Provider::Provider(int options) {
2354
11775
  provider_.source.ptr = nullptr;
2355
11775
  empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
2356
11775
}
2357
2358
46998
Http2Stream::Provider::~Provider() {
2359
23499
  provider_.source.ptr = nullptr;
2360
23499
}
2361
2362
// The Stream Provider pulls data from a linked list of uv_buf_t structs
2363
// built via the StreamBase API and the Streams js API.
2364
11775
Http2Stream::Provider::Stream::Stream(int options)
2365
11775
    : Http2Stream::Provider(options) {
2366
11775
  provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
2367
11775
}
2368
2369
11724
Http2Stream::Provider::Stream::Stream(Http2Stream* stream, int options)
2370
11724
    : Http2Stream::Provider(stream, options) {
2371
11724
  provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
2372
11724
}
2373
2374
28090
ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
2375
                                              int32_t id,
2376
                                              uint8_t* buf,
2377
                                              size_t length,
2378
                                              uint32_t* flags,
2379
                                              nghttp2_data_source* source,
2380
                                              void* user_data) {
2381
28090
  Http2Session* session = static_cast<Http2Session*>(user_data);
2382
  Debug(session, "reading outbound data for stream %d", id);
2383
56180
  BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
2384
28090
  if (!stream) return 0;
2385
28090
  if (stream->statistics_.first_byte_sent == 0)
2386
12735
    stream->statistics_.first_byte_sent = uv_hrtime();
2387
28090
  CHECK_EQ(id, stream->id());
2388
2389
28090
  size_t amount = 0;          // amount of data being sent in this data frame.
2390
2391
  // Remove all empty chunks from the head of the queue.
2392
  // This is done here so that .write('', cb) is still a meaningful way to
2393
  // find out when the HTTP2 stream wants to consume data, and because the
2394
  // StreamBase API allows empty input chunks.
2395

28100
  while (!stream->queue_.empty() && stream->queue_.front().buf.len == 0) {
2396
    BaseObjectPtr<AsyncWrap> finished =
2397
10
        std::move(stream->queue_.front().req_wrap);
2398
5
    stream->queue_.pop();
2399
5
    if (finished)
2400
3
      WriteWrap::FromObject(finished)->Done(0);
2401
  }
2402
2403
28090
  if (!stream->queue_.empty()) {
2404
    Debug(session, "stream %d has pending outbound data", id);
2405
14008
    amount = std::min(stream->available_outbound_length_, length);
2406
    Debug(session, "sending %d bytes for data frame on stream %d", amount, id);
2407
14008
    if (amount > 0) {
2408
      // Just return the length, let Http2Session::OnSendData take care of
2409
      // actually taking the buffers out of the queue.
2410
14008
      *flags |= NGHTTP2_DATA_FLAG_NO_COPY;
2411
14008
      stream->DecrementAvailableOutboundLength(amount);
2412
    }
2413
  }
2414
2415

28090
  if (amount == 0 && stream->is_writable()) {
2416
2718
    CHECK(stream->queue_.empty());
2417
    Debug(session, "deferring stream %d", id);
2418
2718
    stream->EmitWantsWrite(length);
2419

2718
    if (stream->available_outbound_length_ > 0 || !stream->is_writable()) {
2420
      // EmitWantsWrite() did something interesting synchronously, restart:
2421
      return OnRead(handle, id, buf, length, flags, source, user_data);
2422
    }
2423
2718
    return NGHTTP2_ERR_DEFERRED;
2424
  }
2425
2426

25372
  if (stream->available_outbound_length_ == 0 && !stream->is_writable()) {
2427
    Debug(session, "no more data for stream %d", id);
2428
12724
    *flags |= NGHTTP2_DATA_FLAG_EOF;
2429
12724
    if (stream->has_trailers()) {
2430
37
      *flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
2431
37
      stream->OnTrailers();
2432
    }
2433
  }
2434
2435
25372
  stream->statistics_.sent_bytes += amount;
2436
25372
  return amount;
2437
}
2438
2439
27795
void Http2Stream::IncrementAvailableOutboundLength(size_t amount) {
2440
27795
  available_outbound_length_ += amount;
2441
27795
  session_->IncrementCurrentSessionMemory(amount);
2442
27795
}
2443
2444
14008
void Http2Stream::DecrementAvailableOutboundLength(size_t amount) {
2445
14008
  available_outbound_length_ -= amount;
2446
14008
  session_->DecrementCurrentSessionMemory(amount);
2447
14008
}
2448
2449
2450
// Implementation of the JavaScript API
2451
2452
// Fetches the string description of a nghttp2 error code and passes that
2453
// back to JS land
2454
62
void HttpErrorString(const FunctionCallbackInfo<Value>& args) {
2455
62
  Environment* env = Environment::GetCurrent(args);
2456
248
  uint32_t val = args[0]->Uint32Value(env->context()).ToChecked();
2457
186
  args.GetReturnValue().Set(
2458
      OneByteString(
2459
          env->isolate(),
2460
62
          reinterpret_cast<const uint8_t*>(nghttp2_strerror(val))));
2461
62
}
2462
2463
2464
// Serializes the settings object into a Buffer instance that
2465
// would be suitable, for instance, for creating the Base64
2466
// output for an HTTP2-Settings header field.
2467
17
void PackSettings(const FunctionCallbackInfo<Value>& args) {
2468
17
  Http2State* state = Environment::GetBindingData<Http2State>(args);
2469
51
  args.GetReturnValue().Set(Http2Settings::Pack(state));
2470
17
}
2471
2472
// A TypedArray instance is shared between C++ and JS land to contain the
2473
// default SETTINGS. RefreshDefaultSettings updates that TypedArray with the
2474
// default values.
2475
6
void RefreshDefaultSettings(const FunctionCallbackInfo<Value>& args) {
2476
6
  Http2State* state = Environment::GetBindingData<Http2State>(args);
2477
6
  Http2Settings::RefreshDefaults(state);
2478
6
}
2479
2480
// Sets the next stream ID the Http2Session. If successful, returns true.
2481
1
void Http2Session::SetNextStreamID(const FunctionCallbackInfo<Value>& args) {
2482
1
  Environment* env = Environment::GetCurrent(args);
2483
  Http2Session* session;
2484
1
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2485
4
  int32_t id = args[0]->Int32Value(env->context()).ToChecked();
2486
1
  if (nghttp2_session_set_next_stream_id(session->session(), id) < 0) {
2487
    Debug(session, "failed to set next stream id to %d", id);
2488
    return args.GetReturnValue().Set(false);
2489
  }
2490
2
  args.GetReturnValue().Set(true);
2491
1
  Debug(session, "set next stream id to %d", id);
2492
}
2493
2494
// Set local window size (local endpoints's window size) to the given
2495
// window_size for the stream denoted by 0.
2496
// This function returns 0 if it succeeds, or one of a negative codes
2497
3
void Http2Session::SetLocalWindowSize(
2498
    const FunctionCallbackInfo<Value>& args) {
2499
3
  Environment* env = Environment::GetCurrent(args);
2500
  Http2Session* session;
2501
3
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2502
2503
12
  int32_t window_size = args[0]->Int32Value(env->context()).ToChecked();
2504
2505
3
  int result = nghttp2_session_set_local_window_size(
2506
3
      session->session(), NGHTTP2_FLAG_NONE, 0, window_size);
2507
2508
6
  args.GetReturnValue().Set(result);
2509
2510
3
  Debug(session, "set local window size to %d", window_size);
2511
}
2512
2513
// A TypedArray instance is shared between C++ and JS land to contain the
2514
// SETTINGS (either remote or local). RefreshSettings updates the current
2515
// values established for each of the settings so those can be read in JS land.
2516
template <get_setting fn>
2517
565
void Http2Session::RefreshSettings(const FunctionCallbackInfo<Value>& args) {
2518
  Http2Session* session;
2519

565
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2520
565
  Http2Settings::Update(session, fn);
2521
565
  Debug(session, "settings refreshed for session");
2522
}
2523
2524
// A TypedArray instance is shared between C++ and JS land to contain state
2525
// information of the current Http2Session. This updates the values in the
2526
// TypedArray so those can be read in JS land.
2527
12
void Http2Session::RefreshState(const FunctionCallbackInfo<Value>& args) {
2528
  Http2Session* session;
2529
12
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2530
12
  Debug(session, "refreshing state");
2531
2532
12
  AliasedFloat64Array& buffer = session->http2_state()->session_state_buffer;
2533
2534
12
  nghttp2_session* s = session->session();
2535
2536
  buffer[IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE] =
2537
12
      nghttp2_session_get_effective_local_window_size(s);
2538
  buffer[IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH] =
2539
12
      nghttp2_session_get_effective_recv_data_length(s);
2540
  buffer[IDX_SESSION_STATE_NEXT_STREAM_ID] =
2541
12
      nghttp2_session_get_next_stream_id(s);
2542
  buffer[IDX_SESSION_STATE_LOCAL_WINDOW_SIZE] =
2543
12
      nghttp2_session_get_local_window_size(s);
2544
  buffer[IDX_SESSION_STATE_LAST_PROC_STREAM_ID] =
2545
12
      nghttp2_session_get_last_proc_stream_id(s);
2546
  buffer[IDX_SESSION_STATE_REMOTE_WINDOW_SIZE] =
2547
12
      nghttp2_session_get_remote_window_size(s);
2548
  buffer[IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE] =
2549
12
      static_cast<double>(nghttp2_session_get_outbound_queue_size(s));
2550
  buffer[IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE] =
2551
12
      static_cast<double>(nghttp2_session_get_hd_deflate_dynamic_table_size(s));
2552
  buffer[IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE] =
2553
12
      static_cast<double>(nghttp2_session_get_hd_inflate_dynamic_table_size(s));
2554
}
2555
2556
2557
// Constructor for new Http2Session instances.
2558
666
void Http2Session::New(const FunctionCallbackInfo<Value>& args) {
2559
666
  Http2State* state = Environment::GetBindingData<Http2State>(args);
2560
666
  Environment* env = state->env();
2561
666
  CHECK(args.IsConstructCall());
2562
  SessionType type =
2563
      static_cast<SessionType>(
2564
2664
          args[0]->Int32Value(env->context()).ToChecked());
2565
666
  Http2Session* session = new Http2Session(state, args.This(), type);
2566
666
  session->get_async_id();  // avoid compiler warning
2567
  Debug(session, "session created");
2568
666
}
2569
2570
2571
// Binds the Http2Session with a StreamBase used for i/o
2572
666
void Http2Session::Consume(const FunctionCallbackInfo<Value>& args) {
2573
  Http2Session* session;
2574
666
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2575
1332
  CHECK(args[0]->IsObject());
2576
1332
  session->Consume(args[0].As<Object>());
2577
}
2578
2579
// Destroys the Http2Session instance and renders it unusable
2580
647
void Http2Session::Destroy(const FunctionCallbackInfo<Value>& args) {
2581
  Http2Session* session;
2582
647
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2583
647
  Debug(session, "destroying session");
2584
647
  Environment* env = Environment::GetCurrent(args);
2585
647
  Local<Context> context = env->context();
2586
2587
1941
  uint32_t code = args[0]->Uint32Value(context).ToChecked();
2588
1294
  session->Close(code, args[1]->IsTrue());
2589
}
2590
2591
// Submits a new request on the Http2Session and returns either an error code
2592
// or the Http2Stream object.
2593
11775
void Http2Session::Request(const FunctionCallbackInfo<Value>& args) {
2594
  Http2Session* session;
2595
11776
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2596
11775
  Environment* env = session->env();
2597
2598
23550
  Local<Array> headers = args[0].As<Array>();
2599
47100
  int32_t options = args[1]->Int32Value(env->context()).ToChecked();
2600
2601
11775
  Debug(session, "request submitted");
2602
2603
11775
  int32_t ret = 0;
2604
  Http2Stream* stream =
2605
11775
      session->Http2Session::SubmitRequest(
2606
23550
          Http2Priority(env, args[2], args[3], args[4]),
2607
23550
          Http2Headers(env, headers),
2608
          &ret,
2609
11775
          static_cast<int>(options));
2610
2611

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

9
  if (ret <= 0 || stream == nullptr) {
2765
    Debug(parent, "failed to create push stream: %d", ret);
2766
    return args.GetReturnValue().Set(ret);
2767
  }
2768
18
  Debug(parent, "push stream %d created", stream->id());
2769
27
  args.GetReturnValue().Set(stream->object());
2770
}
2771
2772
// Send a PRIORITY frame
2773
6
void Http2Stream::Priority(const FunctionCallbackInfo<Value>& args) {
2774
6
  Environment* env = Environment::GetCurrent(args);
2775
  Http2Stream* stream;
2776
6
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2777
2778
18
  CHECK_EQ(stream->SubmitPriority(
2779
      Http2Priority(env, args[0], args[1], args[2]),
2780
      args[3]->IsTrue()), 0);
2781
6
  Debug(stream, "priority submitted");
2782
}
2783
2784
// A TypedArray shared by C++ and JS land is used to communicate state
2785
// information about the Http2Stream. This updates the values in that
2786
// TypedArray so that the state can be read by JS.
2787
11
void Http2Stream::RefreshState(const FunctionCallbackInfo<Value>& args) {
2788
  Http2Stream* stream;
2789
11
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2790
2791
11
  Debug(stream, "refreshing state");
2792
2793
11
  CHECK_NOT_NULL(stream->session());
2794
  AliasedFloat64Array& buffer =
2795
11
      stream->session()->http2_state()->stream_state_buffer;
2796
2797
11
  nghttp2_stream* str = stream->stream();
2798
11
  nghttp2_session* s = stream->session()->session();
2799
2800
11
  if (str == nullptr) {
2801
1
    buffer[IDX_STREAM_STATE] = NGHTTP2_STREAM_STATE_IDLE;
2802
    buffer[IDX_STREAM_STATE_WEIGHT] =
2803
        buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2804
        buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2805
        buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2806
1
        buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] = 0;
2807
  } else {
2808
    buffer[IDX_STREAM_STATE] =
2809
10
        nghttp2_stream_get_state(str);
2810
    buffer[IDX_STREAM_STATE_WEIGHT] =
2811
10
        nghttp2_stream_get_weight(str);
2812
    buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2813
10
        nghttp2_stream_get_sum_dependency_weight(str);
2814
    buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2815
10
        nghttp2_session_get_stream_local_close(s, stream->id());
2816
    buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2817
10
        nghttp2_session_get_stream_remote_close(s, stream->id());
2818
    buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] =
2819
10
        nghttp2_session_get_stream_local_window_size(s, stream->id());
2820
  }
2821
}
2822
2823
5
void Http2Session::AltSvc(int32_t id,
2824
                          uint8_t* origin,
2825
                          size_t origin_len,
2826
                          uint8_t* value,
2827
                          size_t value_len) {
2828
10
  Http2Scope h2scope(this);
2829
5
  CHECK_EQ(nghttp2_submit_altsvc(session_.get(), NGHTTP2_FLAG_NONE, id,
2830
                                 origin, origin_len, value, value_len), 0);
2831
5
}
2832
2833
5
void Http2Session::Origin(const Origins& origins) {
2834
10
  Http2Scope h2scope(this);
2835
5
  CHECK_EQ(nghttp2_submit_origin(
2836
      session_.get(),
2837
      NGHTTP2_FLAG_NONE,
2838
      *origins,
2839
      origins.length()), 0);
2840
5
}
2841
2842
// Submits an AltSvc frame to be sent to the connected peer.
2843
5
void Http2Session::AltSvc(const FunctionCallbackInfo<Value>& args) {
2844
5
  Environment* env = Environment::GetCurrent(args);
2845
  Http2Session* session;
2846
5
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2847
2848
20
  int32_t id = args[0]->Int32Value(env->context()).ToChecked();
2849
2850
  // origin and value are both required to be ASCII, handle them as such.
2851
20
  Local<String> origin_str = args[1]->ToString(env->context()).ToLocalChecked();
2852
20
  Local<String> value_str = args[2]->ToString(env->context()).ToLocalChecked();
2853
2854

10
  if (origin_str.IsEmpty() || value_str.IsEmpty())
2855
    return;
2856
2857
5
  size_t origin_len = origin_str->Length();
2858
5
  size_t value_len = value_str->Length();
2859
2860
5
  CHECK_LE(origin_len + value_len, 16382);  // Max permitted for ALTSVC
2861
  // Verify that origin len != 0 if stream id == 0, or
2862
  // that origin len == 0 if stream id != 0
2863



5
  CHECK((origin_len != 0 && id == 0) || (origin_len == 0 && id != 0));
2864
2865
10
  MaybeStackBuffer<uint8_t> origin(origin_len);
2866
10
  MaybeStackBuffer<uint8_t> value(value_len);
2867
10
  origin_str->WriteOneByte(env->isolate(), *origin);
2868
10
  value_str->WriteOneByte(env->isolate(), *value);
2869
2870
5
  session->AltSvc(id, *origin, origin_len, *value, value_len);
2871
}
2872
2873
5
void Http2Session::Origin(const FunctionCallbackInfo<Value>& args) {
2874
5
  Environment* env = Environment::GetCurrent(args);
2875
5
  Local<Context> context = env->context();
2876
  Http2Session* session;
2877
5
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2878
2879
10
  Local<String> origin_string = args[0].As<String>();
2880
15
  size_t count = args[1]->Int32Value(context).ToChecked();
2881
2882
5
  session->Origin(Origins(env, origin_string, count));
2883
}
2884
2885
// Submits a PING frame to be sent to the connected peer.
2886
13
void Http2Session::Ping(const FunctionCallbackInfo<Value>& args) {
2887
  Http2Session* session;
2888
13
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2889
2890
  // A PING frame may have exactly 8 bytes of payload data. If not provided,
2891
  // then the current hrtime will be used as the payload.
2892
13
  ArrayBufferViewContents<uint8_t, 8> payload;
2893
26
  if (args[0]->IsArrayBufferView()) {
2894
12
    payload.Read(args[0].As<ArrayBufferView>());
2895
6
    CHECK_EQ(payload.length(), 8);
2896
  }
2897
2898
26
  CHECK(args[1]->IsFunction());
2899
39
  args.GetReturnValue().Set(
2900
39
      session->AddPing(payload.data(), args[1].As<Function>()));
2901
}
2902
2903
// Submits a SETTINGS frame for the Http2Session
2904
676
void Http2Session::Settings(const FunctionCallbackInfo<Value>& args) {
2905
  Http2Session* session;
2906
676
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2907
1352
  CHECK(args[0]->IsFunction());
2908
2704
  args.GetReturnValue().Set(session->AddSettings(args[0].As<Function>()));
2909
}
2910
2911
659
BaseObjectPtr<Http2Ping> Http2Session::PopPing() {
2912
659
  BaseObjectPtr<Http2Ping> ping;
2913
659
  if (!outstanding_pings_.empty()) {
2914
11
    ping = std::move(outstanding_pings_.front());
2915
11
    outstanding_pings_.pop();
2916
11
    DecrementCurrentSessionMemory(sizeof(*ping));
2917
  }
2918
659
  return ping;
2919
}
2920
2921
13
bool Http2Session::AddPing(const uint8_t* payload, Local<Function> callback) {
2922
  Local<Object> obj;
2923
39
  if (!env()->http2ping_constructor_template()
2924
39
          ->NewInstance(env()->context())
2925
13
              .ToLocal(&obj)) {
2926
    return false;
2927
  }
2928
2929
  BaseObjectPtr<Http2Ping> ping =
2930
26
      MakeDetachedBaseObject<Http2Ping>(this, obj, callback);
2931
13
  if (!ping)
2932
    return false;
2933
2934
13
  if (outstanding_pings_.size() == max_outstanding_pings_) {
2935
2
    ping->Done(false);
2936
2
    return false;
2937
  }
2938
2939
11
  IncrementCurrentSessionMemory(sizeof(*ping));
2940
  // The Ping itself is an Async resource. When the acknowledgement is received,
2941
  // the callback will be invoked and a notification sent out to JS land. The
2942
  // notification will include the duration of the ping, allowing the round
2943
  // trip to be measured.
2944
11
  ping->Send(payload);
2945
2946
11
  outstanding_pings_.emplace(std::move(ping));
2947
11
  return true;
2948
}
2949
2950
540
BaseObjectPtr<Http2Settings> Http2Session::PopSettings() {
2951
540
  BaseObjectPtr<Http2Settings> settings;
2952
540
  if (!outstanding_settings_.empty()) {
2953
540
    settings = std::move(outstanding_settings_.front());
2954
540
    outstanding_settings_.pop();
2955
540
    DecrementCurrentSessionMemory(sizeof(*settings));
2956
  }
2957
540
  return settings;
2958
}
2959
2960
676
bool Http2Session::AddSettings(Local<Function> callback) {
2961
  Local<Object> obj;
2962
2028
  if (!env()->http2settings_constructor_template()
2963
2028
          ->NewInstance(env()->context())
2964
676
              .ToLocal(&obj)) {
2965
    return false;
2966
  }
2967
2968
  BaseObjectPtr<Http2Settings> settings =
2969
1352
      MakeDetachedBaseObject<Http2Settings>(this, obj, callback, 0);
2970
676
  if (!settings)
2971
    return false;
2972
2973
676
  if (outstanding_settings_.size() == max_outstanding_settings_) {
2974
2
    settings->Done(false);
2975
2
    return false;
2976
  }
2977
2978
674
  IncrementCurrentSessionMemory(sizeof(*settings));
2979
674
  settings->Send();
2980
674
  outstanding_settings_.emplace(std::move(settings));
2981
674
  return true;
2982
}
2983
2984
13
Http2Ping::Http2Ping(
2985
    Http2Session* session,
2986
    Local<Object> obj,
2987
13
    Local<Function> callback)
2988
    : AsyncWrap(session->env(), obj, AsyncWrap::PROVIDER_HTTP2PING),
2989
      session_(session),
2990
26
      startTime_(uv_hrtime()) {
2991
13
  callback_.Reset(env()->isolate(), callback);
2992
13
}
2993
2994
void Http2Ping::MemoryInfo(MemoryTracker* tracker) const {
2995
  tracker->TrackField("callback", callback_);
2996
}
2997
2998
13
Local<Function> Http2Ping::callback() const {
2999
26
  return callback_.Get(env()->isolate());
3000
}
3001
3002
11
void Http2Ping::Send(const uint8_t* payload) {
3003
11
  CHECK(session_);
3004
  uint8_t data[8];
3005
11
  if (payload == nullptr) {
3006
5
    memcpy(&data, &startTime_, arraysize(data));
3007
5
    payload = data;
3008
  }
3009
22
  Http2Scope h2scope(session_.get());
3010
11
  CHECK_EQ(nghttp2_submit_ping(
3011
      session_->session(),
3012
      NGHTTP2_FLAG_NONE,
3013
      payload), 0);
3014
11
}
3015
3016
13
void Http2Ping::Done(bool ack, const uint8_t* payload) {
3017
13
  uint64_t duration_ns = uv_hrtime() - startTime_;
3018
13
  double duration_ms = duration_ns / 1e6;
3019
13
  if (session_) session_->statistics_.ping_rtt = duration_ns;
3020
3021
13
  Isolate* isolate = env()->isolate();
3022
26
  HandleScope handle_scope(isolate);
3023
13
  Context::Scope context_scope(env()->context());
3024
3025
  Local<Value> buf = Undefined(isolate);
3026
13
  if (payload != nullptr) {
3027
30
    buf = Buffer::Copy(isolate,
3028
                       reinterpret_cast<const char*>(payload),
3029
10
                       8).ToLocalChecked();
3030
  }
3031
3032
  Local<Value> argv[] = {
3033
    ack ? v8::True(isolate) : v8::False(isolate),
3034
    Number::New(isolate, duration_ms),
3035
    buf
3036
39
  };
3037
13
  MakeCallback(callback(), arraysize(argv), argv);
3038
13
}
3039
3040
1
void Http2Ping::DetachFromSession() {
3041
1
  session_.reset();
3042
1
}
3043
3044
void NgHttp2StreamWrite::MemoryInfo(MemoryTracker* tracker) const {
3045
  if (req_wrap)
3046
    tracker->TrackField("req_wrap", req_wrap);
3047
  tracker->TrackField("buf", buf);
3048
}
3049
3050
247
void SetCallbackFunctions(const FunctionCallbackInfo<Value>& args) {
3051
247
  Environment* env = Environment::GetCurrent(args);
3052
247
  CHECK_EQ(args.Length(), 11);
3053
3054
#define SET_FUNCTION(arg, name)                                               \
3055
  CHECK(args[arg]->IsFunction());                                             \
3056
  env->set_http2session_on_ ## name ## _function(args[arg].As<Function>());
3057
3058
988
  SET_FUNCTION(0, error)
3059
988
  SET_FUNCTION(1, priority)
3060
988
  SET_FUNCTION(2, settings)
3061
988
  SET_FUNCTION(3, ping)
3062
988
  SET_FUNCTION(4, headers)
3063
988
  SET_FUNCTION(5, frame_error)
3064
988
  SET_FUNCTION(6, goaway_data)
3065
988
  SET_FUNCTION(7, altsvc)
3066
988
  SET_FUNCTION(8, origin)
3067
988
  SET_FUNCTION(9, stream_trailers)
3068
988
  SET_FUNCTION(10, stream_close)
3069
3070
#undef SET_FUNCTION
3071
247
}
3072
3073
2
void Http2State::MemoryInfo(MemoryTracker* tracker) const {
3074
2
  tracker->TrackField("root_buffer", root_buffer);
3075
2
}
3076
3077
// TODO(addaleax): Remove once we're on C++17.
3078
constexpr FastStringKey Http2State::type_name;
3079
3080
// Set up the process.binding('http2') binding.
3081
252
void Initialize(Local<Object> target,
3082
                Local<Value> unused,
3083
                Local<Context> context,
3084
                void* priv) {
3085
252
  Environment* env = Environment::GetCurrent(context);
3086
252
  Isolate* isolate = env->isolate();
3087
504
  HandleScope handle_scope(isolate);
3088
3089
252
  Http2State* const state = env->AddBindingData<Http2State>(context, target);
3090
252
  if (state == nullptr) return;
3091
3092
#define SET_STATE_TYPEDARRAY(name, field)             \
3093
  target->Set(context,                                \
3094
              FIXED_ONE_BYTE_STRING(isolate, (name)), \
3095
              (field)).FromJust()
3096
3097
  // Initialize the buffer used to store the session state
3098
1008
  SET_STATE_TYPEDARRAY(
3099
    "sessionState", state->session_state_buffer.GetJSArray());
3100
  // Initialize the buffer used to store the stream state
3101
1008
  SET_STATE_TYPEDARRAY(
3102
    "streamState", state->stream_state_buffer.GetJSArray());
3103
1008
  SET_STATE_TYPEDARRAY(
3104
    "settingsBuffer", state->settings_buffer.GetJSArray());
3105
1008
  SET_STATE_TYPEDARRAY(
3106
    "optionsBuffer", state->options_buffer.GetJSArray());
3107
1008
  SET_STATE_TYPEDARRAY(
3108
    "streamStats", state->stream_stats_buffer.GetJSArray());
3109
1008
  SET_STATE_TYPEDARRAY(
3110
    "sessionStats", state->session_stats_buffer.GetJSArray());
3111
#undef SET_STATE_TYPEDARRAY
3112
3113
1008
  NODE_DEFINE_CONSTANT(target, kBitfield);
3114
252
  NODE_DEFINE_CONSTANT(target, kSessionPriorityListenerCount);
3115
756
  NODE_DEFINE_CONSTANT(target, kSessionFrameErrorListenerCount);
3116
1008
  NODE_DEFINE_CONSTANT(target, kSessionMaxInvalidFrames);
3117
1764
  NODE_DEFINE_CONSTANT(target, kSessionMaxRejectedStreams);
3118
2016
  NODE_DEFINE_CONSTANT(target, kSessionUint8FieldCount);
3119
1764
3120
1512
  NODE_DEFINE_CONSTANT(target, kSessionHasRemoteSettingsListeners);
3121
1512
  NODE_DEFINE_CONSTANT(target, kSessionRemoteSettingsIsUpToDate);
3122
1512
  NODE_DEFINE_CONSTANT(target, kSessionHasPingListeners);
3123
1260
  NODE_DEFINE_CONSTANT(target, kSessionHasAltsvcListeners);
3124
1764
3125
1260
  // Method to fetch the nghttp2 string description of an nghttp2 error code
3126
1008
  env->SetMethod(target, "nghttp2ErrorString", HttpErrorString);
3127
756
  env->SetMethod(target, "refreshDefaultSettings", RefreshDefaultSettings);
3128
252
  env->SetMethod(target, "packSettings", PackSettings);
3129
252
  env->SetMethod(target, "setCallbackFunctions", SetCallbackFunctions);
3130
3131
252
  Local<FunctionTemplate> ping = FunctionTemplate::New(env->isolate());
3132
504
  ping->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Ping"));
3133
504
  ping->Inherit(AsyncWrap::GetConstructorTemplate(env));
3134
252
  Local<ObjectTemplate> pingt = ping->InstanceTemplate();
3135
252
  pingt->SetInternalFieldCount(Http2Ping::kInternalFieldCount);
3136
252
  env->set_http2ping_constructor_template(pingt);
3137
3138
252
  Local<FunctionTemplate> setting = FunctionTemplate::New(env->isolate());
3139
504
  setting->Inherit(AsyncWrap::GetConstructorTemplate(env));
3140
252
  Local<ObjectTemplate> settingt = setting->InstanceTemplate();
3141
252
  settingt->SetInternalFieldCount(AsyncWrap::kInternalFieldCount);
3142
252
  env->set_http2settings_constructor_template(settingt);
3143
3144
252
  Local<FunctionTemplate> stream = FunctionTemplate::New(env->isolate());
3145
252
  env->SetProtoMethod(stream, "id", Http2Stream::GetID);
3146
252
  env->SetProtoMethod(stream, "destroy", Http2Stream::Destroy);
3147
252
  env->SetProtoMethod(stream, "priority", Http2Stream::Priority);
3148
252
  env->SetProtoMethod(stream, "pushPromise", Http2Stream::PushPromise);
3149
252
  env->SetProtoMethod(stream, "info", Http2Stream::Info);
3150
252
  env->SetProtoMethod(stream, "trailers", Http2Stream::Trailers);
3151
252
  env->SetProtoMethod(stream, "respond", Http2Stream::Respond);
3152
252
  env->SetProtoMethod(stream, "rstStream", Http2Stream::RstStream);
3153
252
  env->SetProtoMethod(stream, "refreshState", Http2Stream::RefreshState);
3154
504
  stream->Inherit(AsyncWrap::GetConstructorTemplate(env));
3155
252
  StreamBase::AddMethods(env, stream);
3156
252
  Local<ObjectTemplate> streamt = stream->InstanceTemplate();
3157
252
  streamt->SetInternalFieldCount(StreamBase::kInternalFieldCount);
3158
252
  env->set_http2stream_constructor_template(streamt);
3159
252
  env->SetConstructorFunction(target, "Http2Stream", stream);
3160
3161
  Local<FunctionTemplate> session =
3162
252
      env->NewFunctionTemplate(Http2Session::New);
3163
756
  session->InstanceTemplate()->SetInternalFieldCount(
3164
252
      Http2Session::kInternalFieldCount);
3165
504
  session->Inherit(AsyncWrap::GetConstructorTemplate(env));
3166
252
  env->SetProtoMethod(session, "origin", Http2Session::Origin);
3167
252
  env->SetProtoMethod(session, "altsvc", Http2Session::AltSvc);
3168
252
  env->SetProtoMethod(session, "ping", Http2Session::Ping);
3169
252
  env->SetProtoMethod(session, "consume", Http2Session::Consume);
3170
252
  env->SetProtoMethod(session, "receive", Http2Session::Receive);
3171
252
  env->SetProtoMethod(session, "destroy", Http2Session::Destroy);
3172
252
  env->SetProtoMethod(session, "goaway", Http2Session::Goaway);
3173
252
  env->SetProtoMethod(session, "settings", Http2Session::Settings);
3174
252
  env->SetProtoMethod(session, "request", Http2Session::Request);
3175
  env->SetProtoMethod(session, "setNextStreamID",
3176
252
                      Http2Session::SetNextStreamID);
3177
  env->SetProtoMethod(session, "setLocalWindowSize",
3178
252
                      Http2Session::SetLocalWindowSize);
3179
  env->SetProtoMethod(session, "updateChunksSent",
3180
252
                      Http2Session::UpdateChunksSent);
3181
252
  env->SetProtoMethod(session, "refreshState", Http2Session::RefreshState);
3182
  env->SetProtoMethod(
3183
      session, "localSettings",
3184
252
      Http2Session::RefreshSettings<nghttp2_session_get_local_settings>);
3185
  env->SetProtoMethod(
3186
      session, "remoteSettings",
3187
252
      Http2Session::RefreshSettings<nghttp2_session_get_remote_settings>);
3188
252
  env->SetConstructorFunction(target, "Http2Session", session);
3189
3190
252
  Local<Object> constants = Object::New(isolate);
3191
3192
  // This does alocate one more slot than needed but it's not used.
3193
#define V(name) FIXED_ONE_BYTE_STRING(isolate, #name),
3194
  Local<Value> error_code_names[] = {
3195
    HTTP2_ERROR_CODES(V)
3196
3780
  };
3197
#undef V
3198
3199
  Local<Array> name_for_error_code =
3200
      Array::New(
3201
          isolate,
3202
          error_code_names,
3203
252
          arraysize(error_code_names));
3204
3205
504
  target->Set(context,
3206
              FIXED_ONE_BYTE_STRING(isolate, "nameForErrorCode"),
3207
756
              name_for_error_code).Check();
3208
3209
#define V(constant) NODE_DEFINE_HIDDEN_CONSTANT(constants, constant);
3210
7056
  HTTP2_HIDDEN_CONSTANTS(V)
3211
504
#undef V
3212
504
3213
252
#define V(constant) NODE_DEFINE_CONSTANT(constants, constant);
3214
51912
  HTTP2_CONSTANTS(V)
3215
252
#undef V
3216
504
3217
252
  // NGHTTP2_DEFAULT_WEIGHT is a macro and not a regular define
3218
756
  // it won't be set properly on the constants object if included
3219
756
  // in the HTTP2_CONSTANTS macro.
3220
756
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_DEFAULT_WEIGHT);
3221
756
3222
756
#define V(NAME, VALUE)                                          \
3223
1512
  NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_HEADER_" # NAME, VALUE);
3224
118944
  HTTP_KNOWN_HEADERS(V)
3225
1260
#undef V
3226
756
3227
1008
#define V(NAME, VALUE)                                          \
3228
1260
  NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_METHOD_" # NAME, VALUE);
3229
49644
  HTTP_KNOWN_METHODS(V)
3230
1764
#undef V
3231
1512
3232
1512
#define V(name, _) NODE_DEFINE_CONSTANT(constants, HTTP_STATUS_##name);
3233
59220
  HTTP_STATUS_CODES(V)
3234
1764
#undef V
3235
2268
3236
2772
  target->Set(context, env->constants_string(), constants).Check();
3237
1764
}
3238
1764
}  // namespace http2
3239
2016
}  // namespace node
3240
2016
3241

21328
NODE_MODULE_CONTEXT_AWARE_INTERNAL(http2, node::http2::Initialize)