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: 1548 1604 96.5 %
Date: 2019-07-27 22:37:30 Branches: 540 741 72.9 %

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

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

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

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

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

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

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

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

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

23176
  return streams_.size() < maxSize &&
790
23176
         IsAvailableSessionMemory(sizeof(Http2Stream));
791
}
792
793
23176
inline void Http2Session::AddStream(Http2Stream* stream) {
794
23176
  CHECK_GE(++statistics_.stream_count, 0);
795
23176
  streams_[stream->id()] = stream;
796
23176
  size_t size = streams_.size();
797
23176
  if (size > statistics_.max_concurrent_streams)
798
1039
    statistics_.max_concurrent_streams = size;
799
23176
  IncrementCurrentSessionMemory(sizeof(*stream));
800
23176
}
801
802
803
23174
inline void Http2Session::RemoveStream(Http2Stream* stream) {
804

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

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

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


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

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

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

23149
  if (stream == nullptr || stream->IsDestroyed())
1088
48
    return 0;
1089
1090
23101
  stream->Close(code);
1091
1092
  // It is possible for the stream close to occur before the stream is
1093
  // ever passed on to the javascript side. If that happens, the callback
1094
  // will return false.
1095
23101
  Local<Value> arg = Integer::NewFromUnsigned(isolate, code);
1096
  MaybeLocal<Value> answer =
1097
    stream->MakeCallback(env->http2session_on_stream_close_function(),
1098
23101
                          1, &arg);
1099

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

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

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

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


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


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

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

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

211800
  if (!session_->IsAvailableSessionMemory(length) ||
2208

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

54692
  while (!stream->queue_.empty() && stream->queue_.front().buf.len == 0) {
2272
4
    WriteWrap* finished = stream->queue_.front().req_wrap;
2273
4
    stream->queue_.pop();
2274
4
    if (finished != nullptr)
2275
2
      finished->Done(0);
2276
  }
2277
2278
27344
  if (!stream->queue_.empty()) {
2279
    Debug(session, "stream %d has pending outbound data", id);
2280
11195
    amount = std::min(stream->available_outbound_length_, length);
2281
    Debug(session, "sending %d bytes for data frame on stream %d", amount, id);
2282
11195
    if (amount > 0) {
2283
      // Just return the length, let Http2Session::OnSendData take care of
2284
      // actually taking the buffers out of the queue.
2285
11195
      *flags |= NGHTTP2_DATA_FLAG_NO_COPY;
2286
11195
      stream->DecrementAvailableOutboundLength(amount);
2287
    }
2288
  }
2289
2290

27344
  if (amount == 0 && stream->IsWritable()) {
2291
3598
    CHECK(stream->queue_.empty());
2292
    Debug(session, "deferring stream %d", id);
2293
3598
    stream->EmitWantsWrite(length);
2294

3598
    if (stream->available_outbound_length_ > 0 || !stream->IsWritable()) {
2295
      // EmitWantsWrite() did something interesting synchronously, restart:
2296
      return OnRead(handle, id, buf, length, flags, source, user_data);
2297
    }
2298
3598
    return NGHTTP2_ERR_DEFERRED;
2299
  }
2300
2301

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

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

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

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


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

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