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: 1529 1591 96.1 %
Date: 2019-02-26 22:23:30 Branches: 528 737 71.6 %

Line Branch Exec Source
1
#include "aliased_buffer.h"
2
#include "debug_utils.h"
3
#include "node.h"
4
#include "node_buffer.h"
5
#include "node_http2.h"
6
#include "node_http2_state.h"
7
#include "node_perf.h"
8
#include "util.h"
9
10
#include <algorithm>
11
12
namespace node {
13
14
using v8::ArrayBuffer;
15
using v8::Boolean;
16
using v8::Context;
17
using v8::Float64Array;
18
using v8::Function;
19
using v8::Integer;
20
using v8::NewStringType;
21
using v8::Number;
22
using v8::ObjectTemplate;
23
using v8::String;
24
using v8::Uint32;
25
using v8::Uint32Array;
26
using v8::Undefined;
27
28
using node::performance::PerformanceEntry;
29
namespace http2 {
30
31
namespace {
32
33
const char zero_bytes_256[256] = {};
34
35
33527
inline Http2Stream* GetStream(Http2Session* session,
36
                              int32_t id,
37
                              nghttp2_data_source* source) {
38
33527
  Http2Stream* stream = static_cast<Http2Stream*>(source->ptr);
39
33527
  if (stream == nullptr)
40
28011
    stream = session->FindStream(id);
41
33527
  CHECK_NOT_NULL(stream);
42
33527
  CHECK_EQ(id, stream->id());
43
33527
  return stream;
44
}
45
46
}  // anonymous namespace
47
48
// These configure the callbacks required by nghttp2 itself. There are
49
// two sets of callback functions, one that is used if a padding callback
50
// is set, and other that does not include the padding callback.
51
8564
const Http2Session::Callbacks Http2Session::callback_struct_saved[2] = {
52
    Callbacks(false),
53
4282
    Callbacks(true)};
54
55
// The Http2Scope object is used to queue a write to the i/o stream. It is
56
// used whenever any action is take on the underlying nghttp2 API that may
57
// push data into nghttp2 outbound data queue.
58
//
59
// For example:
60
//
61
// Http2Scope h2scope(session);
62
// nghttp2_submit_ping(**session, ... );
63
//
64
// When the Http2Scope passes out of scope and is deconstructed, it will
65
// call Http2Session::MaybeScheduleWrite().
66
55654
Http2Scope::Http2Scope(Http2Stream* stream) : Http2Scope(stream->session()) {}
67
68
95083
Http2Scope::Http2Scope(Http2Session* session) {
69
95083
  if (session == nullptr)
70
    return;
71
72
95083
  if (session->flags_ & (SESSION_STATE_HAS_SCOPE |
73
                         SESSION_STATE_WRITE_SCHEDULED)) {
74
    // There is another scope further below on the stack, or it is already
75
    // known that a write is scheduled. In either case, there is nothing to do.
76
66506
    return;
77
  }
78
28577
  session->flags_ |= SESSION_STATE_HAS_SCOPE;
79
28577
  session_ = session;
80
81
  // Always keep the session object alive for at least as long as
82
  // this scope is active.
83
28577
  session_handle_ = session->object();
84
57154
  CHECK(!session_handle_.IsEmpty());
85
}
86
87
95083
Http2Scope::~Http2Scope() {
88
95083
  if (session_ == nullptr)
89
66506
    return;
90
91
28577
  session_->flags_ &= ~SESSION_STATE_HAS_SCOPE;
92
28577
  session_->MaybeScheduleWrite();
93
28577
}
94
95
// The Http2Options object is used during the construction of Http2Session
96
// instances to configure an appropriate nghttp2_options struct. The class
97
// uses a single TypedArray instance that is shared with the JavaScript side
98
// to more efficiently pass values back and forth.
99
555
Http2Options::Http2Options(Environment* env, nghttp2_session_type type) {
100
555
  nghttp2_option_new(&options_);
101
102
  // Make sure closed connections aren't kept around, taking up memory.
103
  // Note that this breaks the priority tree, which we don't use.
104
555
  nghttp2_option_set_no_closed_streams(options_, 1);
105
106
  // We manually handle flow control within a session in order to
107
  // implement backpressure -- that is, we only send WINDOW_UPDATE
108
  // frames to the remote peer as data is actually consumed by user
109
  // code. This ensures that the flow of data over the connection
110
  // does not move too quickly and limits the amount of data we
111
  // are required to buffer.
112
555
  nghttp2_option_set_no_auto_window_update(options_, 1);
113
114
  // Enable built in support for receiving ALTSVC and ORIGIN frames (but
115
  // only on client side sessions
116
555
  if (type == NGHTTP2_SESSION_CLIENT) {
117
273
    nghttp2_option_set_builtin_recv_extension_type(options_, NGHTTP2_ALTSVC);
118
273
    nghttp2_option_set_builtin_recv_extension_type(options_, NGHTTP2_ORIGIN);
119
  }
120
121
  AliasedBuffer<uint32_t, Uint32Array>& buffer =
122
555
      env->http2_state()->options_buffer;
123
555
  uint32_t flags = buffer[IDX_OPTIONS_FLAGS];
124
125
555
  if (flags & (1 << IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE)) {
126
    nghttp2_option_set_max_deflate_dynamic_table_size(
127
        options_,
128
        buffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE]);
129
  }
130
131
555
  if (flags & (1 << IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS)) {
132
    nghttp2_option_set_max_reserved_remote_streams(
133
        options_,
134
1
        buffer[IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS]);
135
  }
136
137
555
  if (flags & (1 << IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH)) {
138
    nghttp2_option_set_max_send_header_block_length(
139
        options_,
140
1
        buffer[IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH]);
141
  }
142
143
  // Recommended default
144
555
  nghttp2_option_set_peer_max_concurrent_streams(options_, 100);
145
555
  if (flags & (1 << IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS)) {
146
    nghttp2_option_set_peer_max_concurrent_streams(
147
        options_,
148
        buffer[IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS]);
149
  }
150
151
  // The padding strategy sets the mechanism by which we determine how much
152
  // additional frame padding to apply to DATA and HEADERS frames. Currently
153
  // this is set on a per-session basis, but eventually we may switch to
154
  // a per-stream setting, giving users greater control
155
555
  if (flags & (1 << IDX_OPTIONS_PADDING_STRATEGY)) {
156
    padding_strategy_type strategy =
157
        static_cast<padding_strategy_type>(
158
4
            buffer.GetValue(IDX_OPTIONS_PADDING_STRATEGY));
159
4
    SetPaddingStrategy(strategy);
160
  }
161
162
  // The max header list pairs option controls the maximum number of
163
  // header pairs the session may accept. This is a hard limit.. that is,
164
  // if the remote peer sends more than this amount, the stream will be
165
  // automatically closed with an RST_STREAM.
166
555
  if (flags & (1 << IDX_OPTIONS_MAX_HEADER_LIST_PAIRS)) {
167
3
    SetMaxHeaderPairs(buffer[IDX_OPTIONS_MAX_HEADER_LIST_PAIRS]);
168
  }
169
170
  // The HTTP2 specification places no limits on the number of HTTP2
171
  // PING frames that can be sent. In order to prevent PINGS from being
172
  // abused as an attack vector, however, we place a strict upper limit
173
  // on the number of unacknowledged PINGS that can be sent at any given
174
  // time.
175
555
  if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_PINGS)) {
176
2
    SetMaxOutstandingPings(buffer[IDX_OPTIONS_MAX_OUTSTANDING_PINGS]);
177
  }
178
179
  // The HTTP2 specification places no limits on the number of HTTP2
180
  // SETTINGS frames that can be sent. In order to prevent PINGS from being
181
  // abused as an attack vector, however, we place a strict upper limit
182
  // on the number of unacknowledged SETTINGS that can be sent at any given
183
  // time.
184
555
  if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS)) {
185
2
    SetMaxOutstandingSettings(buffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS]);
186
  }
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
555
  if (flags & (1 << IDX_OPTIONS_MAX_SESSION_MEMORY)) {
197
2
    SetMaxSessionMemory(buffer[IDX_OPTIONS_MAX_SESSION_MEMORY] * 1e6);
198
  }
199
555
}
200
201
578
void Http2Session::Http2Settings::Init() {
202
  AliasedBuffer<uint32_t, Uint32Array>& buffer =
203
578
      env()->http2_state()->settings_buffer;
204
578
  uint32_t flags = buffer[IDX_SETTINGS_COUNT];
205
206
578
  size_t n = 0;
207
208
#define GRABSETTING(N, trace)                                                 \
209
  if (flags & (1 << IDX_SETTINGS_##N)) {                                      \
210
    uint32_t val = buffer[IDX_SETTINGS_##N];                                  \
211
    if (session_ != nullptr)                                                  \
212
      Debug(session_, "setting " trace ": %d\n", val);                        \
213
    entries_[n++] =                                                           \
214
        nghttp2_settings_entry {NGHTTP2_SETTINGS_##N, val};                   \
215
  }
216
217

583
  GRABSETTING(HEADER_TABLE_SIZE, "header table size");
218

583
  GRABSETTING(MAX_CONCURRENT_STREAMS, "max concurrent streams");
219

584
  GRABSETTING(MAX_FRAME_SIZE, "max frame size");
220

596
  GRABSETTING(INITIAL_WINDOW_SIZE, "initial window size");
221

585
  GRABSETTING(MAX_HEADER_LIST_SIZE, "max header list size");
222

589
  GRABSETTING(ENABLE_PUSH, "enable push");
223

581
  GRABSETTING(ENABLE_CONNECT_PROTOCOL, "enable connect protocol");
224
225
#undef GRABSETTING
226
227
578
  count_ = n;
228
578
}
229
230
// The Http2Settings class is used to configure a SETTINGS frame that is
231
// to be sent to the connected peer. The settings are set using a TypedArray
232
// that is shared with the JavaScript side.
233
578
Http2Session::Http2Settings::Http2Settings(Environment* env,
234
                                           Http2Session* session,
235
                                           Local<Object> obj,
236
                                           uint64_t start_time)
237
    : AsyncWrap(env, obj, PROVIDER_HTTP2SETTINGS),
238
      session_(session),
239
578
      startTime_(start_time) {
240
578
  Init();
241
578
}
242
243
// Generates a Buffer that contains the serialized payload of a SETTINGS
244
// frame. This can be used, for instance, to create the Base64-encoded
245
// content of an Http2-Settings header field.
246
15
Local<Value> Http2Session::Http2Settings::Pack() {
247
15
  const size_t len = count_ * 6;
248
30
  Local<Value> buf = Buffer::New(env(), len).ToLocalChecked();
249
  ssize_t ret =
250
      nghttp2_pack_settings_payload(
251
15
        reinterpret_cast<uint8_t*>(Buffer::Data(buf)), len,
252
30
        &entries_[0], count_);
253
15
  if (ret >= 0)
254
14
    return buf;
255
  else
256
2
    return Undefined(env()->isolate());
257
}
258
259
// Updates the shared TypedArray with the current remote or local settings for
260
// the session.
261
18253
void Http2Session::Http2Settings::Update(Environment* env,
262
                                         Http2Session* session,
263
                                         get_setting fn) {
264
  AliasedBuffer<uint32_t, Uint32Array>& buffer =
265
18253
      env->http2_state()->settings_buffer;
266
36506
  buffer[IDX_SETTINGS_HEADER_TABLE_SIZE] =
267
36506
      fn(**session, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE);
268
36506
  buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS] =
269
36506
      fn(**session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
270
36506
  buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] =
271
36506
      fn(**session, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
272
36506
  buffer[IDX_SETTINGS_MAX_FRAME_SIZE] =
273
36506
      fn(**session, NGHTTP2_SETTINGS_MAX_FRAME_SIZE);
274
36506
  buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] =
275
36506
      fn(**session, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE);
276
36506
  buffer[IDX_SETTINGS_ENABLE_PUSH] =
277
36506
      fn(**session, NGHTTP2_SETTINGS_ENABLE_PUSH);
278
36506
  buffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL] =
279
36506
      fn(**session, NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL);
280
18253
}
281
282
// Initializes the shared TypedArray with the default settings values.
283
3
void Http2Session::Http2Settings::RefreshDefaults(Environment* env) {
284
  AliasedBuffer<uint32_t, Uint32Array>& buffer =
285
3
      env->http2_state()->settings_buffer;
286
287
6
  buffer[IDX_SETTINGS_HEADER_TABLE_SIZE] =
288
3
      DEFAULT_SETTINGS_HEADER_TABLE_SIZE;
289
6
  buffer[IDX_SETTINGS_ENABLE_PUSH] =
290
3
      DEFAULT_SETTINGS_ENABLE_PUSH;
291
6
  buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] =
292
3
      DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE;
293
6
  buffer[IDX_SETTINGS_MAX_FRAME_SIZE] =
294
3
      DEFAULT_SETTINGS_MAX_FRAME_SIZE;
295
6
  buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] =
296
3
      DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE;
297
6
  buffer[IDX_SETTINGS_COUNT] =
298
    (1 << IDX_SETTINGS_HEADER_TABLE_SIZE) |
299
    (1 << IDX_SETTINGS_ENABLE_PUSH) |
300
    (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE) |
301
    (1 << IDX_SETTINGS_MAX_FRAME_SIZE) |
302
3
    (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE);
303
3
}
304
305
306
561
void Http2Session::Http2Settings::Send() {
307
561
  Http2Scope h2scope(session_);
308
561
  CHECK_EQ(nghttp2_submit_settings(**session_, NGHTTP2_FLAG_NONE,
309
561
                                   &entries_[0], count_), 0);
310
561
}
311
312
468
void Http2Session::Http2Settings::Done(bool ack) {
313
468
  uint64_t end = uv_hrtime();
314
468
  double duration = (end - startTime_) / 1e6;
315
316
  Local<Value> argv[2] = {
317
    Boolean::New(env()->isolate(), ack),
318
    Number::New(env()->isolate(), duration)
319
1404
  };
320
468
  MakeCallback(env()->ondone_string(), arraysize(argv), argv);
321
468
  delete this;
322
468
}
323
324
// The Http2Priority class initializes an appropriate nghttp2_priority_spec
325
// struct used when either creating a stream or updating its priority
326
// settings.
327
10615
Http2Priority::Http2Priority(Environment* env,
328
                             Local<Value> parent,
329
                             Local<Value> weight,
330
                             Local<Value> exclusive) {
331
10615
  Local<Context> context = env->context();
332
21230
  int32_t parent_ = parent->Int32Value(context).ToChecked();
333
21230
  int32_t weight_ = weight->Int32Value(context).ToChecked();
334
21230
  bool exclusive_ = exclusive->BooleanValue(env->isolate());
335
  Debug(env, DebugCategory::HTTP2STREAM,
336
        "Http2Priority: parent: %d, weight: %d, exclusive: %d\n",
337
        parent_, weight_, exclusive_);
338
10615
  nghttp2_priority_spec_init(&spec, parent_, weight_, exclusive_ ? 1 : 0);
339
10615
}
340
341
342
130
const char* Http2Session::TypeName() const {
343
130
  switch (session_type_) {
344
68
    case NGHTTP2_SESSION_SERVER: return "server";
345
62
    case NGHTTP2_SESSION_CLIENT: return "client";
346
    default:
347
      // This should never happen
348
      ABORT();
349
  }
350
}
351
352
// The Headers class initializes a proper array of nghttp2_nv structs
353
// containing the header name value pairs.
354
21190
Headers::Headers(Isolate* isolate,
355
                 Local<Context> context,
356
21190
                 Local<Array> headers) {
357
42380
  Local<Value> header_string = headers->Get(context, 0).ToLocalChecked();
358
42380
  Local<Value> header_count = headers->Get(context, 1).ToLocalChecked();
359
42380
  count_ = header_count.As<Uint32>()->Value();
360
42380
  int header_string_len = header_string.As<String>()->Length();
361
362
21190
  if (count_ == 0) {
363
24
    CHECK_EQ(header_string_len, 0);
364
49
    return;
365
  }
366
367
  // Allocate a single buffer with count_ nghttp2_nv structs, followed
368
  // by the raw header data as passed from JS. This looks like:
369
  // | possible padding | nghttp2_nv | nghttp2_nv | ... | header contents |
370
  buf_.AllocateSufficientStorage((alignof(nghttp2_nv) - 1) +
371
21166
                                 count_ * sizeof(nghttp2_nv) +
372
42332
                                 header_string_len);
373
  // Make sure the start address is aligned appropriately for an nghttp2_nv*.
374
  char* start = reinterpret_cast<char*>(
375
21166
      RoundUp(reinterpret_cast<uintptr_t>(*buf_), alignof(nghttp2_nv)));
376
21166
  char* header_contents = start + (count_ * sizeof(nghttp2_nv));
377
21166
  nghttp2_nv* const nva = reinterpret_cast<nghttp2_nv*>(start);
378
379
21166
  CHECK_LE(header_contents + header_string_len, *buf_ + buf_.length());
380
63498
  CHECK_EQ(header_string.As<String>()->WriteOneByte(
381
               isolate,
382
               reinterpret_cast<uint8_t*>(header_contents),
383
               0,
384
               header_string_len,
385
               String::NO_NULL_TERMINATION),
386
           header_string_len);
387
388
21166
  size_t n = 0;
389
  char* p;
390
84989
  for (p = header_contents; p < header_contents + header_string_len; n++) {
391
63824
    if (n >= count_) {
392
      // This can happen if a passed header contained a null byte. In that
393
      // case, just provide nghttp2 with an invalid header to make it reject
394
      // the headers list.
395
      static uint8_t zero = '\0';
396
1
      nva[0].name = nva[0].value = &zero;
397
1
      nva[0].namelen = nva[0].valuelen = 1;
398
1
      count_ = 1;
399
1
      return;
400
    }
401
402
63823
    nva[n].flags = NGHTTP2_NV_FLAG_NONE;
403
63823
    nva[n].name = reinterpret_cast<uint8_t*>(p);
404
63823
    nva[n].namelen = strlen(p);
405
63823
    p += nva[n].namelen + 1;
406
63823
    nva[n].value = reinterpret_cast<uint8_t*>(p);
407
63823
    nva[n].valuelen = strlen(p);
408
63823
    p += nva[n].valuelen + 1;
409
  }
410
}
411
412
5
Origins::Origins(Isolate* isolate,
413
                 Local<Context> context,
414
                 Local<String> origin_string,
415
5
                 size_t origin_count) : count_(origin_count) {
416
5
  int origin_string_len = origin_string->Length();
417
5
  if (count_ == 0) {
418
    CHECK_EQ(origin_string_len, 0);
419
    return;
420
  }
421
422
  // Allocate a single buffer with count_ nghttp2_nv structs, followed
423
  // by the raw header data as passed from JS. This looks like:
424
  // | possible padding | nghttp2_nv | nghttp2_nv | ... | header contents |
425
  buf_.AllocateSufficientStorage((alignof(nghttp2_origin_entry) - 1) +
426
5
                                 count_ * sizeof(nghttp2_origin_entry) +
427
10
                                 origin_string_len);
428
429
  // Make sure the start address is aligned appropriately for an nghttp2_nv*.
430
  char* start = reinterpret_cast<char*>(
431
5
      RoundUp(reinterpret_cast<uintptr_t>(*buf_),
432
5
              alignof(nghttp2_origin_entry)));
433
5
  char* origin_contents = start + (count_ * sizeof(nghttp2_origin_entry));
434
  nghttp2_origin_entry* const nva =
435
5
      reinterpret_cast<nghttp2_origin_entry*>(start);
436
437
5
  CHECK_LE(origin_contents + origin_string_len, *buf_ + buf_.length());
438
10
  CHECK_EQ(origin_string->WriteOneByte(
439
               isolate,
440
               reinterpret_cast<uint8_t*>(origin_contents),
441
               0,
442
               origin_string_len,
443
               String::NO_NULL_TERMINATION),
444
           origin_string_len);
445
446
5
  size_t n = 0;
447
  char* p;
448
14
  for (p = origin_contents; p < origin_contents + origin_string_len; n++) {
449
9
    if (n >= count_) {
450
      static uint8_t zero = '\0';
451
      nva[0].origin = &zero;
452
      nva[0].origin_len = 1;
453
      count_ = 1;
454
      return;
455
    }
456
457
9
    nva[n].origin = reinterpret_cast<uint8_t*>(p);
458
9
    nva[n].origin_len = strlen(p);
459
9
    p += nva[n].origin_len + 1;
460
  }
461
}
462
463
// Sets the various callback functions that nghttp2 will use to notify us
464
// about significant events while processing http2 stuff.
465
8564
Http2Session::Callbacks::Callbacks(bool kHasGetPaddingCallback) {
466
8564
  CHECK_EQ(nghttp2_session_callbacks_new(&callbacks), 0);
467
468
  nghttp2_session_callbacks_set_on_begin_headers_callback(
469
8564
    callbacks, OnBeginHeadersCallback);
470
  nghttp2_session_callbacks_set_on_header_callback2(
471
8564
    callbacks, OnHeaderCallback);
472
  nghttp2_session_callbacks_set_on_frame_recv_callback(
473
8564
    callbacks, OnFrameReceive);
474
  nghttp2_session_callbacks_set_on_stream_close_callback(
475
8564
    callbacks, OnStreamClose);
476
  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
477
8564
    callbacks, OnDataChunkReceived);
478
  nghttp2_session_callbacks_set_on_frame_not_send_callback(
479
8564
    callbacks, OnFrameNotSent);
480
  nghttp2_session_callbacks_set_on_invalid_header_callback2(
481
8564
    callbacks, OnInvalidHeader);
482
  nghttp2_session_callbacks_set_error_callback(
483
8564
    callbacks, OnNghttpError);
484
  nghttp2_session_callbacks_set_send_data_callback(
485
8564
    callbacks, OnSendData);
486
  nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
487
8564
    callbacks, OnInvalidFrame);
488
  nghttp2_session_callbacks_set_on_frame_send_callback(
489
8564
    callbacks, OnFrameSent);
490
491
8564
  if (kHasGetPaddingCallback) {
492
    nghttp2_session_callbacks_set_select_padding_callback(
493
4282
      callbacks, OnSelectPadding);
494
  }
495
8564
}
496
497
498
8564
Http2Session::Callbacks::~Callbacks() {
499
8564
  nghttp2_session_callbacks_del(callbacks);
500
8564
}
501
502
// Track memory allocated by nghttp2 using a custom allocator.
503
class Http2Session::MemoryAllocatorInfo {
504
 public:
505
555
  explicit MemoryAllocatorInfo(Http2Session* session)
506
555
      : info({ session, H2Malloc, H2Free, H2Calloc, H2Realloc }) {}
507
508
116752
  static void* H2Malloc(size_t size, void* user_data) {
509
116752
    return H2Realloc(nullptr, size, user_data);
510
  }
511
512
1110
  static void* H2Calloc(size_t nmemb, size_t size, void* user_data) {
513
1110
    size_t real_size = MultiplyWithOverflowCheck(nmemb, size);
514
1110
    void* mem = H2Malloc(real_size, user_data);
515
1110
    if (mem != nullptr)
516
1110
      memset(mem, 0, real_size);
517
1110
    return mem;
518
  }
519
520
309718
  static void H2Free(void* ptr, void* user_data) {
521
619436
    if (ptr == nullptr) return;  // free(null); happens quite often.
522
117533
    void* result = H2Realloc(ptr, 0, user_data);
523
117533
    CHECK_NULL(result);
524
  }
525
526
235083
  static void* H2Realloc(void* ptr, size_t size, void* user_data) {
527
235083
    Http2Session* session = static_cast<Http2Session*>(user_data);
528
235083
    size_t previous_size = 0;
529
235083
    char* original_ptr = nullptr;
530
531
    // We prepend each allocated buffer with a size_t containing the full
532
    // size of the allocation.
533
235083
    if (size > 0) size += sizeof(size_t);
534
535
235083
    if (ptr != nullptr) {
536
      // We are free()ing or re-allocating.
537
117550
      original_ptr = static_cast<char*>(ptr) - sizeof(size_t);
538
117550
      previous_size = *reinterpret_cast<size_t*>(original_ptr);
539
      // This means we called StopTracking() on this pointer before.
540
117550
      if (previous_size == 0) {
541
        // Fall back to the standard Realloc() function.
542
773
        char* ret = UncheckedRealloc(original_ptr, size);
543
773
        if (ret != nullptr)
544
          ret += sizeof(size_t);
545
773
        return ret;
546
      }
547
    }
548
234310
    CHECK_GE(session->current_nghttp2_memory_, previous_size);
549
550
    // TODO(addaleax): Add the following, and handle NGHTTP2_ERR_NOMEM properly
551
    // everywhere:
552
    //
553
    // if (size > previous_size &&
554
    //     !session->IsAvailableSessionMemory(size - previous_size)) {
555
    //  return nullptr;
556
    //}
557
558
234310
    char* mem = UncheckedRealloc(original_ptr, size);
559
560
234310
    if (mem != nullptr) {
561
      // Adjust the memory info counter.
562
117550
      session->current_nghttp2_memory_ += size - previous_size;
563
117550
      *reinterpret_cast<size_t*>(mem) = size;
564
117550
      mem += sizeof(size_t);
565
116760
    } else if (size == 0) {
566
116760
      session->current_nghttp2_memory_ -= previous_size;
567
    }
568
569
234310
    return mem;
570
  }
571
572
21655
  static void StopTracking(Http2Session* session, void* ptr) {
573
    size_t* original_ptr = reinterpret_cast<size_t*>(
574
21655
        static_cast<char*>(ptr) - sizeof(size_t));
575
21655
    session->current_nghttp2_memory_ -= *original_ptr;
576
21655
    *original_ptr = 0;
577
21655
  }
578
579
555
  inline nghttp2_mem* operator*() { return &info; }
580
581
  nghttp2_mem info;
582
};
583
584
21655
void Http2Session::StopTrackingRcbuf(nghttp2_rcbuf* buf) {
585
21655
  MemoryAllocatorInfo::StopTracking(this, buf);
586
21655
}
587
588
555
Http2Session::Http2Session(Environment* env,
589
                           Local<Object> wrap,
590
                           nghttp2_session_type type)
591
    : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_HTTP2SESSION),
592
1110
      session_type_(type) {
593
555
  MakeWeak();
594
555
  statistics_.start_time = uv_hrtime();
595
596
  // Capture the configuration options for this session
597
555
  Http2Options opts(env, type);
598
599
555
  max_session_memory_ = opts.GetMaxSessionMemory();
600
601
555
  uint32_t maxHeaderPairs = opts.GetMaxHeaderPairs();
602
  max_header_pairs_ =
603
      type == NGHTTP2_SESSION_SERVER
604
1401
          ? std::max(maxHeaderPairs, 4U)     // minimum # of request headers
605

1392
          : std::max(maxHeaderPairs, 1U);    // minimum # of response headers
606
607
555
  max_outstanding_pings_ = opts.GetMaxOutstandingPings();
608
555
  max_outstanding_settings_ = opts.GetMaxOutstandingSettings();
609
610
555
  padding_strategy_ = opts.GetPaddingStrategy();
611
612
  bool hasGetPaddingCallback =
613
555
      padding_strategy_ != PADDING_STRATEGY_NONE;
614
615
  nghttp2_session_callbacks* callbacks
616
555
      = callback_struct_saved[hasGetPaddingCallback ? 1 : 0].callbacks;
617
618
  auto fn = type == NGHTTP2_SESSION_SERVER ?
619
      nghttp2_session_server_new3 :
620
555
      nghttp2_session_client_new3;
621
622
555
  MemoryAllocatorInfo allocator_info(this);
623
624
  // This should fail only if the system is out of memory, which
625
  // is going to cause lots of other problems anyway, or if any
626
  // of the options are out of acceptable range, which we should
627
  // be catching before it gets this far. Either way, crash if this
628
  // fails.
629
555
  CHECK_EQ(fn(&session_, callbacks, this, *opts, *allocator_info), 0);
630
631
555
  outgoing_storage_.reserve(4096);
632
555
  outgoing_buffers_.reserve(32);
633
555
}
634
635
1665
Http2Session::~Http2Session() {
636
555
  CHECK_EQ(flags_ & SESSION_STATE_HAS_SCOPE, 0);
637
555
  Debug(this, "freeing nghttp2 session");
638
555
  for (const auto& iter : streams_)
639
    iter.second->session_ = nullptr;
640
555
  nghttp2_session_del(session_);
641
555
  CHECK_EQ(current_nghttp2_memory_, 0);
642
1110
}
643
644
130
std::string Http2Session::diagnostic_name() const {
645
260
  return std::string("Http2Session ") + TypeName() + " (" +
646
390
      std::to_string(static_cast<int64_t>(get_async_id())) + ")";
647
}
648
649
21759
inline bool HasHttp2Observer(Environment* env) {
650
  AliasedBuffer<uint32_t, Uint32Array>& observers =
651
21759
      env->performance_state()->observers;
652
21759
  return observers[performance::NODE_PERFORMANCE_ENTRY_TYPE_HTTP2] != 0;
653
}
654
655
21224
void Http2Stream::EmitStatistics() {
656
21224
  if (!HasHttp2Observer(env()))
657
42446
    return;
658
  Http2StreamPerformanceEntry* entry =
659
2
    new Http2StreamPerformanceEntry(env(), id_, statistics_);
660
6
  env()->SetImmediate([](Environment* env, void* data) {
661
    // This takes ownership, the entry is destroyed at the end of this scope.
662
    std::unique_ptr<Http2StreamPerformanceEntry> entry {
663
2
        static_cast<Http2StreamPerformanceEntry*>(data) };
664
2
    if (!HasHttp2Observer(env))
665
2
      return;
666
4
    HandleScope handle_scope(env->isolate());
667
    AliasedBuffer<double, Float64Array>& buffer =
668
2
        env->http2_state()->stream_stats_buffer;
669
2
    buffer[IDX_STREAM_STATS_ID] = entry->id();
670
2
    if (entry->first_byte() != 0) {
671
      buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTE] =
672
          (entry->first_byte() - entry->startTimeNano()) / 1e6;
673
    } else {
674
2
      buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTE] = 0;
675
    }
676
2
    if (entry->first_header() != 0) {
677
4
      buffer[IDX_STREAM_STATS_TIMETOFIRSTHEADER] =
678
4
          (entry->first_header() - entry->startTimeNano()) / 1e6;
679
    } else {
680
      buffer[IDX_STREAM_STATS_TIMETOFIRSTHEADER] = 0;
681
    }
682
2
    if (entry->first_byte_sent() != 0) {
683
2
      buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTESENT] =
684
2
          (entry->first_byte_sent() - entry->startTimeNano()) / 1e6;
685
    } else {
686
1
      buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTESENT] = 0;
687
    }
688
2
    buffer[IDX_STREAM_STATS_SENTBYTES] = entry->sent_bytes();
689
2
    buffer[IDX_STREAM_STATS_RECEIVEDBYTES] = entry->received_bytes();
690
    Local<Object> obj;
691
6
    if (entry->ToObject().ToLocal(&obj)) entry->Notify(obj);
692
8
  }, static_cast<void*>(entry));
693
}
694
695
531
void Http2Session::EmitStatistics() {
696
531
  if (!HasHttp2Observer(env()))
697
1060
    return;
698
  Http2SessionPerformanceEntry* entry =
699
2
    new Http2SessionPerformanceEntry(env(), statistics_, session_type_);
700
6
  env()->SetImmediate([](Environment* env, void* data) {
701
    // This takes ownership, the entr is destroyed at the end of this scope.
702
    std::unique_ptr<Http2SessionPerformanceEntry> entry {
703
2
        static_cast<Http2SessionPerformanceEntry*>(data) };
704
2
    if (!HasHttp2Observer(env))
705
2
      return;
706
4
    HandleScope handle_scope(env->isolate());
707
    AliasedBuffer<double, Float64Array>& buffer =
708
2
        env->http2_state()->session_stats_buffer;
709
2
    buffer[IDX_SESSION_STATS_TYPE] = entry->type();
710
2
    buffer[IDX_SESSION_STATS_PINGRTT] = entry->ping_rtt() / 1e6;
711
2
    buffer[IDX_SESSION_STATS_FRAMESRECEIVED] = entry->frame_count();
712
2
    buffer[IDX_SESSION_STATS_FRAMESSENT] = entry->frame_sent();
713
2
    buffer[IDX_SESSION_STATS_STREAMCOUNT] = entry->stream_count();
714
4
    buffer[IDX_SESSION_STATS_STREAMAVERAGEDURATION] =
715
4
        entry->stream_average_duration();
716
2
    buffer[IDX_SESSION_STATS_DATA_SENT] = entry->data_sent();
717
2
    buffer[IDX_SESSION_STATS_DATA_RECEIVED] = entry->data_received();
718
4
    buffer[IDX_SESSION_STATS_MAX_CONCURRENT_STREAMS] =
719
4
        entry->max_concurrent_streams();
720
    Local<Object> obj;
721
6
    if (entry->ToObject().ToLocal(&obj)) entry->Notify(obj);
722
8
  }, static_cast<void*>(entry));
723
}
724
725
// Closes the session and frees the associated resources
726
531
void Http2Session::Close(uint32_t code, bool socket_closed) {
727
531
  Debug(this, "closing session");
728
729
531
  if (flags_ & SESSION_STATE_CLOSING)
730
531
    return;
731
531
  flags_ |= SESSION_STATE_CLOSING;
732
733
  // Stop reading on the i/o stream
734
531
  if (stream_ != nullptr)
735
520
    stream_->ReadStop();
736
737
  // If the socket is not closed, then attempt to send a closing GOAWAY
738
  // frame. There is no guarantee that this GOAWAY will be received by
739
  // the peer but the HTTP/2 spec recommends sending it anyway. We'll
740
  // make a best effort.
741
531
  if (!socket_closed) {
742
503
    Debug(this, "terminating session with code %d", code);
743
503
    CHECK_EQ(nghttp2_session_terminate_session(session_, code), 0);
744
503
    SendPendingData();
745
28
  } else if (stream_ != nullptr) {
746
17
    stream_->RemoveStreamListener(this);
747
  }
748
749
531
  flags_ |= SESSION_STATE_CLOSED;
750
751
  // If there are outstanding pings, those will need to be canceled, do
752
  // so on the next iteration of the event loop to avoid calling out into
753
  // javascript since this may be called during garbage collection.
754
1062
  while (!outstanding_pings_.empty()) {
755
    Http2Session::Http2Ping* ping = PopPing();
756
    env()->SetImmediate([](Environment* env, void* data) {
757
      static_cast<Http2Session::Http2Ping*>(data)->Done(false);
758
    }, static_cast<void*>(ping));
759
  }
760
761
531
  statistics_.end_time = uv_hrtime();
762
531
  EmitStatistics();
763
}
764
765
// Locates an existing known stream by ID. nghttp2 has a similar method
766
// but this is faster and does not fail if the stream is not found.
767
186135
inline Http2Stream* Http2Session::FindStream(int32_t id) {
768
186135
  auto s = streams_.find(id);
769
186135
  return s != streams_.end() ? s->second : nullptr;
770
}
771
772
10615
inline bool Http2Session::CanAddStream() {
773
  uint32_t maxConcurrentStreams =
774
      nghttp2_session_get_local_settings(
775
10615
          session_, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
776
  size_t maxSize =
777
10615
      std::min(streams_.max_size(), static_cast<size_t>(maxConcurrentStreams));
778
  // We can add a new stream so long as we are less than the current
779
  // maximum on concurrent streams and there's enough available memory
780

21230
  return streams_.size() < maxSize &&
781
21230
         IsAvailableSessionMemory(sizeof(Http2Stream));
782
}
783
784
21230
inline void Http2Session::AddStream(Http2Stream* stream) {
785
21230
  CHECK_GE(++statistics_.stream_count, 0);
786
21230
  streams_[stream->id()] = stream;
787
21230
  size_t size = streams_.size();
788
21230
  if (size > statistics_.max_concurrent_streams)
789
1008
    statistics_.max_concurrent_streams = size;
790
21230
  IncrementCurrentSessionMemory(sizeof(*stream));
791
21230
}
792
793
794
21230
inline void Http2Session::RemoveStream(Http2Stream* stream) {
795

21230
  if (streams_.empty() || stream == nullptr)
796
21230
    return;  // Nothing to remove, item was never added?
797
21230
  streams_.erase(stream->id());
798
21230
  DecrementCurrentSessionMemory(sizeof(*stream));
799
}
800
801
// Used as one of the Padding Strategy functions. Will attempt to ensure
802
// that the total frame size, including header bytes, are 8-byte aligned.
803
// If maxPayloadLen is smaller than the number of bytes necessary to align,
804
// will return maxPayloadLen instead.
805
4
ssize_t Http2Session::OnDWordAlignedPadding(size_t frameLen,
806
                                            size_t maxPayloadLen) {
807
4
  size_t r = (frameLen + 9) % 8;
808
4
  if (r == 0) return frameLen;  // If already a multiple of 8, return.
809
810
4
  size_t pad = frameLen + (8 - r);
811
812
  // If maxPayloadLen happens to be less than the calculated pad length,
813
  // use the max instead, even tho this means the frame will not be
814
  // aligned.
815
4
  pad = std::min(maxPayloadLen, pad);
816
4
  Debug(this, "using frame size padding: %d", pad);
817
4
  return pad;
818
}
819
820
// Used as one of the Padding Strategy functions. Uses the maximum amount
821
// of padding allowed for the current frame.
822
ssize_t Http2Session::OnMaxFrameSizePadding(size_t frameLen,
823
                                            size_t maxPayloadLen) {
824
  Debug(this, "using max frame size padding: %d", maxPayloadLen);
825
  return maxPayloadLen;
826
}
827
828
// Used as one of the Padding Strategy functions. Uses a callback to JS land
829
// to determine the amount of padding for the current frame. This option is
830
// rather more expensive because of the JS boundary cross. It generally should
831
// not be the preferred option.
832
4
ssize_t Http2Session::OnCallbackPadding(size_t frameLen,
833
                                        size_t maxPayloadLen) {
834
4
  if (frameLen == 0) return 0;
835
3
  Debug(this, "using callback to determine padding");
836
3
  Isolate* isolate = env()->isolate();
837
3
  HandleScope handle_scope(isolate);
838
3
  Local<Context> context = env()->context();
839
  Context::Scope context_scope(context);
840
841
  AliasedBuffer<uint32_t, Uint32Array>& buffer =
842
3
      env()->http2_state()->padding_buffer;
843
3
  buffer[PADDING_BUF_FRAME_LENGTH] = frameLen;
844
3
  buffer[PADDING_BUF_MAX_PAYLOAD_LENGTH] = maxPayloadLen;
845
3
  buffer[PADDING_BUF_RETURN_VALUE] = frameLen;
846
3
  MakeCallback(env()->http2session_on_select_padding_function(), 0, nullptr);
847
3
  uint32_t retval = buffer[PADDING_BUF_RETURN_VALUE];
848
3
  retval = std::min(retval, static_cast<uint32_t>(maxPayloadLen));
849
3
  retval = std::max(retval, static_cast<uint32_t>(frameLen));
850
3
  Debug(this, "using padding size %d", retval);
851
6
  return retval;
852
}
853
854
855
// Write data received from the i/o stream to the underlying nghttp2_session.
856
// On each call to nghttp2_session_mem_recv, nghttp2 will begin calling the
857
// various callback functions. Each of these will typically result in a call
858
// out to JavaScript so this particular function is rather hot and can be
859
// quite expensive. This is a potential performance optimization target later.
860
27732
ssize_t Http2Session::Write(const uv_buf_t* bufs, size_t nbufs) {
861
27732
  size_t total = 0;
862
  // Note that nghttp2_session_mem_recv is a synchronous operation that
863
  // will trigger a number of other callbacks. Those will, in turn have
864
  // multiple side effects.
865
55460
  for (size_t n = 0; n < nbufs; n++) {
866
    Debug(this, "receiving %d bytes [wants data? %d]",
867
27732
          bufs[n].len,
868
83196
          nghttp2_session_want_read(session_));
869
    ssize_t ret =
870
      nghttp2_session_mem_recv(session_,
871
27732
                               reinterpret_cast<uint8_t*>(bufs[n].base),
872
55464
                               bufs[n].len);
873
27732
    CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
874
875
27732
    if (ret < 0)
876
4
      return ret;
877
878
27728
    total += ret;
879
  }
880
  // Send any data that was queued up while processing the received data.
881
27728
  if (!IsDestroyed()) {
882
27259
    SendPendingData();
883
  }
884
27728
  return total;
885
}
886
887
888
126715
inline int32_t GetFrameID(const nghttp2_frame* frame) {
889
  // If this is a push promise, we want to grab the id of the promised stream
890
126715
  return (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
891
      frame->push_promise.promised_stream_id :
892
126715
      frame->hd.stream_id;
893
}
894
895
896
// Called by nghttp2 at the start of receiving a HEADERS frame. We use this
897
// callback to determine if a new stream is being created or if we are simply
898
// adding a new block of headers to an existing stream. The header pairs
899
// themselves are set in the OnHeaderCallback
900
21155
int Http2Session::OnBeginHeadersCallback(nghttp2_session* handle,
901
                                         const nghttp2_frame* frame,
902
                                         void* user_data) {
903
21155
  Http2Session* session = static_cast<Http2Session*>(user_data);
904
21155
  int32_t id = GetFrameID(frame);
905
  Debug(session, "beginning headers for stream %d", id);
906
907
21155
  Http2Stream* stream = session->FindStream(id);
908
  // The common case is that we're creating a new stream. The less likely
909
  // case is that we're receiving a set of trailers
910
21155
  if (LIKELY(stream == nullptr)) {
911

10615
    if (UNLIKELY(!session->CanAddStream() ||
912
                 Http2Stream::New(session, id, frame->headers.cat) ==
913
                     nullptr)) {
914
      // Too many concurrent streams being opened
915
      nghttp2_submit_rst_stream(**session, NGHTTP2_FLAG_NONE, id,
916
1
                                NGHTTP2_ENHANCE_YOUR_CALM);
917
1
      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
918
    }
919
10540
  } else if (!stream->IsDestroyed()) {
920
10540
    stream->StartHeaders(frame->headers.cat);
921
  }
922
21154
  return 0;
923
}
924
925
// Called by nghttp2 for each header name/value pair in a HEADERS block.
926
// This had to have been preceded by a call to OnBeginHeadersCallback so
927
// the Http2Stream is guaranteed to already exist.
928
63758
int Http2Session::OnHeaderCallback(nghttp2_session* handle,
929
                                   const nghttp2_frame* frame,
930
                                   nghttp2_rcbuf* name,
931
                                   nghttp2_rcbuf* value,
932
                                   uint8_t flags,
933
                                   void* user_data) {
934
63758
  Http2Session* session = static_cast<Http2Session*>(user_data);
935
63758
  int32_t id = GetFrameID(frame);
936
63758
  Http2Stream* stream = session->FindStream(id);
937
  // If stream is null at this point, either something odd has happened
938
  // or the stream was closed locally while header processing was occurring.
939
  // either way, do not proceed and close the stream.
940
63758
  if (UNLIKELY(stream == nullptr))
941
    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
942
943
  // If the stream has already been destroyed, ignore.
944

63758
  if (!stream->IsDestroyed() && !stream->AddHeader(name, value, flags)) {
945
    // This will only happen if the connected peer sends us more
946
    // than the allowed number of header items at any given time
947
2
    stream->SubmitRstStream(NGHTTP2_ENHANCE_YOUR_CALM);
948
2
    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
949
  }
950
63756
  return 0;
951
}
952
953
954
// Called by nghttp2 when a complete HTTP2 frame has been received. There are
955
// only a handful of frame types that we care about handling here.
956
77277
int Http2Session::OnFrameReceive(nghttp2_session* handle,
957
                                 const nghttp2_frame* frame,
958
                                 void* user_data) {
959
77277
  Http2Session* session = static_cast<Http2Session*>(user_data);
960
77277
  session->statistics_.frame_count++;
961
  Debug(session, "complete frame received: type: %d",
962
77277
        frame->hd.type);
963


77277
  switch (frame->hd.type) {
964
    case NGHTTP2_DATA:
965
20664
      session->HandleDataFrame(frame);
966
20664
      break;
967
    case NGHTTP2_PUSH_PROMISE:
968
      // Intentional fall-through, handled just like headers frames
969
    case NGHTTP2_HEADERS:
970
21118
      session->HandleHeadersFrame(frame);
971
21118
      break;
972
    case NGHTTP2_SETTINGS:
973
18255
      session->HandleSettingsFrame(frame);
974
18255
      break;
975
    case NGHTTP2_PRIORITY:
976
16
      session->HandlePriorityFrame(frame);
977
16
      break;
978
    case NGHTTP2_GOAWAY:
979
260
      session->HandleGoawayFrame(frame);
980
260
      break;
981
    case NGHTTP2_PING:
982
13874
      session->HandlePingFrame(frame);
983
13874
      break;
984
    case NGHTTP2_ALTSVC:
985
4
      session->HandleAltSvcFrame(frame);
986
4
      break;
987
    case NGHTTP2_ORIGIN:
988
5
      session->HandleOriginFrame(frame);
989
5
      break;
990
    default:
991
3081
      break;
992
  }
993
77277
  return 0;
994
}
995
996
38
int Http2Session::OnInvalidFrame(nghttp2_session* handle,
997
                                 const nghttp2_frame* frame,
998
                                 int lib_error_code,
999
                                 void* user_data) {
1000
38
  Http2Session* session = static_cast<Http2Session*>(user_data);
1001
1002
  Debug(session, "invalid frame received, code: %d", lib_error_code);
1003
1004
  // If the error is fatal or if error code is ERR_STREAM_CLOSED... emit error
1005

76
  if (nghttp2_is_fatal(lib_error_code) ||
1006
38
      lib_error_code == NGHTTP2_ERR_STREAM_CLOSED) {
1007
1
    Environment* env = session->env();
1008
1
    Isolate* isolate = env->isolate();
1009
1
    HandleScope scope(isolate);
1010
1
    Local<Context> context = env->context();
1011
    Context::Scope context_scope(context);
1012
1
    Local<Value> arg = Integer::New(isolate, lib_error_code);
1013
2
    session->MakeCallback(env->http2session_on_error_function(), 1, &arg);
1014
  }
1015
38
  return 0;
1016
}
1017
1018
// If nghttp2 is unable to send a queued up frame, it will call this callback
1019
// to let us know. If the failure occurred because we are in the process of
1020
// closing down the session or stream, we go ahead and ignore it. We don't
1021
// really care about those and there's nothing we can reasonably do about it
1022
// anyway. Other types of failures are reported up to JavaScript. This should
1023
// be exceedingly rare.
1024
61
int Http2Session::OnFrameNotSent(nghttp2_session* handle,
1025
                                 const nghttp2_frame* frame,
1026
                                 int error_code,
1027
                                 void* user_data) {
1028
61
  Http2Session* session = static_cast<Http2Session*>(user_data);
1029
61
  Environment* env = session->env();
1030
  Debug(session, "frame type %d was not sent, code: %d",
1031
61
        frame->hd.type, error_code);
1032
1033
  // Do not report if the frame was not sent due to the session closing
1034

66
  if (error_code == NGHTTP2_ERR_SESSION_CLOSING ||
1035
9
      error_code == NGHTTP2_ERR_STREAM_CLOSED ||
1036
4
      error_code == NGHTTP2_ERR_STREAM_CLOSING) {
1037
59
    return 0;
1038
  }
1039
1040
2
  Isolate* isolate = env->isolate();
1041
2
  HandleScope scope(isolate);
1042
2
  Local<Context> context = env->context();
1043
  Context::Scope context_scope(context);
1044
1045
  Local<Value> argv[3] = {
1046
    Integer::New(isolate, frame->hd.stream_id),
1047
    Integer::New(isolate, frame->hd.type),
1048
    Integer::New(isolate, error_code)
1049
8
  };
1050
  session->MakeCallback(
1051
      env->http2session_on_frame_error_function(),
1052
2
      arraysize(argv), argv);
1053
4
  return 0;
1054
}
1055
1056
58132
int Http2Session::OnFrameSent(nghttp2_session* handle,
1057
                              const nghttp2_frame* frame,
1058
                              void* user_data) {
1059
58132
  Http2Session* session = static_cast<Http2Session*>(user_data);
1060
58132
  session->statistics_.frame_sent += 1;
1061
58132
  return 0;
1062
}
1063
1064
// Called by nghttp2 when a stream closes.
1065
21207
int Http2Session::OnStreamClose(nghttp2_session* handle,
1066
                                int32_t id,
1067
                                uint32_t code,
1068
                                void* user_data) {
1069
21207
  Http2Session* session = static_cast<Http2Session*>(user_data);
1070
21207
  Environment* env = session->env();
1071
21207
  Isolate* isolate = env->isolate();
1072
21207
  HandleScope scope(isolate);
1073
21207
  Local<Context> context = env->context();
1074
  Context::Scope context_scope(context);
1075
  Debug(session, "stream %d closed with code: %d", id, code);
1076
21207
  Http2Stream* stream = session->FindStream(id);
1077
  // Intentionally ignore the callback if the stream does not exist or has
1078
  // already been destroyed
1079

21207
  if (stream == nullptr || stream->IsDestroyed())
1080
48
    return 0;
1081
1082
21159
  stream->Close(code);
1083
1084
  // It is possible for the stream close to occur before the stream is
1085
  // ever passed on to the javascript side. If that happens, the callback
1086
  // will return false.
1087
21159
  Local<Value> arg = Integer::NewFromUnsigned(isolate, code);
1088
  MaybeLocal<Value> answer =
1089
    stream->MakeCallback(env->http2session_on_stream_close_function(),
1090
21159
                          1, &arg);
1091

105795
  if (answer.IsEmpty() ||
1092
105795
      !(answer.ToLocalChecked()->BooleanValue(env->isolate()))) {
1093
    // Skip to destroy
1094
36
    stream->Destroy();
1095
  }
1096
42366
  return 0;
1097
}
1098
1099
// Called by nghttp2 when an invalid header has been received. For now, we
1100
// ignore these. If this callback was not provided, nghttp2 would handle
1101
// invalid headers strictly and would shut down the stream. We are intentionally
1102
// being more lenient here although we may want to revisit this choice later.
1103
4
int Http2Session::OnInvalidHeader(nghttp2_session* session,
1104
                                  const nghttp2_frame* frame,
1105
                                  nghttp2_rcbuf* name,
1106
                                  nghttp2_rcbuf* value,
1107
                                  uint8_t flags,
1108
                                  void* user_data) {
1109
  // Ignore invalid header fields by default.
1110
4
  return 0;
1111
}
1112
1113
// When nghttp2 receives a DATA frame, it will deliver the data payload to
1114
// us in discrete chunks. We push these into a linked list stored in the
1115
// Http2Sttream which is flushed out to JavaScript as quickly as possible.
1116
// This can be a particularly hot path.
1117
10218
int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
1118
                                      uint8_t flags,
1119
                                      int32_t id,
1120
                                      const uint8_t* data,
1121
                                      size_t len,
1122
                                      void* user_data) {
1123
10218
  Http2Session* session = static_cast<Http2Session*>(user_data);
1124
  Debug(session, "buffering data chunk for stream %d, size: "
1125
        "%d, flags: %d", id, len, flags);
1126
10218
  Environment* env = session->env();
1127
10218
  HandleScope scope(env->isolate());
1128
1129
  // We should never actually get a 0-length chunk so this check is
1130
  // only a precaution at this point.
1131
10218
  if (len == 0)
1132
    return 0;
1133
1134
  // Notify nghttp2 that we've consumed a chunk of data on the connection
1135
  // so that it can send a WINDOW_UPDATE frame. This is a critical part of
1136
  // the flow control process in http2
1137
10218
  CHECK_EQ(nghttp2_session_consume_connection(handle, len), 0);
1138
10218
  Http2Stream* stream = session->FindStream(id);
1139
  // If the stream has been destroyed, ignore this chunk
1140
10218
  if (stream->IsDestroyed())
1141
    return 0;
1142
1143
10218
  stream->statistics_.received_bytes += len;
1144
1145
  // Repeatedly ask the stream's owner for memory, and copy the read data
1146
  // into those buffers.
1147
  // The typical case is actually the exception here; Http2StreamListeners
1148
  // know about the HTTP2 session associated with this stream, so they know
1149
  // about the larger from-socket read buffer, so they do not require copying.
1150
10218
  do {
1151
10218
    uv_buf_t buf = stream->EmitAlloc(len);
1152
10218
    ssize_t avail = len;
1153
10218
    if (static_cast<ssize_t>(buf.len) < avail)
1154
      avail = buf.len;
1155
1156
    // `buf.base == nullptr` is the default Http2StreamListener's way
1157
    // of saying that it wants a pointer to the raw original.
1158
    // Since it has access to the original socket buffer from which the data
1159
    // was read in the first place, it can use that to minimize ArrayBuffer
1160
    // allocations.
1161
10218
    if (LIKELY(buf.base == nullptr))
1162
10218
      buf.base = reinterpret_cast<char*>(const_cast<uint8_t*>(data));
1163
    else
1164
      memcpy(buf.base, data, avail);
1165
10218
    data += avail;
1166
10218
    len -= avail;
1167
10218
    stream->EmitRead(avail, buf);
1168
1169
    // If the stream owner (e.g. the JS Http2Stream) wants more data, just
1170
    // tell nghttp2 that all data has been consumed. Otherwise, defer until
1171
    // more data is being requested.
1172
10218
    if (stream->IsReading())
1173
10131
      nghttp2_session_consume_stream(handle, id, avail);
1174
    else
1175
87
      stream->inbound_consumed_data_while_paused_ += avail;
1176
10218
  } while (len != 0);
1177
1178
10218
  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
8
ssize_t Http2Session::OnSelectPadding(nghttp2_session* handle,
1184
                                      const nghttp2_frame* frame,
1185
                                      size_t maxPayloadLen,
1186
                                      void* user_data) {
1187
8
  Http2Session* session = static_cast<Http2Session*>(user_data);
1188
8
  ssize_t padding = frame->hd.length;
1189
1190

8
  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
4
      padding = session->OnDWordAlignedPadding(padding, maxPayloadLen);
1199
4
      break;
1200
    case PADDING_STRATEGY_CALLBACK:
1201
4
      padding = session->OnCallbackPadding(padding, maxPayloadLen);
1202
4
      break;
1203
  }
1204
8
  return padding;
1205
}
1206
1207
#define BAD_PEER_MESSAGE "Remote peer returned unexpected data while we "     \
1208
                         "expected SETTINGS frame.  Perhaps, peer does not "  \
1209
                         "support HTTP/2 properly."
1210
1211
// We use this currently to determine when an attempt is made to use the http2
1212
// protocol with a non-http2 peer.
1213
35
int Http2Session::OnNghttpError(nghttp2_session* handle,
1214
                                const char* message,
1215
                                size_t len,
1216
                                void* user_data) {
1217
  // Unfortunately, this is currently the only way for us to know if
1218
  // the session errored because the peer is not an http2 peer.
1219
35
  Http2Session* session = static_cast<Http2Session*>(user_data);
1220
  Debug(session, "Error '%.*s'", len, message);
1221
35
  if (strncmp(message, BAD_PEER_MESSAGE, len) == 0) {
1222
1
    Environment* env = session->env();
1223
1
    Isolate* isolate = env->isolate();
1224
1
    HandleScope scope(isolate);
1225
1
    Local<Context> context = env->context();
1226
    Context::Scope context_scope(context);
1227
1
    Local<Value> arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1228
2
    session->MakeCallback(env->http2session_on_error_function(), 1, &arg);
1229
  }
1230
35
  return 0;
1231
}
1232
1233
10218
uv_buf_t Http2StreamListener::OnStreamAlloc(size_t size) {
1234
  // See the comments in Http2Session::OnDataChunkReceived
1235
  // (which is the only possible call site for this method).
1236
10218
  return uv_buf_init(nullptr, size);
1237
}
1238
1239
20710
void Http2StreamListener::OnStreamRead(ssize_t nread, const uv_buf_t& buf) {
1240
20710
  Http2Stream* stream = static_cast<Http2Stream*>(stream_);
1241
20710
  Http2Session* session = stream->session();
1242
20710
  Environment* env = stream->env();
1243
20710
  HandleScope handle_scope(env->isolate());
1244
30928
  Context::Scope context_scope(env->context());
1245
1246
20710
  if (nread < 0) {
1247
10492
    PassReadErrorToPreviousListener(nread);
1248
31202
    return;
1249
  }
1250
1251
20436
  CHECK(!session->stream_buf_ab_.IsEmpty());
1252
1253
  // There is a single large array buffer for the entire data read from the
1254
  // network; create a slice of that array buffer and emit it as the
1255
  // received data buffer.
1256
10218
  size_t offset = buf.base - session->stream_buf_.base;
1257
1258
  // Verify that the data offset is inside the current read buffer.
1259
10218
  CHECK_LE(offset, session->stream_buf_.len);
1260
10218
  CHECK_LE(offset + buf.len, session->stream_buf_.len);
1261
1262
20436
  stream->CallJSOnreadMethod(nread, session->stream_buf_ab_, offset);
1263
}
1264
1265
1266
// Called by OnFrameReceived to notify JavaScript land that a complete
1267
// HEADERS frame has been received and processed. This method converts the
1268
// received headers into a JavaScript array and pushes those out to JS.
1269
21118
void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
1270
21118
  Isolate* isolate = env()->isolate();
1271
21118
  HandleScope scope(isolate);
1272
21118
  Local<Context> context = env()->context();
1273
21118
  Context::Scope context_scope(context);
1274
1275
21118
  int32_t id = GetFrameID(frame);
1276
21118
  Debug(this, "handle headers frame for stream %d", id);
1277
21118
  Http2Stream* stream = FindStream(id);
1278
1279
  // If the stream has already been destroyed, ignore.
1280
21118
  if (stream->IsDestroyed())
1281
21118
    return;
1282
1283
42236
  std::vector<nghttp2_header> headers(stream->move_headers());
1284
1285
  Local<String> name_str;
1286
  Local<String> value_str;
1287
1288
  // The headers are passed in above as a queue of nghttp2_header structs.
1289
  // The following converts that into a JS array with the structure:
1290
  // [name1, value1, name2, value2, name3, value3, name3, value4] and so on.
1291
  // That array is passed up to the JS layer and converted into an Object form
1292
  // like {name1: value1, name2: value2, name3: [value3, value4]}. We do it
1293
  // this way for performance reasons (it's faster to generate and pass an
1294
  // array than it is to generate and pass the object).
1295
21118
  size_t headers_size = headers.size();
1296
42236
  std::vector<Local<Value>> headers_v(headers_size * 2);
1297
84765
  for (size_t i = 0; i < headers_size; ++i) {
1298
63647
    const nghttp2_header& item = headers[i];
1299
    // The header name and value are passed as external one-byte strings
1300
63647
    headers_v[i * 2] =
1301
127294
        ExternalHeader::New<true>(this, item.name).ToLocalChecked();
1302
63647
    headers_v[i * 2 + 1] =
1303
127294
        ExternalHeader::New<false>(this, item.value).ToLocalChecked();
1304
  }
1305
1306
  Local<Value> args[5] = {
1307
      stream->object(),
1308
      Integer::New(isolate, id),
1309
21118
      Integer::New(isolate, stream->headers_category()),
1310
      Integer::New(isolate, frame->hd.flags),
1311
147826
      Array::New(isolate, headers_v.data(), headers_size * 2)};
1312
  MakeCallback(env()->http2session_on_headers_function(),
1313
42236
               arraysize(args), args);
1314
}
1315
1316
1317
// Called by OnFrameReceived when a complete PRIORITY frame has been
1318
// received. Notifies JS land about the priority change. Note that priorities
1319
// are considered advisory only, so this has no real effect other than to
1320
// simply let user code know that the priority has changed.
1321
16
void Http2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
1322
16
  Isolate* isolate = env()->isolate();
1323
16
  HandleScope scope(isolate);
1324
16
  Local<Context> context = env()->context();
1325
  Context::Scope context_scope(context);
1326
1327
16
  nghttp2_priority priority_frame = frame->priority;
1328
16
  int32_t id = GetFrameID(frame);
1329
16
  Debug(this, "handle priority frame for stream %d", id);
1330
  // Priority frame stream ID should never be <= 0. nghttp2 handles this for us
1331
16
  nghttp2_priority_spec spec = priority_frame.pri_spec;
1332
1333
  Local<Value> argv[4] = {
1334
    Integer::New(isolate, id),
1335
    Integer::New(isolate, spec.stream_id),
1336
    Integer::New(isolate, spec.weight),
1337
    Boolean::New(isolate, spec.exclusive)
1338
80
  };
1339
  MakeCallback(env()->http2session_on_priority_function(),
1340
32
               arraysize(argv), argv);
1341
16
}
1342
1343
1344
// Called by OnFrameReceived when a complete DATA frame has been received.
1345
// If we know that this was the last DATA frame (because the END_STREAM flag
1346
// is set), then we'll terminate the readable side of the StreamBase.
1347
20664
void Http2Session::HandleDataFrame(const nghttp2_frame* frame) {
1348
20664
  int32_t id = GetFrameID(frame);
1349
20664
  Debug(this, "handling data frame for stream %d", id);
1350
20664
  Http2Stream* stream = FindStream(id);
1351
1352

20664
  if (!stream->IsDestroyed() && frame->hd.flags & NGHTTP2_FLAG_END_STREAM)
1353
10492
    stream->EmitRead(UV_EOF);
1354
20664
}
1355
1356
1357
// Called by OnFrameReceived when a complete GOAWAY frame has been received.
1358
260
void Http2Session::HandleGoawayFrame(const nghttp2_frame* frame) {
1359
260
  Isolate* isolate = env()->isolate();
1360
260
  HandleScope scope(isolate);
1361
260
  Local<Context> context = env()->context();
1362
  Context::Scope context_scope(context);
1363
1364
260
  nghttp2_goaway goaway_frame = frame->goaway;
1365
260
  Debug(this, "handling goaway frame");
1366
1367
  Local<Value> argv[3] = {
1368
    Integer::NewFromUnsigned(isolate, goaway_frame.error_code),
1369
    Integer::New(isolate, goaway_frame.last_stream_id),
1370
    Undefined(isolate)
1371
1040
  };
1372
1373
260
  size_t length = goaway_frame.opaque_data_len;
1374
260
  if (length > 0) {
1375
    argv[2] = Buffer::Copy(isolate,
1376
                           reinterpret_cast<char*>(goaway_frame.opaque_data),
1377
4
                           length).ToLocalChecked();
1378
  }
1379
1380
  MakeCallback(env()->http2session_on_goaway_data_function(),
1381
520
               arraysize(argv), argv);
1382
260
}
1383
1384
// Called by OnFrameReceived when a complete ALTSVC frame has been received.
1385
4
void Http2Session::HandleAltSvcFrame(const nghttp2_frame* frame) {
1386
4
  Isolate* isolate = env()->isolate();
1387
4
  HandleScope scope(isolate);
1388
4
  Local<Context> context = env()->context();
1389
  Context::Scope context_scope(context);
1390
1391
4
  int32_t id = GetFrameID(frame);
1392
1393
4
  nghttp2_extension ext = frame->ext;
1394
4
  nghttp2_ext_altsvc* altsvc = static_cast<nghttp2_ext_altsvc*>(ext.payload);
1395
4
  Debug(this, "handling altsvc frame");
1396
1397
  Local<Value> argv[3] = {
1398
    Integer::New(isolate, id),
1399
    String::NewFromOneByte(isolate,
1400
                           altsvc->origin,
1401
                           NewStringType::kNormal,
1402
4
                           altsvc->origin_len).ToLocalChecked(),
1403
    String::NewFromOneByte(isolate,
1404
                           altsvc->field_value,
1405
                           NewStringType::kNormal,
1406
4
                           altsvc->field_value_len).ToLocalChecked(),
1407
24
  };
1408
1409
  MakeCallback(env()->http2session_on_altsvc_function(),
1410
8
               arraysize(argv), argv);
1411
4
}
1412
1413
5
void Http2Session::HandleOriginFrame(const nghttp2_frame* frame) {
1414
5
  Isolate* isolate = env()->isolate();
1415
5
  HandleScope scope(isolate);
1416
5
  Local<Context> context = env()->context();
1417
  Context::Scope context_scope(context);
1418
1419
5
  Debug(this, "handling origin frame");
1420
1421
5
  nghttp2_extension ext = frame->ext;
1422
5
  nghttp2_ext_origin* origin = static_cast<nghttp2_ext_origin*>(ext.payload);
1423
1424
5
  size_t nov = origin->nov;
1425
10
  std::vector<Local<Value>> origin_v(nov);
1426
1427
14
  for (size_t i = 0; i < nov; ++i) {
1428
9
    const nghttp2_origin_entry& entry = origin->ov[i];
1429
9
    origin_v[i] =
1430
        String::NewFromOneByte(
1431
9
            isolate, entry.origin, NewStringType::kNormal, entry.origin_len)
1432
18
            .ToLocalChecked();
1433
  }
1434
5
  Local<Value> holder = Array::New(isolate, origin_v.data(), origin_v.size());
1435
10
  MakeCallback(env()->http2session_on_origin_function(), 1, &holder);
1436
5
}
1437
1438
// Called by OnFrameReceived when a complete PING frame has been received.
1439
13874
void Http2Session::HandlePingFrame(const nghttp2_frame* frame) {
1440
13874
  Isolate* isolate = env()->isolate();
1441
13874
  HandleScope scope(isolate);
1442
13874
  Local<Context> context = env()->context();
1443
13863
  Context::Scope context_scope(context);
1444
  Local<Value> arg;
1445
13874
  bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
1446
13874
  if (ack) {
1447
11
    Http2Ping* ping = PopPing();
1448
1449
11
    if (ping == nullptr) {
1450
      // PING Ack is unsolicited. Treat as a connection error. The HTTP/2
1451
      // spec does not require this, but there is no legitimate reason to
1452
      // receive an unsolicited PING ack on a connection. Either the peer
1453
      // is buggy or malicious, and we're not going to tolerate such
1454
      // nonsense.
1455
2
      arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1456
1
      MakeCallback(env()->http2session_on_error_function(), 1, &arg);
1457
1
      return;
1458
    }
1459
1460
10
    ping->Done(true, frame->ping.opaque_data);
1461
10
    return;
1462
  }
1463
1464
  // Notify the session that a ping occurred
1465
  arg = Buffer::Copy(env(),
1466
                      reinterpret_cast<const char*>(frame->ping.opaque_data),
1467
27726
                      8).ToLocalChecked();
1468
27726
  MakeCallback(env()->http2session_on_ping_function(), 1, &arg);
1469
}
1470
1471
// Called by OnFrameReceived when a complete SETTINGS frame has been received.
1472
18255
void Http2Session::HandleSettingsFrame(const nghttp2_frame* frame) {
1473
18255
  bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
1474
18255
  if (!ack) {
1475
    // This is not a SETTINGS acknowledgement, notify and return
1476
17789
    MakeCallback(env()->http2session_on_settings_function(), 0, nullptr);
1477
17789
    return;
1478
  }
1479
1480
  // If this is an acknowledgement, we should have an Http2Settings
1481
  // object for it.
1482
466
  Http2Settings* settings = PopSettings();
1483
466
  if (settings != nullptr) {
1484
466
    settings->Done(true);
1485
466
    return;
1486
  }
1487
  // SETTINGS Ack is unsolicited. Treat as a connection error. The HTTP/2
1488
  // spec does not require this, but there is no legitimate reason to
1489
  // receive an unsolicited SETTINGS ack on a connection. Either the peer
1490
  // is buggy or malicious, and we're not going to tolerate such
1491
  // nonsense.
1492
  // Note that nghttp2 currently prevents this from happening for SETTINGS
1493
  // frames, so this block is purely defensive just in case that behavior
1494
  // changes. Specifically, unlike unsolicited PING acks, unsolicited
1495
  // SETTINGS acks should *never* make it this far.
1496
  Isolate* isolate = env()->isolate();
1497
  HandleScope scope(isolate);
1498
  Local<Context> context = env()->context();
1499
  Context::Scope context_scope(context);
1500
  Local<Value> arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1501
  MakeCallback(env()->http2session_on_error_function(), 1, &arg);
1502
}
1503
1504
// Callback used when data has been written to the stream.
1505
638
void Http2Session::OnStreamAfterWrite(WriteWrap* w, int status) {
1506
638
  Debug(this, "write finished with status %d", status);
1507
1508
  // Inform all pending writes about their completion.
1509
638
  ClearOutgoing(status);
1510
1511
638
  if (!(flags_ & SESSION_STATE_WRITE_SCHEDULED)) {
1512
    // Schedule a new write if nghttp2 wants to send data.
1513
375
    MaybeScheduleWrite();
1514
  }
1515
638
}
1516
1517
// If the underlying nghttp2_session struct has data pending in its outbound
1518
// queue, MaybeScheduleWrite will schedule a SendPendingData() call to occur
1519
// on the next iteration of the Node.js event loop (using the SetImmediate
1520
// queue), but only if a write has not already been scheduled.
1521
28952
void Http2Session::MaybeScheduleWrite() {
1522
28952
  CHECK_EQ(flags_ & SESSION_STATE_WRITE_SCHEDULED, 0);
1523
28952
  if (UNLIKELY(session_ == nullptr))
1524
28952
    return;
1525
1526
28952
  if (nghttp2_session_want_write(session_)) {
1527
1463
    HandleScope handle_scope(env()->isolate());
1528
1463
    Debug(this, "scheduling write");
1529
1463
    flags_ |= SESSION_STATE_WRITE_SCHEDULED;
1530
4389
    env()->SetImmediate([](Environment* env, void* data) {
1531
1463
      Http2Session* session = static_cast<Http2Session*>(data);
1532

2926
      if (session->session_ == nullptr ||
1533
1463
          !(session->flags_ & SESSION_STATE_WRITE_SCHEDULED)) {
1534
        // This can happen e.g. when a stream was reset before this turn
1535
        // of the event loop, in which case SendPendingData() is called early,
1536
        // or the session was destroyed in the meantime.
1537
1728
        return;
1538
      }
1539
1540
      // Sending data may call arbitrary JS code, so keep track of
1541
      // async context.
1542
1198
      HandleScope handle_scope(env->isolate());
1543
2396
      InternalCallbackScope callback_scope(session);
1544
1198
      session->SendPendingData();
1545
5587
    }, static_cast<void*>(this), object());
1546
  }
1547
}
1548
1549
55443
void Http2Session::MaybeStopReading() {
1550
55443
  int want_read = nghttp2_session_want_read(session_);
1551
55443
  Debug(this, "wants read? %d", want_read);
1552
55443
  if (want_read == 0)
1553
973
    stream_->ReadStop();
1554
55443
}
1555
1556
// Unset the sending state, finish up all current writes, and reset
1557
// storage for data and metadata that was associated with these writes.
1558
28512
void Http2Session::ClearOutgoing(int status) {
1559
28512
  CHECK_NE(flags_ & SESSION_STATE_SENDING, 0);
1560
1561
28512
  flags_ &= ~SESSION_STATE_SENDING;
1562
1563
28512
  if (outgoing_buffers_.size() > 0) {
1564
27725
    outgoing_storage_.clear();
1565
1566
27725
    std::vector<nghttp2_stream_write> current_outgoing_buffers_;
1567
27725
    current_outgoing_buffers_.swap(outgoing_buffers_);
1568
113535
    for (const nghttp2_stream_write& wr : current_outgoing_buffers_) {
1569
85810
      WriteWrap* wrap = wr.req_wrap;
1570
85810
      if (wrap != nullptr) {
1571
        // TODO(addaleax): Pass `status` instead of 0, so that we actually error
1572
        // out with the error from the write to the underlying protocol,
1573
        // if one occurred.
1574
2563
        wrap->Done(0);
1575
      }
1576
27725
    }
1577
  }
1578
1579
  // Now that we've finished sending queued data, if there are any pending
1580
  // RstStreams we should try sending again and then flush them one by one.
1581
28512
  if (pending_rst_streams_.size() > 0) {
1582
4
    std::vector<int32_t> current_pending_rst_streams;
1583
4
    pending_rst_streams_.swap(current_pending_rst_streams);
1584
1585
4
    SendPendingData();
1586
1587
8
    for (int32_t stream_id : current_pending_rst_streams) {
1588
4
      Http2Stream* stream = FindStream(stream_id);
1589
4
      if (LIKELY(stream != nullptr))
1590
4
        stream->FlushRstStream();
1591
4
    }
1592
  }
1593
28512
}
1594
1595
// Queue a given block of data for sending. This always creates a copy,
1596
// so it is used for the cases in which nghttp2 requests sending of a
1597
// small chunk of data.
1598
58407
void Http2Session::CopyDataIntoOutgoing(const uint8_t* src, size_t src_length) {
1599
58407
  size_t offset = outgoing_storage_.size();
1600
58407
  outgoing_storage_.resize(offset + src_length);
1601
58407
  memcpy(&outgoing_storage_[offset], src, src_length);
1602
1603
  // Store with a base of `nullptr` initially, since future resizes
1604
  // of the outgoing_buffers_ vector may invalidate the pointer.
1605
  // The correct base pointers will be set later, before writing to the
1606
  // underlying socket.
1607
  outgoing_buffers_.emplace_back(nghttp2_stream_write {
1608
    uv_buf_init(nullptr, src_length)
1609
58407
  });
1610
58407
}
1611
1612
// Prompts nghttp2 to begin serializing it's pending data and pushes each
1613
// chunk out to the i/o socket to be sent. This is a particularly hot method
1614
// that will generally be called at least twice be event loop iteration.
1615
// This is a potential performance optimization target later.
1616
// Returns non-zero value if a write is already in progress.
1617
29084
uint8_t Http2Session::SendPendingData() {
1618
29084
  Debug(this, "sending pending data");
1619
  // Do not attempt to send data on the socket if the destroying flag has
1620
  // been set. That means everything is shutting down and the socket
1621
  // will not be usable.
1622
29084
  if (IsDestroyed())
1623
32
    return 0;
1624
29052
  flags_ &= ~SESSION_STATE_WRITE_SCHEDULED;
1625
1626
  // SendPendingData should not be called recursively.
1627
29052
  if (flags_ & SESSION_STATE_SENDING)
1628
538
    return 1;
1629
  // This is cleared by ClearOutgoing().
1630
28514
  flags_ |= SESSION_STATE_SENDING;
1631
1632
  ssize_t src_length;
1633
  const uint8_t* src;
1634
1635
28514
  CHECK_EQ(outgoing_buffers_.size(), 0);
1636
28514
  CHECK_EQ(outgoing_storage_.size(), 0);
1637
1638
  // Part One: Gather data from nghttp2
1639
1640
105239
  while ((src_length = nghttp2_session_mem_send(session_, &src)) > 0) {
1641
48211
    Debug(this, "nghttp2 has %d bytes to send", src_length);
1642
48211
    CopyDataIntoOutgoing(src, src_length);
1643
  }
1644
1645
28514
  CHECK_NE(src_length, NGHTTP2_ERR_NOMEM);
1646
1647
28514
  if (stream_ == nullptr) {
1648
    // It would seem nice to bail out earlier, but `nghttp2_session_mem_send()`
1649
    // does take care of things like closing the individual streams after
1650
    // a socket has been torn down, so we still need to call it.
1651
12
    ClearOutgoing(UV_ECANCELED);
1652
12
    return 0;
1653
  }
1654
1655
  // Part Two: Pass Data to the underlying stream
1656
1657
28502
  size_t count = outgoing_buffers_.size();
1658
28502
  if (count == 0) {
1659
787
    ClearOutgoing(0);
1660
787
    return 0;
1661
  }
1662
27715
  MaybeStackBuffer<uv_buf_t, 32> bufs;
1663
27715
  bufs.AllocateSufficientStorage(count);
1664
1665
  // Set the buffer base pointers for copied data that ended up in the
1666
  // sessions's own storage since it might have shifted around during gathering.
1667
  // (Those are marked by having .base == nullptr.)
1668
27715
  size_t offset = 0;
1669
27715
  size_t i = 0;
1670
113509
  for (const nghttp2_stream_write& write : outgoing_buffers_) {
1671
85794
    statistics_.data_sent += write.buf.len;
1672
85794
    if (write.buf.base == nullptr) {
1673
58388
      bufs[i++] = uv_buf_init(
1674
58388
          reinterpret_cast<char*>(outgoing_storage_.data() + offset),
1675
116776
          write.buf.len);
1676
58388
      offset += write.buf.len;
1677
    } else {
1678
27406
      bufs[i++] = write.buf;
1679
    }
1680
  }
1681
1682
27715
  chunks_sent_since_last_write_++;
1683
1684
27715
  StreamWriteResult res = underlying_stream()->Write(*bufs, count);
1685
27715
  if (!res.async) {
1686
27075
    ClearOutgoing(res.err);
1687
  }
1688
1689
27715
  MaybeStopReading();
1690
1691
27715
  return 0;
1692
}
1693
1694
1695
// This callback is called from nghttp2 when it wants to send DATA frames for a
1696
// given Http2Stream, when we set the `NGHTTP2_DATA_FLAG_NO_COPY` flag earlier
1697
// in the Http2Stream::Provider::Stream::OnRead callback.
1698
// We take the write information directly out of the stream's data queue.
1699
10194
int Http2Session::OnSendData(
1700
      nghttp2_session* session_,
1701
      nghttp2_frame* frame,
1702
      const uint8_t* framehd,
1703
      size_t length,
1704
      nghttp2_data_source* source,
1705
      void* user_data) {
1706
10194
  Http2Session* session = static_cast<Http2Session*>(user_data);
1707
10194
  Http2Stream* stream = GetStream(session, frame->hd.stream_id, source);
1708
1709
  // Send the frame header + a byte that indicates padding length.
1710
10194
  session->CopyDataIntoOutgoing(framehd, 9);
1711
10194
  if (frame->data.padlen > 0) {
1712
2
    uint8_t padding_byte = frame->data.padlen - 1;
1713
2
    CHECK_EQ(padding_byte, frame->data.padlen - 1);
1714
2
    session->CopyDataIntoOutgoing(&padding_byte, 1);
1715
  }
1716
1717
  Debug(session, "nghttp2 has %d bytes to send directly", length);
1718
40161
  while (length > 0) {
1719
    // nghttp2 thinks that there is data available (length > 0), which means
1720
    // we told it so, which means that we *should* have data available.
1721
27404
    CHECK(!stream->queue_.empty());
1722
1723
27404
    nghttp2_stream_write& write = stream->queue_.front();
1724
27404
    if (write.buf.len <= length) {
1725
      // This write does not suffice by itself, so we can consume it completely.
1726
19773
      length -= write.buf.len;
1727
19773
      session->outgoing_buffers_.emplace_back(std::move(write));
1728
19773
      stream->queue_.pop();
1729
19773
      continue;
1730
    }
1731
1732
    // Slice off `length` bytes of the first write in the queue.
1733
    session->outgoing_buffers_.emplace_back(nghttp2_stream_write {
1734
      uv_buf_init(write.buf.base, length)
1735
7631
    });
1736
7631
    write.buf.base += length;
1737
7631
    write.buf.len -= length;
1738
7631
    break;
1739
  }
1740
1741
10194
  if (frame->data.padlen > 0) {
1742
    // Send padding if that was requested.
1743
    session->outgoing_buffers_.emplace_back(nghttp2_stream_write {
1744
      uv_buf_init(const_cast<char*>(zero_bytes_256), frame->data.padlen - 1)
1745
2
    });
1746
  }
1747
1748
10194
  return 0;
1749
}
1750
1751
// Creates a new Http2Stream and submits a new http2 request.
1752
10609
Http2Stream* Http2Session::SubmitRequest(
1753
    nghttp2_priority_spec* prispec,
1754
    nghttp2_nv* nva,
1755
    size_t len,
1756
    int32_t* ret,
1757
    int options) {
1758
10609
  Debug(this, "submitting request");
1759
10609
  Http2Scope h2scope(this);
1760
10609
  Http2Stream* stream = nullptr;
1761
21218
  Http2Stream::Provider::Stream prov(options);
1762
10609
  *ret = nghttp2_submit_request(session_, prispec, nva, len, *prov, nullptr);
1763
10609
  CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
1764
10609
  if (LIKELY(*ret > 0))
1765
10608
    stream = Http2Stream::New(this, *ret, NGHTTP2_HCAT_HEADERS, options);
1766
21218
  return stream;
1767
}
1768
1769
27747
uv_buf_t Http2Session::OnStreamAlloc(size_t suggested_size) {
1770
27747
  return env()->AllocateManaged(suggested_size).release();
1771
}
1772
1773
// Callback used to receive inbound data from the i/o stream
1774
27754
void Http2Session::OnStreamRead(ssize_t nread, const uv_buf_t& buf_) {
1775
27754
  HandleScope handle_scope(env()->isolate());
1776
55482
  Context::Scope context_scope(env()->context());
1777
55482
  Http2Scope h2scope(this);
1778
27754
  CHECK_NOT_NULL(stream_);
1779
27754
  Debug(this, "receiving %d bytes", nread);
1780
55508
  CHECK(stream_buf_ab_.IsEmpty());
1781
55482
  AllocatedBuffer buf(env(), buf_);
1782
1783
  // Only pass data on if nread > 0
1784
27754
  if (nread <= 0) {
1785
22
    if (nread < 0) {
1786
22
      PassReadErrorToPreviousListener(nread);
1787
    }
1788
22
    return;
1789
  }
1790
1791
  // Shrink to the actual amount of used data.
1792
27732
  buf.Resize(nread);
1793
1794
27732
  IncrementCurrentSessionMemory(buf.size());
1795
27732
  OnScopeLeave on_scope_leave([&]() {
1796
    // Once finished handling this write, reset the stream buffer.
1797
    // The memory has either been free()d or was handed over to V8.
1798
27732
    DecrementCurrentSessionMemory(buf.size());
1799
55464
    stream_buf_ab_ = Local<ArrayBuffer>();
1800
27732
    stream_buf_ = uv_buf_init(nullptr, 0);
1801
83192
  });
1802
1803
  // Make sure that there was no read previously active.
1804
27732
  CHECK_NULL(stream_buf_.base);
1805
27732
  CHECK_EQ(stream_buf_.len, 0);
1806
1807
  // Remember the current buffer, so that OnDataChunkReceived knows the
1808
  // offset of a DATA frame's data into the socket read buffer.
1809
27732
  stream_buf_ = uv_buf_init(buf.data(), nread);
1810
1811
27732
  Isolate* isolate = env()->isolate();
1812
1813
  // Create an array buffer for the read data. DATA frames will be emitted
1814
  // as slices of this array buffer to avoid having to copy memory.
1815
27732
  stream_buf_ab_ = buf.ToArrayBuffer();
1816
1817
27732
  statistics_.data_received += nread;
1818
27732
  ssize_t ret = Write(&stream_buf_, 1);
1819
1820
27732
  if (UNLIKELY(ret < 0)) {
1821
4
    Debug(this, "fatal error receiving data: %d", ret);
1822
4
    Local<Value> arg = Integer::New(isolate, ret);
1823
4
    MakeCallback(env()->http2session_on_error_function(), 1, &arg);
1824
4
    return;
1825
  }
1826
1827
55456
  MaybeStopReading();
1828
}
1829
1830
21224
bool Http2Session::HasWritesOnSocketForStream(Http2Stream* stream) {
1831
24542
  for (const nghttp2_stream_write& wr : outgoing_buffers_) {
1832


3320
    if (wr.req_wrap != nullptr && wr.req_wrap->stream() == stream)
1833
2
      return true;
1834
  }
1835
21222
  return false;
1836
}
1837
1838
// Every Http2Session session is tightly bound to a single i/o StreamBase
1839
// (typically a net.Socket or tls.TLSSocket). The lifecycle of the two is
1840
// tightly coupled with all data transfer between the two happening at the
1841
// C++ layer via the StreamBase API.
1842
555
void Http2Session::Consume(Local<External> external) {
1843
555
  StreamBase* stream = static_cast<StreamBase*>(external->Value());
1844
555
  stream->PushStreamListener(this);
1845
555
  Debug(this, "i/o stream consumed");
1846
555
}
1847
1848
21230
Http2Stream* Http2Stream::New(Http2Session* session,
1849
                              int32_t id,
1850
                              nghttp2_headers_category category,
1851
                              int options) {
1852
  Local<Object> obj;
1853
42460
  if (!session->env()
1854
21230
           ->http2stream_constructor_template()
1855
84920
           ->NewInstance(session->env()->context())
1856
63690
           .ToLocal(&obj)) {
1857
    return nullptr;
1858
  }
1859
21230
  return new Http2Stream(session, obj, id, category, options);
1860
}
1861
1862
21230
Http2Stream::Http2Stream(Http2Session* session,
1863
                         Local<Object> obj,
1864
                         int32_t id,
1865
                         nghttp2_headers_category category,
1866
                         int options)
1867
    : AsyncWrap(session->env(), obj, AsyncWrap::PROVIDER_HTTP2STREAM),
1868
      StreamBase(session->env()),
1869
      session_(session),
1870
      id_(id),
1871
21230
      current_headers_category_(category) {
1872
21230
  MakeWeak();
1873
21230
  statistics_.start_time = uv_hrtime();
1874
1875
  // Limit the number of header pairs
1876
21230
  max_header_pairs_ = session->GetMaxHeaderPairs();
1877
21230
  if (max_header_pairs_ == 0)
1878
  max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
1879
21230
  current_headers_.reserve(max_header_pairs_);
1880
1881
  // Limit the number of header octets
1882
  max_header_length_ =
1883
      std::min(
1884
        nghttp2_session_get_local_settings(
1885
          session->session(),
1886
21230
          NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE),
1887
42460
      MAX_MAX_HEADER_LIST_SIZE);
1888
1889
21230
  if (options & STREAM_OPTION_GET_TRAILERS)
1890
2
    flags_ |= NGHTTP2_STREAM_FLAG_TRAILERS;
1891
1892
21230
  PushStreamListener(&stream_listener_);
1893
1894
21230
  if (options & STREAM_OPTION_EMPTY_PAYLOAD)
1895
439
    Shutdown();
1896
21230
  session->AddStream(this);
1897
21230
}
1898
1899
1900


63690
Http2Stream::~Http2Stream() {
1901
21339
  for (nghttp2_header& header : current_headers_) {
1902
109
    nghttp2_rcbuf_decref(header.name);
1903
109
    nghttp2_rcbuf_decref(header.value);
1904
  }
1905
1906
21230
  if (session_ == nullptr)
1907
    return;
1908
21230
  Debug(this, "tearing down stream");
1909
21230
  session_->RemoveStream(this);
1910
21230
  session_ = nullptr;
1911
42460
}
1912
1913
17
std::string Http2Stream::diagnostic_name() const {
1914
34
  return "HttpStream " + std::to_string(id()) + " (" +
1915
68
      std::to_string(static_cast<int64_t>(get_async_id())) + ") [" +
1916
51
      session()->diagnostic_name() + "]";
1917
}
1918
1919
// Notify the Http2Stream that a new block of HEADERS is being processed.
1920
10540
void Http2Stream::StartHeaders(nghttp2_headers_category category) {
1921
10540
  Debug(this, "starting headers, category: %d", id_, category);
1922
10540
  CHECK(!this->IsDestroyed());
1923
10540
  current_headers_length_ = 0;
1924
10540
  current_headers_.clear();
1925
10540
  current_headers_category_ = category;
1926
10540
}
1927
1928
1929
11
nghttp2_stream* Http2Stream::operator*() {
1930
11
  return nghttp2_session_find_stream(**session_, id_);
1931
}
1932
1933
21159
void Http2Stream::Close(int32_t code) {
1934
21159
  CHECK(!this->IsDestroyed());
1935
21159
  flags_ |= NGHTTP2_STREAM_FLAG_CLOSED;
1936
21159
  code_ = code;
1937
21159
  Debug(this, "closed with code %d", code);
1938
21159
}
1939
1940
21580
int Http2Stream::DoShutdown(ShutdownWrap* req_wrap) {
1941
21580
  if (IsDestroyed())
1942
    return UV_EPIPE;
1943
1944
  {
1945
21580
    Http2Scope h2scope(this);
1946
21580
    flags_ |= NGHTTP2_STREAM_FLAG_SHUT;
1947
21580
    CHECK_NE(nghttp2_session_resume_data(**session_, id_),
1948
             NGHTTP2_ERR_NOMEM);
1949
43160
    Debug(this, "writable side shutdown");
1950
  }
1951
21580
  return 1;
1952
}
1953
1954
// Destroy the Http2Stream and render it unusable. Actual resources for the
1955
// Stream will not be freed until the next tick of the Node.js event loop
1956
// using the SetImmediate queue.
1957
21224
void Http2Stream::Destroy() {
1958
  // Do nothing if this stream instance is already destroyed
1959
21224
  if (IsDestroyed())
1960
21224
    return;
1961
21224
  if (session_->HasPendingRstStream(id_))
1962
3
    FlushRstStream();
1963
21224
  flags_ |= NGHTTP2_STREAM_FLAG_DESTROYED;
1964
1965
21224
  Debug(this, "destroying stream");
1966
1967
  // Wait until the start of the next loop to delete because there
1968
  // may still be some pending operations queued for this stream.
1969
63672
  env()->SetImmediate([](Environment* env, void* data) {
1970
21224
    Http2Stream* stream = static_cast<Http2Stream*>(data);
1971
    // Free any remaining outgoing data chunks here. This should be done
1972
    // here because it's possible for destroy to have been called while
1973
    // we still have queued outbound writes.
1974
42452
    while (!stream->queue_.empty()) {
1975
4
      nghttp2_stream_write& head = stream->queue_.front();
1976
4
      if (head.req_wrap != nullptr)
1977
4
        head.req_wrap->Done(UV_ECANCELED);
1978
4
      stream->queue_.pop();
1979
    }
1980
1981
    // We can destroy the stream now if there are no writes for it
1982
    // already on the socket. Otherwise, we'll wait for the garbage collector
1983
    // to take care of cleaning up.
1984

42448
    if (stream->session() == nullptr ||
1985
21224
        !stream->session()->HasWritesOnSocketForStream(stream))
1986
21222
      delete stream;
1987
84896
  }, this, this->object());
1988
1989
21224
  statistics_.end_time = uv_hrtime();
1990
  session_->statistics_.stream_average_duration =
1991
21224
      ((statistics_.end_time - statistics_.start_time) /
1992
42448
          session_->statistics_.stream_count) / 1e6;
1993
21224
  EmitStatistics();
1994
}
1995
1996
1997
// Initiates a response on the Http2Stream using data provided via the
1998
// StreamBase Streams API.
1999
10539
int Http2Stream::SubmitResponse(nghttp2_nv* nva, size_t len, int options) {
2000
10539
  CHECK(!this->IsDestroyed());
2001
10539
  Http2Scope h2scope(this);
2002
10539
  Debug(this, "submitting response");
2003
10539
  if (options & STREAM_OPTION_GET_TRAILERS)
2004
183
    flags_ |= NGHTTP2_STREAM_FLAG_TRAILERS;
2005
2006
10539
  if (!IsWritable())
2007
8
    options |= STREAM_OPTION_EMPTY_PAYLOAD;
2008
2009
21078
  Http2Stream::Provider::Stream prov(this, options);
2010
10539
  int ret = nghttp2_submit_response(**session_, id_, nva, len, *prov);
2011
10539
  CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2012
21078
  return ret;
2013
}
2014
2015
2016
// Submit informational headers for a stream.
2017
5
int Http2Stream::SubmitInfo(nghttp2_nv* nva, size_t len) {
2018
5
  CHECK(!this->IsDestroyed());
2019
5
  Http2Scope h2scope(this);
2020
5
  Debug(this, "sending %d informational headers", len);
2021
  int ret = nghttp2_submit_headers(**session_,
2022
                                   NGHTTP2_FLAG_NONE,
2023
                                   id_, nullptr,
2024
5
                                   nva, len, nullptr);
2025
5
  CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2026
5
  return ret;
2027
}
2028
2029
31
void Http2Stream::OnTrailers() {
2030
31
  Debug(this, "let javascript know we are ready for trailers");
2031
31
  CHECK(!this->IsDestroyed());
2032
31
  Isolate* isolate = env()->isolate();
2033
31
  HandleScope scope(isolate);
2034
31
  Local<Context> context = env()->context();
2035
  Context::Scope context_scope(context);
2036
31
  flags_ &= ~NGHTTP2_STREAM_FLAG_TRAILERS;
2037
62
  MakeCallback(env()->http2session_on_stream_trailers_function(), 0, nullptr);
2038
31
}
2039
2040
// Submit informational headers for a stream.
2041
29
int Http2Stream::SubmitTrailers(nghttp2_nv* nva, size_t len) {
2042
29
  CHECK(!this->IsDestroyed());
2043
29
  Http2Scope h2scope(this);
2044
29
  Debug(this, "sending %d trailers", len);
2045
  int ret;
2046
  // Sending an empty trailers frame poses problems in Safari, Edge & IE.
2047
  // Instead we can just send an empty data frame with NGHTTP2_FLAG_END_STREAM
2048
  // to indicate that the stream is ready to be closed.
2049
29
  if (len == 0) {
2050
24
    Http2Stream::Provider::Stream prov(this, 0);
2051
24
    ret = nghttp2_submit_data(**session_, NGHTTP2_FLAG_END_STREAM, id_, *prov);
2052
  } else {
2053
5
    ret = nghttp2_submit_trailer(**session_, id_, nva, len);
2054
  }
2055
29
  CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2056
29
  return ret;
2057
}
2058
2059
// Submit a PRIORITY frame to the connected peer.
2060
6
int Http2Stream::SubmitPriority(nghttp2_priority_spec* prispec,
2061
                                bool silent) {
2062
6
  CHECK(!this->IsDestroyed());
2063
6
  Http2Scope h2scope(this);
2064
6
  Debug(this, "sending priority spec");
2065
  int ret = silent ?
2066
      nghttp2_session_change_stream_priority(**session_,
2067
                                             id_, prispec) :
2068
      nghttp2_submit_priority(**session_,
2069
                              NGHTTP2_FLAG_NONE,
2070
6
                              id_, prispec);
2071
6
  CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2072
6
  return ret;
2073
}
2074
2075
// Closes the Http2Stream by submitting an RST_STREAM frame to the connected
2076
// peer.
2077
120
void Http2Stream::SubmitRstStream(const uint32_t code) {
2078
120
  CHECK(!this->IsDestroyed());
2079
120
  code_ = code;
2080
  // If possible, force a purge of any currently pending data here to make sure
2081
  // it is sent before closing the stream. If it returns non-zero then we need
2082
  // to wait until the current write finishes and try again to avoid nghttp2
2083
  // behaviour where it prioritizes RstStream over everything else.
2084
120
  if (session_->SendPendingData() != 0) {
2085
4
    session_->AddPendingRstStream(id_);
2086
124
    return;
2087
  }
2088
2089
116
  FlushRstStream();
2090
}
2091
2092
123
void Http2Stream::FlushRstStream() {
2093
123
  if (IsDestroyed())
2094
148
    return;
2095
98
  Http2Scope h2scope(this);
2096
98
  CHECK_EQ(nghttp2_submit_rst_stream(**session_, NGHTTP2_FLAG_NONE,
2097
98
                                     id_, code_), 0);
2098
}
2099
2100
2101
// Submit a push promise and create the associated Http2Stream if successful.
2102
8
Http2Stream* Http2Stream::SubmitPushPromise(nghttp2_nv* nva,
2103
                                            size_t len,
2104
                                            int32_t* ret,
2105
                                            int options) {
2106
8
  CHECK(!this->IsDestroyed());
2107
8
  Http2Scope h2scope(this);
2108
8
  Debug(this, "sending push promise");
2109
  *ret = nghttp2_submit_push_promise(**session_, NGHTTP2_FLAG_NONE,
2110
8
                                     id_, nva, len, nullptr);
2111
8
  CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
2112
8
  Http2Stream* stream = nullptr;
2113
8
  if (*ret > 0)
2114
8
    stream = Http2Stream::New(session_, *ret, NGHTTP2_HCAT_HEADERS, options);
2115
2116
8
  return stream;
2117
}
2118
2119
// Switch the StreamBase into flowing mode to begin pushing chunks of data
2120
// out to JS land.
2121
20818
int Http2Stream::ReadStart() {
2122
20818
  Http2Scope h2scope(this);
2123
20818
  CHECK(!this->IsDestroyed());
2124
20818
  flags_ |= NGHTTP2_STREAM_FLAG_READ_START;
2125
20818
  flags_ &= ~NGHTTP2_STREAM_FLAG_READ_PAUSED;
2126
2127
20818
  Debug(this, "reading starting");
2128
2129
  // Tell nghttp2 about our consumption of the data that was handed
2130
  // off to JS land.
2131
  nghttp2_session_consume_stream(**session_,
2132
                                 id_,
2133
20818
                                 inbound_consumed_data_while_paused_);
2134
20818
  inbound_consumed_data_while_paused_ = 0;
2135
2136
20818
  return 0;
2137
}
2138
2139
// Switch the StreamBase into paused mode.
2140
111
int Http2Stream::ReadStop() {
2141
111
  CHECK(!this->IsDestroyed());
2142
111
  if (!IsReading())
2143
73
    return 0;
2144
38
  flags_ |= NGHTTP2_STREAM_FLAG_READ_PAUSED;
2145
38
  Debug(this, "reading stopped");
2146
38
  return 0;
2147
}
2148
2149
// The Http2Stream class is a subclass of StreamBase. The DoWrite method
2150
// receives outbound chunks of data to send as outbound DATA frames. These
2151
// are queued in an internal linked list of uv_buf_t structs that are sent
2152
// when nghttp2 is ready to serialize the data frame.
2153
//
2154
// Queue the given set of uv_but_t handles for writing to an
2155
// nghttp2_stream. The WriteWrap's Done callback will be invoked once the
2156
// chunks of data have been flushed to the underlying nghttp2_session.
2157
// Note that this does *not* mean that the data has been flushed
2158
// to the socket yet.
2159
2571
int Http2Stream::DoWrite(WriteWrap* req_wrap,
2160
                         uv_buf_t* bufs,
2161
                         size_t nbufs,
2162
                         uv_stream_t* send_handle) {
2163
2571
  CHECK_NULL(send_handle);
2164
2571
  Http2Scope h2scope(this);
2165

2571
  if (!IsWritable() || IsDestroyed()) {
2166
    req_wrap->Done(UV_EOF);
2167
    return 0;
2168
  }
2169
2571
  Debug(this, "queuing %d buffers to send", id_, nbufs);
2170
22354
  for (size_t i = 0; i < nbufs; ++i) {
2171
    // Store the req_wrap on the last write info in the queue, so that it is
2172
    // only marked as finished once all buffers associated with it are finished.
2173
    queue_.emplace(nghttp2_stream_write {
2174
19783
      i == nbufs - 1 ? req_wrap : nullptr,
2175
      bufs[i]
2176
19783
    });
2177
19783
    IncrementAvailableOutboundLength(bufs[i].len);
2178
  }
2179
2571
  CHECK_NE(nghttp2_session_resume_data(**session_, id_), NGHTTP2_ERR_NOMEM);
2180
2571
  return 0;
2181
}
2182
2183
// Ads a header to the Http2Stream. Note that the header name and value are
2184
// provided using a buffer structure provided by nghttp2 that allows us to
2185
// avoid unnecessary memcpy's. Those buffers are ref counted. The ref count
2186
// is incremented here and are decremented when the header name and values
2187
// are garbage collected later.
2188
63758
bool Http2Stream::AddHeader(nghttp2_rcbuf* name,
2189
                            nghttp2_rcbuf* value,
2190
                            uint8_t flags) {
2191
63758
  CHECK(!this->IsDestroyed());
2192
63758
  if (this->statistics_.first_header == 0)
2193
21136
    this->statistics_.first_header = uv_hrtime();
2194
63758
  size_t length = nghttp2_rcbuf_get_buf(name).len +
2195
63758
                  nghttp2_rcbuf_get_buf(value).len + 32;
2196
  // A header can only be added if we have not exceeded the maximum number
2197
  // of headers and the session has memory available for it.
2198

191274
  if (!session_->IsAvailableSessionMemory(length) ||
2199

127515
      current_headers_.size() == max_header_pairs_ ||
2200
63757
      current_headers_length_ + length > max_header_length_) {
2201
2
    return false;
2202
  }
2203
63756
  nghttp2_header header;
2204
63756
  header.name = name;
2205
63756
  header.value = value;
2206
63756
  header.flags = flags;
2207
63756
  current_headers_.push_back(header);
2208
63756
  nghttp2_rcbuf_incref(name);
2209
63756
  nghttp2_rcbuf_incref(value);
2210
63756
  current_headers_length_ += length;
2211
63756
  return true;
2212
}
2213
2214
// A Provider is the thing that provides outbound DATA frame data.
2215
10563
Http2Stream::Provider::Provider(Http2Stream* stream, int options) {
2216
10563
  CHECK(!stream->IsDestroyed());
2217
10563
  provider_.source.ptr = stream;
2218
10563
  empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
2219
10563
}
2220
2221
10609
Http2Stream::Provider::Provider(int options) {
2222
10609
  provider_.source.ptr = nullptr;
2223
10609
  empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
2224
10609
}
2225
2226
21172
Http2Stream::Provider::~Provider() {
2227
21172
  provider_.source.ptr = nullptr;
2228
21172
}
2229
2230
// The Stream Provider pulls data from a linked list of uv_buf_t structs
2231
// built via the StreamBase API and the Streams js API.
2232
10609
Http2Stream::Provider::Stream::Stream(int options)
2233
10609
    : Http2Stream::Provider(options) {
2234
10609
  provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
2235
10609
}
2236
2237
10563
Http2Stream::Provider::Stream::Stream(Http2Stream* stream, int options)
2238
10563
    : Http2Stream::Provider(stream, options) {
2239
10563
  provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
2240
10563
}
2241
2242
23333
ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
2243
                                              int32_t id,
2244
                                              uint8_t* buf,
2245
                                              size_t length,
2246
                                              uint32_t* flags,
2247
                                              nghttp2_data_source* source,
2248
                                              void* user_data) {
2249
23333
  Http2Session* session = static_cast<Http2Session*>(user_data);
2250
  Debug(session, "reading outbound data for stream %d", id);
2251
23333
  Http2Stream* stream = GetStream(session, id, source);
2252
23333
  if (stream->statistics_.first_byte_sent == 0)
2253
10550
    stream->statistics_.first_byte_sent = uv_hrtime();
2254
23333
  CHECK_EQ(id, stream->id());
2255
2256
23333
  size_t amount = 0;          // amount of data being sent in this data frame.
2257
2258
  // Remove all empty chunks from the head of the queue.
2259
  // This is done here so that .write('', cb) is still a meaningful way to
2260
  // find out when the HTTP2 stream wants to consume data, and because the
2261
  // StreamBase API allows empty input chunks.
2262

46670
  while (!stream->queue_.empty() && stream->queue_.front().buf.len == 0) {
2263
4
    WriteWrap* finished = stream->queue_.front().req_wrap;
2264
4
    stream->queue_.pop();
2265
4
    if (finished != nullptr)
2266
2
      finished->Done(0);
2267
  }
2268
2269
23333
  if (!stream->queue_.empty()) {
2270
    Debug(session, "stream %d has pending outbound data", id);
2271
10194
    amount = std::min(stream->available_outbound_length_, length);
2272
    Debug(session, "sending %d bytes for data frame on stream %d", amount, id);
2273
10194
    if (amount > 0) {
2274
      // Just return the length, let Http2Session::OnSendData take care of
2275
      // actually taking the buffers out of the queue.
2276
10194
      *flags |= NGHTTP2_DATA_FLAG_NO_COPY;
2277
10194
      stream->DecrementAvailableOutboundLength(amount);
2278
    }
2279
  }
2280
2281

23333
  if (amount == 0 && stream->IsWritable()) {
2282
2599
    CHECK(stream->queue_.empty());
2283
    Debug(session, "deferring stream %d", id);
2284
2599
    stream->EmitWantsWrite(length);
2285

2599
    if (stream->available_outbound_length_ > 0 || !stream->IsWritable()) {
2286
      // EmitWantsWrite() did something interesting synchronously, restart:
2287
      return OnRead(handle, id, buf, length, flags, source, user_data);
2288
    }
2289
2599
    return NGHTTP2_ERR_DEFERRED;
2290
  }
2291
2292

20734
  if (stream->queue_.empty() && !stream->IsWritable()) {
2293
    Debug(session, "no more data for stream %d", id);
2294
10540
    *flags |= NGHTTP2_DATA_FLAG_EOF;
2295
10540
    if (stream->HasTrailers()) {
2296
31
      *flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
2297
31
      stream->OnTrailers();
2298
    }
2299
  }
2300
2301
20734
  stream->statistics_.sent_bytes += amount;
2302
20734
  return amount;
2303
}
2304
2305
19783
inline void Http2Stream::IncrementAvailableOutboundLength(size_t amount) {
2306
19783
  available_outbound_length_ += amount;
2307
19783
  session_->IncrementCurrentSessionMemory(amount);
2308
19783
}
2309
2310
10194
inline void Http2Stream::DecrementAvailableOutboundLength(size_t amount) {
2311
10194
  available_outbound_length_ -= amount;
2312
10194
  session_->DecrementCurrentSessionMemory(amount);
2313
10194
}
2314
2315
2316
// Implementation of the JavaScript API
2317
2318
// Fetches the string description of a nghttp2 error code and passes that
2319
// back to JS land
2320
51
void HttpErrorString(const FunctionCallbackInfo<Value>& args) {
2321
51
  Environment* env = Environment::GetCurrent(args);
2322
204
  uint32_t val = args[0]->Uint32Value(env->context()).ToChecked();
2323
  args.GetReturnValue().Set(
2324
      String::NewFromOneByte(
2325
          env->isolate(),
2326
51
          reinterpret_cast<const uint8_t*>(nghttp2_strerror(val)),
2327
153
          NewStringType::kInternalized).ToLocalChecked());
2328
51
}
2329
2330
2331
// Serializes the settings object into a Buffer instance that
2332
// would be suitable, for instance, for creating the Base64
2333
// output for an HTTP2-Settings header field.
2334
15
void PackSettings(const FunctionCallbackInfo<Value>& args) {
2335
15
  Environment* env = Environment::GetCurrent(args);
2336
  // TODO(addaleax): We should not be creating a full AsyncWrap for this.
2337
  Local<Object> obj;
2338
30
  if (!env->http2settings_constructor_template()
2339
45
           ->NewInstance(env->context())
2340
45
           .ToLocal(&obj)) {
2341
15
    return;
2342
  }
2343
15
  Http2Session::Http2Settings settings(env, nullptr, obj);
2344
45
  args.GetReturnValue().Set(settings.Pack());
2345
}
2346
2347
// A TypedArray instance is shared between C++ and JS land to contain the
2348
// default SETTINGS. RefreshDefaultSettings updates that TypedArray with the
2349
// default values.
2350
3
void RefreshDefaultSettings(const FunctionCallbackInfo<Value>& args) {
2351
3
  Environment* env = Environment::GetCurrent(args);
2352
3
  Http2Session::Http2Settings::RefreshDefaults(env);
2353
3
}
2354
2355
// Sets the next stream ID the Http2Session. If successful, returns true.
2356
1
void Http2Session::SetNextStreamID(const FunctionCallbackInfo<Value>& args) {
2357
1
  Environment* env = Environment::GetCurrent(args);
2358
  Http2Session* session;
2359
1
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2360
4
  int32_t id = args[0]->Int32Value(env->context()).ToChecked();
2361
1
  if (nghttp2_session_set_next_stream_id(**session, id) < 0) {
2362
    Debug(session, "failed to set next stream id to %d", id);
2363
    return args.GetReturnValue().Set(false);
2364
  }
2365
2
  args.GetReturnValue().Set(true);
2366
1
  Debug(session, "set next stream id to %d", id);
2367
}
2368
2369
// A TypedArray instance is shared between C++ and JS land to contain the
2370
// SETTINGS (either remote or local). RefreshSettings updates the current
2371
// values established for each of the settings so those can be read in JS land.
2372
template <get_setting fn>
2373
18253
void Http2Session::RefreshSettings(const FunctionCallbackInfo<Value>& args) {
2374
18253
  Environment* env = Environment::GetCurrent(args);
2375
  Http2Session* session;
2376

36506
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2377
18253
  Http2Settings::Update(env, session, fn);
2378
18253
  Debug(session, "settings refreshed for session");
2379
}
2380
2381
// A TypedArray instance is shared between C++ and JS land to contain state
2382
// information of the current Http2Session. This updates the values in the
2383
// TypedArray so those can be read in JS land.
2384
3
void Http2Session::RefreshState(const FunctionCallbackInfo<Value>& args) {
2385
3
  Environment* env = Environment::GetCurrent(args);
2386
  Http2Session* session;
2387
6
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2388
3
  Debug(session, "refreshing state");
2389
2390
  AliasedBuffer<double, Float64Array>& buffer =
2391
3
      env->http2_state()->session_state_buffer;
2392
2393
3
  nghttp2_session* s = **session;
2394
2395
6
  buffer[IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE] =
2396
6
      nghttp2_session_get_effective_local_window_size(s);
2397
6
  buffer[IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH] =
2398
6
      nghttp2_session_get_effective_recv_data_length(s);
2399
6
  buffer[IDX_SESSION_STATE_NEXT_STREAM_ID] =
2400
6
      nghttp2_session_get_next_stream_id(s);
2401
6
  buffer[IDX_SESSION_STATE_LOCAL_WINDOW_SIZE] =
2402
6
      nghttp2_session_get_local_window_size(s);
2403
6
  buffer[IDX_SESSION_STATE_LAST_PROC_STREAM_ID] =
2404
6
      nghttp2_session_get_last_proc_stream_id(s);
2405
6
  buffer[IDX_SESSION_STATE_REMOTE_WINDOW_SIZE] =
2406
6
      nghttp2_session_get_remote_window_size(s);
2407
6
  buffer[IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE] =
2408
6
      nghttp2_session_get_outbound_queue_size(s);
2409
6
  buffer[IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE] =
2410
6
      nghttp2_session_get_hd_deflate_dynamic_table_size(s);
2411
6
  buffer[IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE] =
2412
6
      nghttp2_session_get_hd_inflate_dynamic_table_size(s);
2413
}
2414
2415
2416
// Constructor for new Http2Session instances.
2417
555
void Http2Session::New(const FunctionCallbackInfo<Value>& args) {
2418
555
  Environment* env = Environment::GetCurrent(args);
2419
555
  CHECK(args.IsConstructCall());
2420
2220
  int val = args[0]->IntegerValue(env->context()).ToChecked();
2421
555
  nghttp2_session_type type = static_cast<nghttp2_session_type>(val);
2422
555
  Http2Session* session = new Http2Session(env, args.This(), type);
2423
555
  session->get_async_id();  // avoid compiler warning
2424
  Debug(session, "session created");
2425
555
}
2426
2427
2428
// Binds the Http2Session with a StreamBase used for i/o
2429
555
void Http2Session::Consume(const FunctionCallbackInfo<Value>& args) {
2430
  Http2Session* session;
2431
1110
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2432
1110
  CHECK(args[0]->IsExternal());
2433
1110
  session->Consume(args[0].As<External>());
2434
}
2435
2436
// Destroys the Http2Session instance and renders it unusable
2437
531
void Http2Session::Destroy(const FunctionCallbackInfo<Value>& args) {
2438
  Http2Session* session;
2439
1062
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2440
531
  Debug(session, "destroying session");
2441
531
  Environment* env = Environment::GetCurrent(args);
2442
531
  Local<Context> context = env->context();
2443
2444
1593
  uint32_t code = args[0]->Uint32Value(context).ToChecked();
2445
1593
  bool socketDestroyed = args[1]->BooleanValue(env->isolate());
2446
2447
531
  session->Close(code, socketDestroyed);
2448
}
2449
2450
// Submits a new request on the Http2Session and returns either an error code
2451
// or the Http2Stream object.
2452
10609
void Http2Session::Request(const FunctionCallbackInfo<Value>& args) {
2453
  Http2Session* session;
2454
10610
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2455
10609
  Environment* env = session->env();
2456
10609
  Local<Context> context = env->context();
2457
10609
  Isolate* isolate = env->isolate();
2458
2459
21218
  Local<Array> headers = args[0].As<Array>();
2460
31827
  int options = args[1]->IntegerValue(context).ToChecked();
2461
10609
  Http2Priority priority(env, args[2], args[3], args[4]);
2462
2463
10609
  Headers list(isolate, context, headers);
2464
2465
10609
  Debug(session, "request submitted");
2466
2467
10609
  int32_t ret = 0;
2468
  Http2Stream* stream =
2469
      session->Http2Session::SubmitRequest(*priority, *list, list.length(),
2470
10609
                                           &ret, options);
2471
2472

10609
  if (ret <= 0 || stream == nullptr) {
2473
2
    Debug(session, "could not submit request: %s", nghttp2_strerror(ret));
2474
3
    return args.GetReturnValue().Set(ret);
2475
  }
2476
2477
21216
  Debug(session, "request submitted, new stream id %d", stream->id());
2478
31824
  args.GetReturnValue().Set(stream->object());
2479
}
2480
2481
// Submits a GOAWAY frame to signal that the Http2Session is in the process
2482
// of shutting down. Note that this function does not actually alter the
2483
// state of the Http2Session, it's simply a notification.
2484
485
void Http2Session::Goaway(uint32_t code,
2485
                          int32_t lastStreamID,
2486
                          uint8_t* data,
2487
                          size_t len) {
2488
485
  if (IsDestroyed())
2489
485
    return;
2490
2491
485
  Http2Scope h2scope(this);
2492
  // the last proc stream id is the most recently created Http2Stream.
2493
485
  if (lastStreamID <= 0)
2494
485
    lastStreamID = nghttp2_session_get_last_proc_stream_id(session_);
2495
485
  Debug(this, "submitting goaway");
2496
  nghttp2_submit_goaway(session_, NGHTTP2_FLAG_NONE,
2497
485
                        lastStreamID, code, data, len);
2498
}
2499
2500
// Submits a GOAWAY frame to signal that the Http2Session is in the process
2501
// of shutting down. The opaque data argument is an optional TypedArray that
2502
// can be used to send debugging data to the connected peer.
2503
485
void Http2Session::Goaway(const FunctionCallbackInfo<Value>& args) {
2504
485
  Environment* env = Environment::GetCurrent(args);
2505
485
  Local<Context> context = env->context();
2506
  Http2Session* session;
2507
970
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2508
2509
1455
  uint32_t code = args[0]->Uint32Value(context).ToChecked();
2510
1455
  int32_t lastStreamID = args[1]->Int32Value(context).ToChecked();
2511
485
  Local<Value> opaqueData = args[2];
2512
485
  uint8_t* data = nullptr;
2513
485
  size_t length = 0;
2514
2515
485
  if (Buffer::HasInstance(opaqueData)) {
2516
1
    data = reinterpret_cast<uint8_t*>(Buffer::Data(opaqueData));
2517
1
    length = Buffer::Length(opaqueData);
2518
  }
2519
2520
485
  session->Goaway(code, lastStreamID, data, length);
2521
}
2522
2523
// Update accounting of data chunks. This is used primarily to manage timeout
2524
// logic when using the FD Provider.
2525
10
void Http2Session::UpdateChunksSent(const FunctionCallbackInfo<Value>& args) {
2526
10
  Environment* env = Environment::GetCurrent(args);
2527
10
  Isolate* isolate = env->isolate();
2528
10
  HandleScope scope(isolate);
2529
  Http2Session* session;
2530
20
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2531
2532
10
  uint32_t length = session->chunks_sent_since_last_write_;
2533
2534
10
  session->object()->Set(env->context(),
2535
                         env->chunks_sent_since_last_write_string(),
2536
60
                         Integer::NewFromUnsigned(isolate, length)).FromJust();
2537
2538
20
  args.GetReturnValue().Set(length);
2539
}
2540
2541
// Submits an RST_STREAM frame effectively closing the Http2Stream. Note that
2542
// this *WILL* alter the state of the stream, causing the OnStreamClose
2543
// callback to the triggered.
2544
118
void Http2Stream::RstStream(const FunctionCallbackInfo<Value>& args) {
2545
118
  Environment* env = Environment::GetCurrent(args);
2546
118
  Local<Context> context = env->context();
2547
  Http2Stream* stream;
2548
236
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2549
354
  uint32_t code = args[0]->Uint32Value(context).ToChecked();
2550
118
  Debug(stream, "sending rst_stream with code %d", code);
2551
118
  stream->SubmitRstStream(code);
2552
}
2553
2554
// Initiates a response on the Http2Stream using the StreamBase API to provide
2555
// outbound DATA frames.
2556
10539
void Http2Stream::Respond(const FunctionCallbackInfo<Value>& args) {
2557
10539
  Environment* env = Environment::GetCurrent(args);
2558
10539
  Local<Context> context = env->context();
2559
10539
  Isolate* isolate = env->isolate();
2560
  Http2Stream* stream;
2561
21078
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2562
2563
21078
  Local<Array> headers = args[0].As<Array>();
2564
31617
  int options = args[1]->IntegerValue(context).ToChecked();
2565
2566
10539
  Headers list(isolate, context, headers);
2567
2568
  args.GetReturnValue().Set(
2569
31617
      stream->SubmitResponse(*list, list.length(), options));
2570
21078
  Debug(stream, "response submitted");
2571
}
2572
2573
2574
// Submits informational headers on the Http2Stream
2575
5
void Http2Stream::Info(const FunctionCallbackInfo<Value>& args) {
2576
5
  Environment* env = Environment::GetCurrent(args);
2577
5
  Local<Context> context = env->context();
2578
5
  Isolate* isolate = env->isolate();
2579
  Http2Stream* stream;
2580
10
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2581
2582
10
  Local<Array> headers = args[0].As<Array>();
2583
2584
5
  Headers list(isolate, context, headers);
2585
15
  args.GetReturnValue().Set(stream->SubmitInfo(*list, list.length()));
2586
10
  Debug(stream, "%d informational headers sent", list.length());
2587
}
2588
2589
// Submits trailing headers on the Http2Stream
2590
29
void Http2Stream::Trailers(const FunctionCallbackInfo<Value>& args) {
2591
29
  Environment* env = Environment::GetCurrent(args);
2592
29
  Local<Context> context = env->context();
2593
29
  Isolate* isolate = env->isolate();
2594
  Http2Stream* stream;
2595
58
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2596
2597
58
  Local<Array> headers = args[0].As<Array>();
2598
2599
29
  Headers list(isolate, context, headers);
2600
87
  args.GetReturnValue().Set(stream->SubmitTrailers(*list, list.length()));
2601
58
  Debug(stream, "%d trailing headers sent", list.length());
2602
}
2603
2604
// Grab the numeric id of the Http2Stream
2605
10616
void Http2Stream::GetID(const FunctionCallbackInfo<Value>& args) {
2606
  Http2Stream* stream;
2607
21232
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2608
31848
  args.GetReturnValue().Set(stream->id());
2609
}
2610
2611
// Destroy the Http2Stream, rendering it no longer usable
2612
21188
void Http2Stream::Destroy(const FunctionCallbackInfo<Value>& args) {
2613
  Http2Stream* stream;
2614
42376
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2615
21188
  Debug(stream, "destroying stream");
2616
21188
  stream->Destroy();
2617
}
2618
2619
// Prompt the Http2Stream to begin sending data to the JS land.
2620
void Http2Stream::FlushData(const FunctionCallbackInfo<Value>& args) {
2621
  Http2Stream* stream;
2622
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2623
  stream->ReadStart();
2624
  Debug(stream, "data flushed to js");
2625
}
2626
2627
// Initiate a Push Promise and create the associated Http2Stream
2628
8
void Http2Stream::PushPromise(const FunctionCallbackInfo<Value>& args) {
2629
8
  Environment* env = Environment::GetCurrent(args);
2630
8
  Local<Context> context = env->context();
2631
8
  Isolate* isolate = env->isolate();
2632
  Http2Stream* parent;
2633
8
  ASSIGN_OR_RETURN_UNWRAP(&parent, args.Holder());
2634
2635
16
  Local<Array> headers = args[0].As<Array>();
2636
24
  int options = args[1]->IntegerValue(context).ToChecked();
2637
2638
8
  Headers list(isolate, context, headers);
2639
2640
8
  Debug(parent, "creating push promise");
2641
2642
8
  int32_t ret = 0;
2643
  Http2Stream* stream = parent->SubmitPushPromise(*list, list.length(),
2644
8
                                                  &ret, options);
2645

8
  if (ret <= 0 || stream == nullptr) {
2646
    Debug(parent, "failed to create push stream: %d", ret);
2647
    return args.GetReturnValue().Set(ret);
2648
  }
2649
16
  Debug(parent, "push stream %d created", stream->id());
2650
24
  args.GetReturnValue().Set(stream->object());
2651
}
2652
2653
// Send a PRIORITY frame
2654
6
void Http2Stream::Priority(const FunctionCallbackInfo<Value>& args) {
2655
6
  Environment* env = Environment::GetCurrent(args);
2656
  Http2Stream* stream;
2657
12
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2658
2659
6
  Http2Priority priority(env, args[0], args[1], args[2]);
2660
18
  bool silent = args[3]->BooleanValue(env->isolate());
2661
2662
6
  CHECK_EQ(stream->SubmitPriority(*priority, silent), 0);
2663
6
  Debug(stream, "priority submitted");
2664
}
2665
2666
// A TypedArray shared by C++ and JS land is used to communicate state
2667
// information about the Http2Stream. This updates the values in that
2668
// TypedArray so that the state can be read by JS.
2669
11
void Http2Stream::RefreshState(const FunctionCallbackInfo<Value>& args) {
2670
11
  Environment* env = Environment::GetCurrent(args);
2671
  Http2Stream* stream;
2672
22
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2673
2674
11
  Debug(stream, "refreshing state");
2675
2676
  AliasedBuffer<double, Float64Array>& buffer =
2677
11
      env->http2_state()->stream_state_buffer;
2678
2679
11
  nghttp2_stream* str = **stream;
2680
11
  nghttp2_session* s = **(stream->session());
2681
2682
11
  if (str == nullptr) {
2683
1
    buffer[IDX_STREAM_STATE] = NGHTTP2_STREAM_STATE_IDLE;
2684
2
    buffer[IDX_STREAM_STATE_WEIGHT] =
2685
2
        buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2686
2
        buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2687
2
        buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2688
3
        buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] = 0;
2689
  } else {
2690
20
    buffer[IDX_STREAM_STATE] =
2691
20
        nghttp2_stream_get_state(str);
2692
20
    buffer[IDX_STREAM_STATE_WEIGHT] =
2693
20
        nghttp2_stream_get_weight(str);
2694
20
    buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2695
20
        nghttp2_stream_get_sum_dependency_weight(str);
2696
20
    buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2697
20
        nghttp2_session_get_stream_local_close(s, stream->id());
2698
20
    buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2699
20
        nghttp2_session_get_stream_remote_close(s, stream->id());
2700
20
    buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] =
2701
20
        nghttp2_session_get_stream_local_window_size(s, stream->id());
2702
  }
2703
}
2704
2705
5
void Http2Session::AltSvc(int32_t id,
2706
                          uint8_t* origin,
2707
                          size_t origin_len,
2708
                          uint8_t* value,
2709
                          size_t value_len) {
2710
5
  Http2Scope h2scope(this);
2711
5
  CHECK_EQ(nghttp2_submit_altsvc(session_, NGHTTP2_FLAG_NONE, id,
2712
5
                                 origin, origin_len, value, value_len), 0);
2713
5
}
2714
2715
5
void Http2Session::Origin(nghttp2_origin_entry* ov, size_t count) {
2716
5
  Http2Scope h2scope(this);
2717
5
  CHECK_EQ(nghttp2_submit_origin(session_, NGHTTP2_FLAG_NONE, ov, count), 0);
2718
5
}
2719
2720
// Submits an AltSvc frame to be sent to the connected peer.
2721
5
void Http2Session::AltSvc(const FunctionCallbackInfo<Value>& args) {
2722
5
  Environment* env = Environment::GetCurrent(args);
2723
  Http2Session* session;
2724
10
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2725
2726
20
  int32_t id = args[0]->Int32Value(env->context()).ToChecked();
2727
2728
  // origin and value are both required to be ASCII, handle them as such.
2729
20
  Local<String> origin_str = args[1]->ToString(env->context()).ToLocalChecked();
2730
20
  Local<String> value_str = args[2]->ToString(env->context()).ToLocalChecked();
2731
2732
5
  size_t origin_len = origin_str->Length();
2733
5
  size_t value_len = value_str->Length();
2734
2735
5
  CHECK_LE(origin_len + value_len, 16382);  // Max permitted for ALTSVC
2736
  // Verify that origin len != 0 if stream id == 0, or
2737
  // that origin len == 0 if stream id != 0
2738


5
  CHECK((origin_len != 0 && id == 0) || (origin_len == 0 && id != 0));
2739
2740
5
  MaybeStackBuffer<uint8_t> origin(origin_len);
2741
10
  MaybeStackBuffer<uint8_t> value(value_len);
2742
10
  origin_str->WriteOneByte(env->isolate(), *origin);
2743
10
  value_str->WriteOneByte(env->isolate(), *value);
2744
2745
10
  session->AltSvc(id, *origin, origin_len, *value, value_len);
2746
}
2747
2748
5
void Http2Session::Origin(const FunctionCallbackInfo<Value>& args) {
2749
5
  Environment* env = Environment::GetCurrent(args);
2750
5
  Local<Context> context = env->context();
2751
  Http2Session* session;
2752
10
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2753
2754
10
  Local<String> origin_string = args[0].As<String>();
2755
15
  int count = args[1]->IntegerValue(context).ToChecked();
2756
2757
2758
  Origins origins(env->isolate(),
2759
                  env->context(),
2760
                  origin_string,
2761
5
                  count);
2762
2763
5
  session->Origin(*origins, origins.length());
2764
}
2765
2766
// Submits a PING frame to be sent to the connected peer.
2767
12
void Http2Session::Ping(const FunctionCallbackInfo<Value>& args) {
2768
12
  Environment* env = Environment::GetCurrent(args);
2769
  Http2Session* session;
2770
14
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2771
2772
  // A PING frame may have exactly 8 bytes of payload data. If not provided,
2773
  // then the current hrtime will be used as the payload.
2774
12
  uint8_t* payload = nullptr;
2775
12
  if (Buffer::HasInstance(args[0])) {
2776
6
    payload = reinterpret_cast<uint8_t*>(Buffer::Data(args[0]));
2777
6
    CHECK_EQ(Buffer::Length(args[0]), 8);
2778
  }
2779
2780
  Local<Object> obj;
2781
24
  if (!env->http2ping_constructor_template()
2782
36
           ->NewInstance(env->context())
2783
36
           .ToLocal(&obj)) {
2784
    return;
2785
  }
2786
48
  if (obj->Set(env->context(), env->ondone_string(), args[1]).IsNothing())
2787
    return;
2788
12
  Http2Session::Http2Ping* ping = new Http2Ping(session, obj);
2789
2790
  // To prevent abuse, we strictly limit the number of unacknowledged PING
2791
  // frames that may be sent at any given time. This is configurable in the
2792
  // Options when creating a Http2Session.
2793
12
  if (!session->AddPing(ping)) {
2794
2
    ping->Done(false);
2795
4
    return args.GetReturnValue().Set(false);
2796
  }
2797
2798
  // The Ping itself is an Async resource. When the acknowledgement is received,
2799
  // the callback will be invoked and a notification sent out to JS land. The
2800
  // notification will include the duration of the ping, allowing the round
2801
  // trip to be measured.
2802
10
  ping->Send(payload);
2803
20
  args.GetReturnValue().Set(true);
2804
}
2805
2806
// Submits a SETTINGS frame for the Http2Session
2807
563
void Http2Session::Settings(const FunctionCallbackInfo<Value>& args) {
2808
563
  Environment* env = Environment::GetCurrent(args);
2809
  Http2Session* session;
2810
565
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2811
2812
  Local<Object> obj;
2813
1126
  if (!env->http2settings_constructor_template()
2814
1689
           ->NewInstance(env->context())
2815
1689
           .ToLocal(&obj)) {
2816
    return;
2817
  }
2818
2252
  if (obj->Set(env->context(), env->ondone_string(), args[0]).IsNothing())
2819
    return;
2820
2821
  Http2Session::Http2Settings* settings =
2822
563
      new Http2Settings(session->env(), session, obj, 0);
2823
563
  if (!session->AddSettings(settings)) {
2824
2
    settings->Done(false);
2825
4
    return args.GetReturnValue().Set(false);
2826
  }
2827
2828
561
  settings->Send();
2829
1122
  args.GetReturnValue().Set(true);
2830
}
2831
2832
2833
11
Http2Session::Http2Ping* Http2Session::PopPing() {
2834
11
  Http2Ping* ping = nullptr;
2835
11
  if (!outstanding_pings_.empty()) {
2836
10
    ping = outstanding_pings_.front();
2837
10
    outstanding_pings_.pop();
2838
10
    DecrementCurrentSessionMemory(sizeof(*ping));
2839
  }
2840
11
  return ping;
2841
}
2842
2843
12
bool Http2Session::AddPing(Http2Session::Http2Ping* ping) {
2844
12
  if (outstanding_pings_.size() == max_outstanding_pings_)
2845
2
    return false;
2846
10
  outstanding_pings_.push(ping);
2847
10
  IncrementCurrentSessionMemory(sizeof(*ping));
2848
10
  return true;
2849
}
2850
2851
466
Http2Session::Http2Settings* Http2Session::PopSettings() {
2852
466
  Http2Settings* settings = nullptr;
2853
466
  if (!outstanding_settings_.empty()) {
2854
466
    settings = outstanding_settings_.front();
2855
466
    outstanding_settings_.pop();
2856
466
    DecrementCurrentSessionMemory(sizeof(*settings));
2857
  }
2858
466
  return settings;
2859
}
2860
2861
563
bool Http2Session::AddSettings(Http2Session::Http2Settings* settings) {
2862
563
  if (outstanding_settings_.size() == max_outstanding_settings_)
2863
2
    return false;
2864
561
  outstanding_settings_.push(settings);
2865
561
  IncrementCurrentSessionMemory(sizeof(*settings));
2866
561
  return true;
2867
}
2868
2869
12
Http2Session::Http2Ping::Http2Ping(Http2Session* session, Local<Object> obj)
2870
    : AsyncWrap(session->env(), obj, AsyncWrap::PROVIDER_HTTP2PING),
2871
      session_(session),
2872
12
      startTime_(uv_hrtime()) {}
2873
2874
10
void Http2Session::Http2Ping::Send(uint8_t* payload) {
2875
  uint8_t data[8];
2876
10
  if (payload == nullptr) {
2877
4
    memcpy(&data, &startTime_, arraysize(data));
2878
4
    payload = data;
2879
  }
2880
10
  Http2Scope h2scope(session_);
2881
10
  CHECK_EQ(nghttp2_submit_ping(**session_, NGHTTP2_FLAG_NONE, payload), 0);
2882
10
}
2883
2884
12
void Http2Session::Http2Ping::Done(bool ack, const uint8_t* payload) {
2885
12
  session_->statistics_.ping_rtt = uv_hrtime() - startTime_;
2886
12
  double duration = session_->statistics_.ping_rtt / 1e6;
2887
2888
12
  Local<Value> buf = Undefined(env()->isolate());
2889
12
  if (payload != nullptr) {
2890
    buf = Buffer::Copy(env()->isolate(),
2891
                       reinterpret_cast<const char*>(payload),
2892
20
                       8).ToLocalChecked();
2893
  }
2894
2895
  Local<Value> argv[3] = {
2896
    Boolean::New(env()->isolate(), ack),
2897
    Number::New(env()->isolate(), duration),
2898
    buf
2899
36
  };
2900
12
  MakeCallback(env()->ondone_string(), arraysize(argv), argv);
2901
12
  delete this;
2902
12
}
2903
2904
2905
void nghttp2_stream_write::MemoryInfo(MemoryTracker* tracker) const {
2906
  if (req_wrap != nullptr)
2907
    tracker->TrackField("req_wrap", req_wrap->GetAsyncWrap());
2908
  tracker->TrackField("buf", buf);
2909
}
2910
2911
2912
void nghttp2_header::MemoryInfo(MemoryTracker* tracker) const {
2913
  tracker->TrackFieldWithSize("name", nghttp2_rcbuf_get_buf(name).len);
2914
  tracker->TrackFieldWithSize("value", nghttp2_rcbuf_get_buf(value).len);
2915
}
2916
2917
214
void SetCallbackFunctions(const FunctionCallbackInfo<Value>& args) {
2918
214
  Environment* env = Environment::GetCurrent(args);
2919
214
  CHECK_EQ(args.Length(), 12);
2920
2921
#define SET_FUNCTION(arg, name)                                               \
2922
  CHECK(args[arg]->IsFunction());                                             \
2923
  env->set_http2session_on_ ## name ## _function(args[arg].As<Function>());
2924
2925
856
  SET_FUNCTION(0, error)
2926
856
  SET_FUNCTION(1, priority)
2927
856
  SET_FUNCTION(2, settings)
2928
856
  SET_FUNCTION(3, ping)
2929
856
  SET_FUNCTION(4, headers)
2930
856
  SET_FUNCTION(5, frame_error)
2931
856
  SET_FUNCTION(6, goaway_data)
2932
856
  SET_FUNCTION(7, altsvc)
2933
856
  SET_FUNCTION(8, origin)
2934
856
  SET_FUNCTION(9, select_padding)
2935
856
  SET_FUNCTION(10, stream_trailers)
2936
856
  SET_FUNCTION(11, stream_close)
2937
2938
#undef SET_FUNCTION
2939
214
}
2940
2941
// Set up the process.binding('http2') binding.
2942
220
void Initialize(Local<Object> target,
2943
                Local<Value> unused,
2944
                Local<Context> context,
2945
                void* priv) {
2946
220
  Environment* env = Environment::GetCurrent(context);
2947
220
  Isolate* isolate = env->isolate();
2948
220
  HandleScope scope(isolate);
2949
2950
440
  std::unique_ptr<Http2State> state(new Http2State(isolate));
2951
2952
#define SET_STATE_TYPEDARRAY(name, field)             \
2953
  target->Set(context,                                \
2954
              FIXED_ONE_BYTE_STRING(isolate, (name)), \
2955
              (field)).FromJust()
2956
2957
  // Initialize the buffer used for padding callbacks
2958
880
  SET_STATE_TYPEDARRAY(
2959
    "paddingBuffer", state->padding_buffer.GetJSArray());
2960
  // Initialize the buffer used to store the session state
2961
880
  SET_STATE_TYPEDARRAY(
2962
    "sessionState", state->session_state_buffer.GetJSArray());
2963
  // Initialize the buffer used to store the stream state
2964
880
  SET_STATE_TYPEDARRAY(
2965
    "streamState", state->stream_state_buffer.GetJSArray());
2966
880
  SET_STATE_TYPEDARRAY(
2967
    "settingsBuffer", state->settings_buffer.GetJSArray());
2968
880
  SET_STATE_TYPEDARRAY(
2969
    "optionsBuffer", state->options_buffer.GetJSArray());
2970
880
  SET_STATE_TYPEDARRAY(
2971
    "streamStats", state->stream_stats_buffer.GetJSArray());
2972
880
  SET_STATE_TYPEDARRAY(
2973
    "sessionStats", state->session_stats_buffer.GetJSArray());
2974
#undef SET_STATE_TYPEDARRAY
2975
2976
220
  env->set_http2_state(std::move(state));
2977
2978
880
  NODE_DEFINE_CONSTANT(target, PADDING_BUF_FRAME_LENGTH);
2979
880
  NODE_DEFINE_CONSTANT(target, PADDING_BUF_MAX_PAYLOAD_LENGTH);
2980
880
  NODE_DEFINE_CONSTANT(target, PADDING_BUF_RETURN_VALUE);
2981
2982
  // Method to fetch the nghttp2 string description of an nghttp2 error code
2983
220
  env->SetMethod(target, "nghttp2ErrorString", HttpErrorString);
2984
2985
  Local<String> http2SessionClassName =
2986
220
    FIXED_ONE_BYTE_STRING(isolate, "Http2Session");
2987
2988
220
  Local<FunctionTemplate> ping = FunctionTemplate::New(env->isolate());
2989
440
  ping->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Ping"));
2990
440
  ping->Inherit(AsyncWrap::GetConstructorTemplate(env));
2991
220
  Local<ObjectTemplate> pingt = ping->InstanceTemplate();
2992
220
  pingt->SetInternalFieldCount(1);
2993
220
  env->set_http2ping_constructor_template(pingt);
2994
2995
220
  Local<FunctionTemplate> setting = FunctionTemplate::New(env->isolate());
2996
440
  setting->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Setting"));
2997
440
  setting->Inherit(AsyncWrap::GetConstructorTemplate(env));
2998
220
  Local<ObjectTemplate> settingt = setting->InstanceTemplate();
2999
220
  settingt->SetInternalFieldCount(1);
3000
220
  env->set_http2settings_constructor_template(settingt);
3001
3002
220
  Local<FunctionTemplate> stream = FunctionTemplate::New(env->isolate());
3003
440
  stream->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Stream"));
3004
220
  env->SetProtoMethod(stream, "id", Http2Stream::GetID);
3005
220
  env->SetProtoMethod(stream, "destroy", Http2Stream::Destroy);
3006
220
  env->SetProtoMethod(stream, "flushData", Http2Stream::FlushData);
3007
220
  env->SetProtoMethod(stream, "priority", Http2Stream::Priority);
3008
220
  env->SetProtoMethod(stream, "pushPromise", Http2Stream::PushPromise);
3009
220
  env->SetProtoMethod(stream, "info", Http2Stream::Info);
3010
220
  env->SetProtoMethod(stream, "trailers", Http2Stream::Trailers);
3011
220
  env->SetProtoMethod(stream, "respond", Http2Stream::Respond);
3012
220
  env->SetProtoMethod(stream, "rstStream", Http2Stream::RstStream);
3013
220
  env->SetProtoMethod(stream, "refreshState", Http2Stream::RefreshState);
3014
440
  stream->Inherit(AsyncWrap::GetConstructorTemplate(env));
3015
220
  StreamBase::AddMethods<Http2Stream>(env, stream);
3016
220
  Local<ObjectTemplate> streamt = stream->InstanceTemplate();
3017
220
  streamt->SetInternalFieldCount(1);
3018
220
  env->set_http2stream_constructor_template(streamt);
3019
  target->Set(context,
3020
              FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Stream"),
3021
1100
              stream->GetFunction(env->context()).ToLocalChecked()).FromJust();
3022
3023
  Local<FunctionTemplate> session =
3024
220
      env->NewFunctionTemplate(Http2Session::New);
3025
220
  session->SetClassName(http2SessionClassName);
3026
440
  session->InstanceTemplate()->SetInternalFieldCount(1);
3027
440
  session->Inherit(AsyncWrap::GetConstructorTemplate(env));
3028
220
  env->SetProtoMethod(session, "origin", Http2Session::Origin);
3029
220
  env->SetProtoMethod(session, "altsvc", Http2Session::AltSvc);
3030
220
  env->SetProtoMethod(session, "ping", Http2Session::Ping);
3031
220
  env->SetProtoMethod(session, "consume", Http2Session::Consume);
3032
220
  env->SetProtoMethod(session, "destroy", Http2Session::Destroy);
3033
220
  env->SetProtoMethod(session, "goaway", Http2Session::Goaway);
3034
220
  env->SetProtoMethod(session, "settings", Http2Session::Settings);
3035
220
  env->SetProtoMethod(session, "request", Http2Session::Request);
3036
  env->SetProtoMethod(session, "setNextStreamID",
3037
220
                      Http2Session::SetNextStreamID);
3038
  env->SetProtoMethod(session, "updateChunksSent",
3039
220
                      Http2Session::UpdateChunksSent);
3040
220
  env->SetProtoMethod(session, "refreshState", Http2Session::RefreshState);
3041
  env->SetProtoMethod(
3042
      session, "localSettings",
3043
220
      Http2Session::RefreshSettings<nghttp2_session_get_local_settings>);
3044
  env->SetProtoMethod(
3045
      session, "remoteSettings",
3046
220
      Http2Session::RefreshSettings<nghttp2_session_get_remote_settings>);
3047
  target->Set(context,
3048
              http2SessionClassName,
3049
880
              session->GetFunction(env->context()).ToLocalChecked()).FromJust();
3050
3051
220
  Local<Object> constants = Object::New(isolate);
3052
220
  Local<Array> name_for_error_code = Array::New(isolate);
3053
3054
#define NODE_NGHTTP2_ERROR_CODES(V)                       \
3055
  V(NGHTTP2_SESSION_SERVER);                              \
3056
  V(NGHTTP2_SESSION_CLIENT);                              \
3057
  V(NGHTTP2_STREAM_STATE_IDLE);                           \
3058
  V(NGHTTP2_STREAM_STATE_OPEN);                           \
3059
  V(NGHTTP2_STREAM_STATE_RESERVED_LOCAL);                 \
3060
  V(NGHTTP2_STREAM_STATE_RESERVED_REMOTE);                \
3061
  V(NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL);              \
3062
  V(NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE);             \
3063
  V(NGHTTP2_STREAM_STATE_CLOSED);                         \
3064
  V(NGHTTP2_NO_ERROR);                                    \
3065
  V(NGHTTP2_PROTOCOL_ERROR);                              \
3066
  V(NGHTTP2_INTERNAL_ERROR);                              \
3067
  V(NGHTTP2_FLOW_CONTROL_ERROR);                          \
3068
  V(NGHTTP2_SETTINGS_TIMEOUT);                            \
3069
  V(NGHTTP2_STREAM_CLOSED);                               \
3070
  V(NGHTTP2_FRAME_SIZE_ERROR);                            \
3071
  V(NGHTTP2_REFUSED_STREAM);                              \
3072
  V(NGHTTP2_CANCEL);                                      \
3073
  V(NGHTTP2_COMPRESSION_ERROR);                           \
3074
  V(NGHTTP2_CONNECT_ERROR);                               \
3075
  V(NGHTTP2_ENHANCE_YOUR_CALM);                           \
3076
  V(NGHTTP2_INADEQUATE_SECURITY);                         \
3077
  V(NGHTTP2_HTTP_1_1_REQUIRED);                           \
3078
3079
#define V(name)                                                         \
3080
  NODE_DEFINE_CONSTANT(constants, name);                                \
3081
  name_for_error_code->Set(env->context(),                              \
3082
                           static_cast<int>(name),                      \
3083
                           FIXED_ONE_BYTE_STRING(isolate,               \
3084
                                                 #name)).FromJust();
3085
35420
  NODE_NGHTTP2_ERROR_CODES(V)
3086
#undef V
3087
3088
880
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_REQUEST);
3089
880
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_RESPONSE);
3090
880
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_PUSH_RESPONSE);
3091
880
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_HEADERS);
3092
880
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_NV_FLAG_NONE);
3093
880
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_NV_FLAG_NO_INDEX);
3094
880
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_DEFERRED);
3095
880
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE);
3096
880
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_INVALID_ARGUMENT);
3097
880
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_STREAM_CLOSED);
3098
880
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_ERR_FRAME_SIZE_ERROR);
3099
3100
880
  NODE_DEFINE_HIDDEN_CONSTANT(constants, STREAM_OPTION_EMPTY_PAYLOAD);
3101
880
  NODE_DEFINE_HIDDEN_CONSTANT(constants, STREAM_OPTION_GET_TRAILERS);
3102
3103
880
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_NONE);
3104
880
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_END_STREAM);
3105
880
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_END_HEADERS);
3106
880
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_ACK);
3107
880
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_PADDED);
3108
880
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_PRIORITY);
3109
3110
880
  NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_HEADER_TABLE_SIZE);
3111
880
  NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_ENABLE_PUSH);
3112
880
  NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE);
3113
880
  NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_MAX_FRAME_SIZE);
3114
880
  NODE_DEFINE_CONSTANT(constants, MAX_MAX_FRAME_SIZE);
3115
880
  NODE_DEFINE_CONSTANT(constants, MIN_MAX_FRAME_SIZE);
3116
880
  NODE_DEFINE_CONSTANT(constants, MAX_INITIAL_WINDOW_SIZE);
3117
880
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_DEFAULT_WEIGHT);
3118
3119
880
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE);
3120
880
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_ENABLE_PUSH);
3121
880
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
3122
880
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
3123
880
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_MAX_FRAME_SIZE);
3124
880
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE);
3125
880
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL);
3126
3127
880
  NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_NONE);
3128
880
  NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_ALIGNED);
3129
880
  NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_MAX);
3130
880
  NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_CALLBACK);
3131
3132
#define STRING_CONSTANT(NAME, VALUE)                                          \
3133
  NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_HEADER_" # NAME, VALUE);
3134
100320
HTTP_KNOWN_HEADERS(STRING_CONSTANT)
3135
#undef STRING_CONSTANT
3136
3137
#define STRING_CONSTANT(NAME, VALUE)                                          \
3138
  NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_METHOD_" # NAME, VALUE);
3139
51480
HTTP_KNOWN_METHODS(STRING_CONSTANT)
3140
#undef STRING_CONSTANT
3141
3142
#define V(name, _) NODE_DEFINE_CONSTANT(constants, HTTP_STATUS_##name);
3143
55440
HTTP_STATUS_CODES(V)
3144
#undef V
3145
3146
220
  env->SetMethod(target, "refreshDefaultSettings", RefreshDefaultSettings);
3147
220
  env->SetMethod(target, "packSettings", PackSettings);
3148
220
  env->SetMethod(target, "setCallbackFunctions", SetCallbackFunctions);
3149
3150
  target->Set(context,
3151
              env->constants_string(),
3152
660
              constants).FromJust();
3153
  target->Set(context,
3154
              FIXED_ONE_BYTE_STRING(isolate, "nameForErrorCode"),
3155
880
              name_for_error_code).FromJust();
3156
220
}
3157
}  // namespace http2
3158
}  // namespace node
3159
3160

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