GCC Code Coverage Report
Directory: ../ Exec Total Coverage
File: /home/iojs/build/workspace/node-test-commit-linux-coverage/nodes/benchmark/out/../src/node_http2.cc Lines: 1231 1261 97.6 %
Date: 2017-12-18 Branches: 377 519 72.6 %

Line Branch Exec Source
1
#include "aliased_buffer.h"
2
#include "node.h"
3
#include "node_buffer.h"
4
#include "node_http2.h"
5
#include "node_http2_state.h"
6
7
#include <queue>
8
#include <algorithm>
9
10
namespace node {
11
12
using v8::Boolean;
13
using v8::Context;
14
using v8::Float64Array;
15
using v8::Function;
16
using v8::Integer;
17
using v8::Number;
18
using v8::ObjectTemplate;
19
using v8::String;
20
using v8::Uint32;
21
using v8::Uint32Array;
22
using v8::Undefined;
23
24
namespace http2 {
25
26
// These configure the callbacks required by nghttp2 itself. There are
27
// two sets of callback functions, one that is used if a padding callback
28
// is set, and other that does not include the padding callback.
29
6782
const Http2Session::Callbacks Http2Session::callback_struct_saved[2] = {
30
    Callbacks(false),
31
3391
    Callbacks(true)};
32
33
// The Http2Scope object is used to queue a write to the i/o stream. It is
34
// used whenever any action is take on the underlying nghttp2 API that may
35
// push data into nghttp2 outbound data queue.
36
//
37
// For example:
38
//
39
// Http2Scope h2scope(session);
40
// nghttp2_submit_ping(**session, ... );
41
//
42
// When the Http2Scope passes out of scope and is deconstructed, it will
43
// call Http2Session::MaybeScheduleWrite().
44
10745
Http2Scope::Http2Scope(Http2Stream* stream) : Http2Scope(stream->session()) {}
45
46
14237
Http2Scope::Http2Scope(Http2Session* session) {
47
14237
  if (session == nullptr)
48
    return;
49
50
14237
  if (session->flags_ & (SESSION_STATE_HAS_SCOPE |
51
                         SESSION_STATE_WRITE_SCHEDULED)) {
52
    // There is another scope further below on the stack, or it is already
53
    // known that a write is scheduled. In either case, there is nothing to do.
54
11492
    return;
55
  }
56
2745
  session->flags_ |= SESSION_STATE_HAS_SCOPE;
57
2745
  session_ = session;
58
}
59
60
14237
Http2Scope::~Http2Scope() {
61
14237
  if (session_ == nullptr)
62
11492
    return;
63
64
2745
  session_->flags_ &= ~SESSION_STATE_HAS_SCOPE;
65
2745
  session_->MaybeScheduleWrite();
66
14237
}
67
68
// The Http2Options object is used during the construction of Http2Session
69
// instances to configure an appropriate nghttp2_options struct. The class
70
// uses a single TypedArray instance that is shared with the JavaScript side
71
// to more efficiently pass values back and forth.
72
426
Http2Options::Http2Options(Environment* env) {
73
426
  nghttp2_option_new(&options_);
74
75
  // We manually handle flow control within a session in order to
76
  // implement backpressure -- that is, we only send WINDOW_UPDATE
77
  // frames to the remote peer as data is actually consumed by user
78
  // code. This ensures that the flow of data over the connection
79
  // does not move too quickly and limits the amount of data we
80
  // are required to buffer.
81
426
  nghttp2_option_set_no_auto_window_update(options_, 1);
82
83
  AliasedBuffer<uint32_t, v8::Uint32Array>& buffer =
84
426
      env->http2_state()->options_buffer;
85
426
  uint32_t flags = buffer[IDX_OPTIONS_FLAGS];
86
87
426
  if (flags & (1 << IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE)) {
88
    nghttp2_option_set_max_deflate_dynamic_table_size(
89
        options_,
90
        buffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE]);
91
  }
92
93
426
  if (flags & (1 << IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS)) {
94
    nghttp2_option_set_max_reserved_remote_streams(
95
        options_,
96
1
        buffer[IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS]);
97
  }
98
99
426
  if (flags & (1 << IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH)) {
100
    nghttp2_option_set_max_send_header_block_length(
101
        options_,
102
1
        buffer[IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH]);
103
  }
104
105
  // Recommended default
106
426
  nghttp2_option_set_peer_max_concurrent_streams(options_, 100);
107
426
  if (flags & (1 << IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS)) {
108
    nghttp2_option_set_peer_max_concurrent_streams(
109
        options_,
110
        buffer[IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS]);
111
  }
112
113
  // The padding strategy sets the mechanism by which we determine how much
114
  // additional frame padding to apply to DATA and HEADERS frames. Currently
115
  // this is set on a per-session basis, but eventually we may switch to
116
  // a per-stream setting, giving users greater control
117
426
  if (flags & (1 << IDX_OPTIONS_PADDING_STRATEGY)) {
118
    padding_strategy_type strategy =
119
        static_cast<padding_strategy_type>(
120
2
            buffer.GetValue(IDX_OPTIONS_PADDING_STRATEGY));
121
2
    SetPaddingStrategy(strategy);
122
  }
123
124
  // The max header list pairs option controls the maximum number of
125
  // header pairs the session may accept. This is a hard limit.. that is,
126
  // if the remote peer sends more than this amount, the stream will be
127
  // automatically closed with an RST_STREAM.
128
426
  if (flags & (1 << IDX_OPTIONS_MAX_HEADER_LIST_PAIRS)) {
129
1
    SetMaxHeaderPairs(buffer[IDX_OPTIONS_MAX_HEADER_LIST_PAIRS]);
130
  }
131
132
  // The HTTP2 specification places no limits on the number of HTTP2
133
  // PING frames that can be sent. In order to prevent PINGS from being
134
  // abused as an attack vector, however, we place a strict upper limit
135
  // on the number of unacknowledged PINGS that can be sent at any given
136
  // time.
137
426
  if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_PINGS)) {
138
1
    SetMaxOutstandingPings(buffer[IDX_OPTIONS_MAX_OUTSTANDING_PINGS]);
139
  }
140
426
}
141
142
// The Http2Settings class is used to configure a SETTINGS frame that is
143
// to be sent to the connected peer. The settings are set using a TypedArray
144
// that is shared with the JavaScript side.
145
448
Http2Settings::Http2Settings(Environment* env) : env_(env) {
146
448
  entries_.AllocateSufficientStorage(IDX_SETTINGS_COUNT);
147
  AliasedBuffer<uint32_t, v8::Uint32Array>& buffer =
148
448
      env->http2_state()->settings_buffer;
149
448
  uint32_t flags = buffer[IDX_SETTINGS_COUNT];
150
151
448
  size_t n = 0;
152
153
448
  if (flags & (1 << IDX_SETTINGS_HEADER_TABLE_SIZE)) {
154
5
    uint32_t val = buffer[IDX_SETTINGS_HEADER_TABLE_SIZE];
155
    DEBUG_HTTP2("Http2Settings: setting header table size: %d\n", val);
156
5
    entries_[n].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
157
5
    entries_[n].value = val;
158
5
    n++;
159
  }
160
161
448
  if (flags & (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS)) {
162
4
    uint32_t val = buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS];
163
    DEBUG_HTTP2("Http2Settings: setting max concurrent streams: %d\n", val);
164
4
    entries_[n].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
165
4
    entries_[n].value = val;
166
4
    n++;
167
  }
168
169
448
  if (flags & (1 << IDX_SETTINGS_MAX_FRAME_SIZE)) {
170
6
    uint32_t val = buffer[IDX_SETTINGS_MAX_FRAME_SIZE];
171
    DEBUG_HTTP2("Http2Settings: setting max frame size: %d\n", val);
172
6
    entries_[n].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
173
6
    entries_[n].value = val;
174
6
    n++;
175
  }
176
177
448
  if (flags & (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE)) {
178
16
    uint32_t val = buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE];
179
    DEBUG_HTTP2("Http2Settings: setting initial window size: %d\n", val);
180
16
    entries_[n].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
181
16
    entries_[n].value = val;
182
16
    n++;
183
  }
184
185
448
  if (flags & (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE)) {
186
7
    uint32_t val = buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE];
187
    DEBUG_HTTP2("Http2Settings: setting max header list size: %d\n", val);
188
7
    entries_[n].settings_id = NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE;
189
7
    entries_[n].value = val;
190
7
    n++;
191
  }
192
193
448
  if (flags & (1 << IDX_SETTINGS_ENABLE_PUSH)) {
194
11
    uint32_t val = buffer[IDX_SETTINGS_ENABLE_PUSH];
195
    DEBUG_HTTP2("Http2Settings: setting enable push: %d\n", val);
196
11
    entries_[n].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
197
11
    entries_[n].value = val;
198
11
    n++;
199
  }
200
201
448
  count_ = n;
202
448
}
203
204
205
// Generates a Buffer that contains the serialized payload of a SETTINGS
206
// frame. This can be used, for instance, to create the Base64-encoded
207
// content of an Http2-Settings header field.
208
15
inline Local<Value> Http2Settings::Pack() {
209
15
  const size_t len = count_ * 6;
210
30
  Local<Value> buf = Buffer::New(env_, len).ToLocalChecked();
211
  ssize_t ret =
212
      nghttp2_pack_settings_payload(
213
15
        reinterpret_cast<uint8_t*>(Buffer::Data(buf)), len,
214
30
        *entries_, count_);
215
15
  if (ret >= 0)
216
14
    return buf;
217
  else
218
2
    return Undefined(env_->isolate());
219
}
220
221
// Updates the shared TypedArray with the current remote or local settings for
222
// the session.
223
746
inline void Http2Settings::Update(Environment* env,
224
                                  Http2Session* session,
225
                                  get_setting fn) {
226
  AliasedBuffer<uint32_t, v8::Uint32Array>& buffer =
227
746
      env->http2_state()->settings_buffer;
228
1492
  buffer[IDX_SETTINGS_HEADER_TABLE_SIZE] =
229
1492
      fn(**session, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE);
230
1492
  buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS] =
231
1492
      fn(**session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
232
1492
  buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] =
233
1492
      fn(**session, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
234
1492
  buffer[IDX_SETTINGS_MAX_FRAME_SIZE] =
235
1492
      fn(**session, NGHTTP2_SETTINGS_MAX_FRAME_SIZE);
236
1492
  buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] =
237
1492
      fn(**session, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE);
238
1492
  buffer[IDX_SETTINGS_ENABLE_PUSH] =
239
1492
      fn(**session, NGHTTP2_SETTINGS_ENABLE_PUSH);
240
746
}
241
242
// Initializes the shared TypedArray with the default settings values.
243
3
inline void Http2Settings::RefreshDefaults(Environment* env) {
244
  AliasedBuffer<uint32_t, v8::Uint32Array>& buffer =
245
3
      env->http2_state()->settings_buffer;
246
247
6
  buffer[IDX_SETTINGS_HEADER_TABLE_SIZE] =
248
3
      DEFAULT_SETTINGS_HEADER_TABLE_SIZE;
249
6
  buffer[IDX_SETTINGS_ENABLE_PUSH] =
250
3
      DEFAULT_SETTINGS_ENABLE_PUSH;
251
6
  buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] =
252
3
      DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE;
253
6
  buffer[IDX_SETTINGS_MAX_FRAME_SIZE] =
254
3
      DEFAULT_SETTINGS_MAX_FRAME_SIZE;
255
6
  buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] =
256
3
      DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE;
257
6
  buffer[IDX_SETTINGS_COUNT] =
258
    (1 << IDX_SETTINGS_HEADER_TABLE_SIZE) |
259
    (1 << IDX_SETTINGS_ENABLE_PUSH) |
260
    (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE) |
261
    (1 << IDX_SETTINGS_MAX_FRAME_SIZE) |
262
3
    (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE);
263
3
}
264
265
266
// The Http2Priority class initializes an appropriate nghttp2_priority_spec
267
// struct used when either creating a stream or updating its priority
268
// settings.
269
460
Http2Priority::Http2Priority(Environment* env,
270
                             Local<Value> parent,
271
                             Local<Value> weight,
272
                             Local<Value> exclusive) {
273
460
  Local<Context> context = env->context();
274
920
  int32_t parent_ = parent->Int32Value(context).ToChecked();
275
920
  int32_t weight_ = weight->Int32Value(context).ToChecked();
276
920
  bool exclusive_ = exclusive->BooleanValue(context).ToChecked();
277
  DEBUG_HTTP2("Http2Priority: parent: %d, weight: %d, exclusive: %d\n",
278
              parent_, weight_, exclusive_);
279
460
  nghttp2_priority_spec_init(&spec, parent_, weight_, exclusive_ ? 1 : 0);
280
460
}
281
282
283
inline const char* Http2Session::TypeName() {
284
  switch (session_type_) {
285
    case NGHTTP2_SESSION_SERVER: return "server";
286
    case NGHTTP2_SESSION_CLIENT: return "client";
287
    default:
288
      // This should never happen
289
      ABORT();
290
  }
291
}
292
293
// The Headers class initializes a proper array of nghttp2_nv structs
294
// containing the header name value pairs.
295
879
Headers::Headers(Isolate* isolate,
296
                 Local<Context> context,
297
879
                 Local<Array> headers) {
298
1758
  Local<Value> header_string = headers->Get(context, 0).ToLocalChecked();
299
1758
  Local<Value> header_count = headers->Get(context, 1).ToLocalChecked();
300
1758
  count_ = header_count.As<Uint32>()->Value();
301
1758
  int header_string_len = header_string.As<String>()->Length();
302
303
879
  if (count_ == 0) {
304
15
    CHECK_EQ(header_string_len, 0);
305
31
    return;
306
  }
307
308
  // Allocate a single buffer with count_ nghttp2_nv structs, followed
309
  // by the raw header data as passed from JS. This looks like:
310
  // | possible padding | nghttp2_nv | nghttp2_nv | ... | header contents |
311
  buf_.AllocateSufficientStorage((alignof(nghttp2_nv) - 1) +
312
864
                                 count_ * sizeof(nghttp2_nv) +
313
1728
                                 header_string_len);
314
  // Make sure the start address is aligned appropriately for an nghttp2_nv*.
315
  char* start = reinterpret_cast<char*>(
316
864
      ROUND_UP(reinterpret_cast<uintptr_t>(*buf_), alignof(nghttp2_nv)));
317
864
  char* header_contents = start + (count_ * sizeof(nghttp2_nv));
318
864
  nghttp2_nv* const nva = reinterpret_cast<nghttp2_nv*>(start);
319
320
864
  CHECK_LE(header_contents + header_string_len, *buf_ + buf_.length());
321
1728
  CHECK_EQ(header_string.As<String>()
322
              ->WriteOneByte(reinterpret_cast<uint8_t*>(header_contents),
323
                             0, header_string_len,
324
                             String::NO_NULL_TERMINATION),
325
           header_string_len);
326
327
864
  size_t n = 0;
328
  char* p;
329
3744
  for (p = header_contents; p < header_contents + header_string_len; n++) {
330
2881
    if (n >= count_) {
331
      // This can happen if a passed header contained a null byte. In that
332
      // case, just provide nghttp2 with an invalid header to make it reject
333
      // the headers list.
334
      static uint8_t zero = '\0';
335
1
      nva[0].name = nva[0].value = &zero;
336
1
      nva[0].namelen = nva[0].valuelen = 1;
337
1
      count_ = 1;
338
1
      return;
339
    }
340
341
2880
    nva[n].flags = NGHTTP2_NV_FLAG_NONE;
342
2880
    nva[n].name = reinterpret_cast<uint8_t*>(p);
343
2880
    nva[n].namelen = strlen(p);
344
2880
    p += nva[n].namelen + 1;
345
2880
    nva[n].value = reinterpret_cast<uint8_t*>(p);
346
2880
    nva[n].valuelen = strlen(p);
347
2880
    p += nva[n].valuelen + 1;
348
  }
349
}
350
351
352
// Sets the various callback functions that nghttp2 will use to notify us
353
// about significant events while processing http2 stuff.
354
6782
Http2Session::Callbacks::Callbacks(bool kHasGetPaddingCallback) {
355
6782
  CHECK_EQ(nghttp2_session_callbacks_new(&callbacks), 0);
356
357
  nghttp2_session_callbacks_set_on_begin_headers_callback(
358
6782
    callbacks, OnBeginHeadersCallback);
359
  nghttp2_session_callbacks_set_on_header_callback2(
360
6782
    callbacks, OnHeaderCallback);
361
  nghttp2_session_callbacks_set_on_frame_recv_callback(
362
6782
    callbacks, OnFrameReceive);
363
  nghttp2_session_callbacks_set_on_stream_close_callback(
364
6782
    callbacks, OnStreamClose);
365
  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
366
6782
    callbacks, OnDataChunkReceived);
367
  nghttp2_session_callbacks_set_on_frame_not_send_callback(
368
6782
    callbacks, OnFrameNotSent);
369
  nghttp2_session_callbacks_set_on_invalid_header_callback2(
370
6782
    callbacks, OnInvalidHeader);
371
  nghttp2_session_callbacks_set_error_callback(
372
6782
    callbacks, OnNghttpError);
373
374
6782
  if (kHasGetPaddingCallback) {
375
    nghttp2_session_callbacks_set_select_padding_callback(
376
3391
      callbacks, OnSelectPadding);
377
  }
378
6782
}
379
380
381
6782
Http2Session::Callbacks::~Callbacks() {
382
6782
  nghttp2_session_callbacks_del(callbacks);
383
6782
}
384
385
426
Http2Session::Http2Session(Environment* env,
386
                           Local<Object> wrap,
387
                           nghttp2_session_type type)
388
    : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_HTTP2SESSION),
389
426
      session_type_(type) {
390
426
  MakeWeak<Http2Session>(this);
391
392
  // Capture the configuration options for this session
393
426
  Http2Options opts(env);
394
395
426
  int32_t maxHeaderPairs = opts.GetMaxHeaderPairs();
396
  max_header_pairs_ =
397
      type == NGHTTP2_SESSION_SERVER
398
1071
          ? std::max(maxHeaderPairs, 4)     // minimum # of request headers
399

1067
          : std::max(maxHeaderPairs, 1);    // minimum # of response headers
400
401
426
  max_outstanding_pings_ = opts.GetMaxOutstandingPings();
402
403
426
  padding_strategy_ = opts.GetPaddingStrategy();
404
405
  bool hasGetPaddingCallback =
406

852
      padding_strategy_ == PADDING_STRATEGY_MAX ||
407
852
      padding_strategy_ == PADDING_STRATEGY_CALLBACK;
408
409
  nghttp2_session_callbacks* callbacks
410
426
      = callback_struct_saved[hasGetPaddingCallback ? 1 : 0].callbacks;
411
412
  auto fn = type == NGHTTP2_SESSION_SERVER ?
413
      nghttp2_session_server_new2 :
414
426
      nghttp2_session_client_new2;
415
416
  // This should fail only if the system is out of memory, which
417
  // is going to cause lots of other problems anyway, or if any
418
  // of the options are out of acceptable range, which we should
419
  // be catching before it gets this far. Either way, crash if this
420
  // fails.
421
426
  CHECK_EQ(fn(&session_, callbacks, this, *opts), 0);
422
426
}
423
424
626
void Http2Session::Unconsume() {
425
626
  if (stream_ != nullptr) {
426
    DEBUG_HTTP2SESSION(this, "unconsuming the i/o stream");
427
397
    stream_->set_destruct_cb({ nullptr, nullptr });
428
397
    stream_->set_alloc_cb({ nullptr, nullptr });
429
397
    stream_->set_read_cb({ nullptr, nullptr });
430
397
    stream_->Unconsume();
431
397
    stream_ = nullptr;
432
  }
433
626
}
434
435
1245
Http2Session::~Http2Session() {
436
830
  if (!object().IsEmpty())
437
    ClearWrap(object());
438
415
  persistent().Reset();
439
830
  CHECK(persistent().IsEmpty());
440
415
  Unconsume();
441
  DEBUG_HTTP2SESSION(this, "freeing nghttp2 session");
442
415
  nghttp2_session_del(session_);
443
830
}
444
445
// Closes the session and frees the associated resources
446
419
void Http2Session::Close(uint32_t code, bool socket_closed) {
447
  DEBUG_HTTP2SESSION(this, "closing session");
448
449
419
  if (flags_ & SESSION_STATE_CLOSED)
450
419
    return;
451
419
  flags_ |= SESSION_STATE_CLOSED;
452
453
  // Stop reading on the i/o stream
454
419
  if (stream_ != nullptr)
455
409
    stream_->ReadStop();
456
457
  // If the socket is not closed, then attempt to send a closing GOAWAY
458
  // frame. There is no guarantee that this GOAWAY will be received by
459
  // the peer but the HTTP/2 spec recommends sendinng it anyway. We'll
460
  // make a best effort.
461
419
  if (!socket_closed) {
462
208
    Http2Scope h2scope(this);
463
    DEBUG_HTTP2SESSION2(this, "terminating session with code %d", code);
464
208
    CHECK_EQ(nghttp2_session_terminate_session(session_, code), 0);
465
  } else {
466
211
    Unconsume();
467
  }
468
469
  // If there are outstanding pings, those will need to be canceled, do
470
  // so on the next iteration of the event loop to avoid calling out into
471
  // javascript since this may be called during garbage collection.
472
838
  while (!outstanding_pings_.empty()) {
473
    Http2Session::Http2Ping* ping = PopPing();
474
    env()->SetImmediate([](Environment* env, void* data) {
475
      static_cast<Http2Session::Http2Ping*>(data)->Done(false);
476
    }, static_cast<void*>(ping));
477
  }
478
}
479
480
// Locates an existing known stream by ID. nghttp2 has a similar method
481
// but this is faster and does not fail if the stream is not found.
482
13979
inline Http2Stream* Http2Session::FindStream(int32_t id) {
483
13979
  auto s = streams_.find(id);
484
13979
  return s != streams_.end() ? s->second : nullptr;
485
}
486
487
488
926
inline void Http2Session::AddStream(Http2Stream* stream) {
489
926
  streams_[stream->id()] = stream;
490
926
}
491
492
493
924
inline void Http2Session::RemoveStream(int32_t id) {
494
924
  streams_.erase(id);
495
924
}
496
497
// Used as one of the Padding Strategy functions. Uses the maximum amount
498
// of padding allowed for the current frame.
499
inline ssize_t Http2Session::OnMaxFrameSizePadding(size_t frameLen,
500
                                                   size_t maxPayloadLen) {
501
  DEBUG_HTTP2SESSION2(this, "using max frame size padding: %d", maxPayloadLen);
502
  return maxPayloadLen;
503
}
504
505
// Used as one of the Padding Strategy functions. Uses a callback to JS land
506
// to determine the amount of padding for the current frame. This option is
507
// rather more expensive because of the JS boundary cross. It generally should
508
// not be the preferred option.
509
3
inline ssize_t Http2Session::OnCallbackPadding(size_t frameLen,
510
                                               size_t maxPayloadLen) {
511
  DEBUG_HTTP2SESSION(this, "using callback to determine padding");
512
3
  Isolate* isolate = env()->isolate();
513
3
  HandleScope handle_scope(isolate);
514
3
  Local<Context> context = env()->context();
515
  Context::Scope context_scope(context);
516
517
#if defined(DEBUG) && DEBUG
518
  CHECK(object()->Has(context, env()->ongetpadding_string()).FromJust());
519
#endif
520
521
  AliasedBuffer<uint32_t, v8::Uint32Array>& buffer =
522
3
      env()->http2_state()->padding_buffer;
523
3
  buffer[PADDING_BUF_FRAME_LENGTH] = frameLen;
524
3
  buffer[PADDING_BUF_MAX_PAYLOAD_LENGTH] = maxPayloadLen;
525
3
  buffer[PADDING_BUF_RETURN_VALUE] = frameLen;
526
3
  MakeCallback(env()->ongetpadding_string(), 0, nullptr);
527
3
  uint32_t retval = buffer[PADDING_BUF_RETURN_VALUE];
528
3
  retval = std::min(retval, static_cast<uint32_t>(maxPayloadLen));
529
3
  retval = std::max(retval, static_cast<uint32_t>(frameLen));
530
  DEBUG_HTTP2SESSION2(this, "using padding size %d", retval);
531
6
  return retval;
532
}
533
534
535
// Sends a SETTINGS frame to the connected peer. This has the side effect of
536
// changing the settings state within the nghttp2_session, but those will
537
// only be considered active once the connected peer acknowledges the SETTINGS
538
// frame.
539
// Note: This *must* send a SETTINGS frame even if niv == 0
540
433
inline void Http2Session::Settings(const nghttp2_settings_entry iv[],
541
                                   size_t niv) {
542
  DEBUG_HTTP2SESSION2(this, "submitting %d settings", niv);
543
433
  Http2Scope h2scope(this);
544
  // This will fail either if the system is out of memory, or if the settings
545
  // values are not within the appropriate range. We should be catching the
546
  // latter before it gets this far so crash in either case.
547
433
  CHECK_EQ(nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv, niv), 0);
548
433
}
549
550
551
// Write data received from the i/o stream to the underlying nghttp2_session.
552
// On each call to nghttp2_session_mem_recv, nghttp2 will begin calling the
553
// various callback functions. Each of these will typically result in a call
554
// out to JavaScript so this particular function is rather hot and can be
555
// quite expensive. This is a potential performance optimization target later.
556
1759
inline ssize_t Http2Session::Write(const uv_buf_t* bufs, size_t nbufs) {
557
1759
  size_t total = 0;
558
  // Note that nghttp2_session_mem_recv is a synchronous operation that
559
  // will trigger a number of other callbacks. Those will, in turn have
560
  // multiple side effects.
561
3516
  for (size_t n = 0; n < nbufs; n++) {
562
    DEBUG_HTTP2SESSION2(this, "receiving %d bytes [wants data? %d]",
563
                        bufs[n].len,
564
                        nghttp2_session_want_read(session_));
565
    ssize_t ret =
566
      nghttp2_session_mem_recv(session_,
567
1759
                               reinterpret_cast<uint8_t*>(bufs[n].base),
568
3518
                               bufs[n].len);
569
1759
    CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
570
571
    // If there is an error calling any of the callbacks, ret will be a
572
    // negative number identifying the error code. This can happen, for
573
    // instance, if the session is destroyed during any of the JS callbacks
574
    // Note: if ssize_t is not defined (e.g. on Win32), nghttp2 will typedef
575
    // ssize_t to int. Cast here so that the < 0 check actually works on
576
    // Windows.
577
1759
    if (static_cast<int>(ret) < 0)
578
2
      return ret;
579
580
1757
    total += ret;
581
  }
582
  // Send any data that was queued up while processing the received data.
583
1757
  if (!IsDestroyed()) {
584
1582
    SendPendingData();
585
  }
586
1757
  return total;
587
}
588
589
590
7582
inline int32_t GetFrameID(const nghttp2_frame* frame) {
591
  // If this is a push promise, we want to grab the id of the promised stream
592
7582
  return (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
593
      frame->push_promise.promised_stream_id :
594
7582
      frame->hd.stream_id;
595
}
596
597
598
// Called by nghttp2 at the start of receiving a HEADERS frame. We use this
599
// callback to determine if a new stream is being created or if we are simply
600
// adding a new block of headers to an existing stream. The header pairs
601
// themselves are set in the OnHeaderCallback
602
856
inline int Http2Session::OnBeginHeadersCallback(nghttp2_session* handle,
603
                                                const nghttp2_frame* frame,
604
                                                void* user_data) {
605
856
  Http2Session* session = static_cast<Http2Session*>(user_data);
606
856
  int32_t id = GetFrameID(frame);
607
  DEBUG_HTTP2SESSION2(session, "beginning headers for stream %d", id);
608
609
856
  Http2Stream* stream = session->FindStream(id);
610
856
  if (stream == nullptr) {
611
462
    new Http2Stream(session, id, frame->headers.cat);
612
  } else {
613
    // If the stream has already been destroyed, ignore.
614
394
    if (stream->IsDestroyed())
615
      return 0;
616
394
    stream->StartHeaders(frame->headers.cat);
617
  }
618
856
  return 0;
619
}
620
621
// Called by nghttp2 for each header name/value pair in a HEADERS block.
622
// This had to have been preceeded by a call to OnBeginHeadersCallback so
623
// the Http2Stream is guaranteed to already exist.
624
2827
inline int Http2Session::OnHeaderCallback(nghttp2_session* handle,
625
                                          const nghttp2_frame* frame,
626
                                          nghttp2_rcbuf* name,
627
                                          nghttp2_rcbuf* value,
628
                                          uint8_t flags,
629
                                          void* user_data) {
630
2827
  Http2Session* session = static_cast<Http2Session*>(user_data);
631
2827
  int32_t id = GetFrameID(frame);
632
2827
  Http2Stream* stream = session->FindStream(id);
633
2827
  CHECK_NE(stream, nullptr);
634
  // If the stream has already been destroyed, ignore.
635
2827
  if (stream->IsDestroyed())
636
    return 0;
637
2827
  if (!stream->AddHeader(name, value, flags)) {
638
    // This will only happen if the connected peer sends us more
639
    // than the allowed number of header items at any given time
640
2
    stream->SubmitRstStream(NGHTTP2_ENHANCE_YOUR_CALM);
641
2
    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
642
  }
643
2825
  return 0;
644
}
645
646
647
// Called by nghttp2 when a complete HTTP2 frame has been received. There are
648
// only a handful of frame types tha we care about handling here.
649
5194
inline int Http2Session::OnFrameReceive(nghttp2_session* handle,
650
                                        const nghttp2_frame* frame,
651
                                        void* user_data) {
652
5194
  Http2Session* session = static_cast<Http2Session*>(user_data);
653
  DEBUG_HTTP2SESSION2(session, "complete frame received: type: %d",
654
                      frame->hd.type);
655

5194
  switch (frame->hd.type) {
656
    case NGHTTP2_DATA:
657
3066
      session->HandleDataFrame(frame);
658
3066
      break;
659
    case NGHTTP2_PUSH_PROMISE:
660
      // Intentional fall-through, handled just like headers frames
661
    case NGHTTP2_HEADERS:
662
820
      session->HandleHeadersFrame(frame);
663
820
      break;
664
    case NGHTTP2_SETTINGS:
665
753
      session->HandleSettingsFrame(frame);
666
753
      break;
667
    case NGHTTP2_PRIORITY:
668
13
      session->HandlePriorityFrame(frame);
669
13
      break;
670
    case NGHTTP2_GOAWAY:
671
11
      session->HandleGoawayFrame(frame);
672
11
      break;
673
    case NGHTTP2_PING:
674
6
      session->HandlePingFrame(frame);
675
    default:
676
531
      break;
677
  }
678
5194
  return 0;
679
}
680
681
682
// If nghttp2 is unable to send a queued up frame, it will call this callback
683
// to let us know. If the failure occurred because we are in the process of
684
// closing down the session or stream, we go ahead and ignore it. We don't
685
// really care about those and there's nothing we can reasonably do about it
686
// anyway. Other types of failures are reported up to JavaScript. This should
687
// be exceedingly rare.
688
3
inline int Http2Session::OnFrameNotSent(nghttp2_session* handle,
689
                                        const nghttp2_frame* frame,
690
                                        int error_code,
691
                                        void* user_data) {
692
3
  Http2Session* session = static_cast<Http2Session*>(user_data);
693
3
  Environment* env = session->env();
694
  DEBUG_HTTP2SESSION2(session, "frame type %d was not sent, code: %d",
695
                      frame->hd.type, error_code);
696
  // Do not report if the frame was not sent due to the session closing
697

3
  if (error_code != NGHTTP2_ERR_SESSION_CLOSING &&
698
3
      error_code != NGHTTP2_ERR_STREAM_CLOSED &&
699
      error_code != NGHTTP2_ERR_STREAM_CLOSING) {
700
1
    Isolate* isolate = env->isolate();
701
1
    HandleScope scope(isolate);
702
1
    Local<Context> context = env->context();
703
    Context::Scope context_scope(context);
704
705
    Local<Value> argv[3] = {
706
      Integer::New(isolate, frame->hd.stream_id),
707
      Integer::New(isolate, frame->hd.type),
708
      Integer::New(isolate, error_code)
709
4
    };
710
2
    session->MakeCallback(env->onframeerror_string(), arraysize(argv), argv);
711
  }
712
3
  return 0;
713
}
714
715
// Called by nghttp2 when a stream closes.
716
916
inline int Http2Session::OnStreamClose(nghttp2_session* handle,
717
                                       int32_t id,
718
                                       uint32_t code,
719
                                       void* user_data) {
720
916
  Http2Session* session = static_cast<Http2Session*>(user_data);
721
916
  Environment* env = session->env();
722
916
  Isolate* isolate = env->isolate();
723
916
  HandleScope scope(isolate);
724
916
  Local<Context> context = env->context();
725
  Context::Scope context_scope(context);
726
  DEBUG_HTTP2SESSION2(session, "stream %d closed with code: %d", id, code);
727
916
  Http2Stream* stream = session->FindStream(id);
728
  // Intentionally ignore the callback if the stream does not exist or has
729
  // already been destroyed
730

916
  if (stream != nullptr && !stream->IsDestroyed()) {
731
885
    stream->AddChunk(nullptr, 0);
732
885
    stream->Close(code);
733
    // It is possible for the stream close to occur before the stream is
734
    // ever passed on to the javascript side. If that happens, skip straight
735
    // to destroying the stream. We can check this by looking for the
736
    // onstreamclose function. If it exists, then the stream has already
737
    // been passed on to javascript.
738
    Local<Value> fn =
739
3540
        stream->object()->Get(context, env->onstreamclose_string())
740
1770
            .ToLocalChecked();
741
885
    if (fn->IsFunction()) {
742
      Local<Value> argv[2] = {
743
        Integer::NewFromUnsigned(isolate, code),
744
849
        Boolean::New(isolate, stream->HasDataChunks(true))
745
3396
      };
746
1698
      stream->MakeCallback(fn.As<Function>(), arraysize(argv), argv);
747
    } else {
748
36
      stream->Destroy();
749
    }
750
  }
751
1832
  return 0;
752
}
753
754
// Called by nghttp2 when an invalid header has been received. For now, we
755
// ignore these. If this callback was not provided, nghttp2 would handle
756
// invalid headers strictly and would shut down the stream. We are intentionally
757
// being more lenient here although we may want to revisit this choice later.
758
4
inline int Http2Session::OnInvalidHeader(nghttp2_session* session,
759
                                         const nghttp2_frame* frame,
760
                                         nghttp2_rcbuf* name,
761
                                         nghttp2_rcbuf* value,
762
                                         uint8_t flags,
763
                                         void* user_data) {
764
  // Ignore invalid header fields by default.
765
4
  return 0;
766
}
767
768
// When nghttp2 receives a DATA frame, it will deliver the data payload to
769
// us in discrete chunks. We push these into a linked list stored in the
770
// Http2Sttream which is flushed out to JavaScript as quickly as possible.
771
// This can be a particularly hot path.
772
3191
inline int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
773
                                             uint8_t flags,
774
                                             int32_t id,
775
                                             const uint8_t* data,
776
                                             size_t len,
777
                                             void* user_data) {
778
3191
  Http2Session* session = static_cast<Http2Session*>(user_data);
779
  DEBUG_HTTP2SESSION2(session, "buffering data chunk for stream %d, size: "
780
              "%d, flags: %d", id, len, flags);
781
  // We should never actually get a 0-length chunk so this check is
782
  // only a precaution at this point.
783
3191
  if (len > 0) {
784
    // Notify nghttp2 that we've consumed a chunk of data on the connection
785
    // so that it can send a WINDOW_UPDATE frame. This is a critical part of
786
    // the flow control process in http2
787
3191
    CHECK_EQ(nghttp2_session_consume_connection(handle, len), 0);
788
3191
    Http2Stream* stream = session->FindStream(id);
789
    // If the stream has been destroyed, ignore this chunk
790
3191
    if (stream->IsDestroyed())
791
      return 0;
792
3191
    stream->AddChunk(data, len);
793
  }
794
3191
  return 0;
795
}
796
797
// Called by nghttp2 when it needs to determine how much padding to use in
798
// a DATA or HEADERS frame.
799
3
inline ssize_t Http2Session::OnSelectPadding(nghttp2_session* handle,
800
                                             const nghttp2_frame* frame,
801
                                             size_t maxPayloadLen,
802
                                             void* user_data) {
803
3
  Http2Session* session = static_cast<Http2Session*>(user_data);
804
3
  ssize_t padding = frame->hd.length;
805
806
3
  return session->padding_strategy_ == PADDING_STRATEGY_MAX
807
    ? session->OnMaxFrameSizePadding(padding, maxPayloadLen)
808
3
    : session->OnCallbackPadding(padding, maxPayloadLen);
809
}
810
811
#define BAD_PEER_MESSAGE "Remote peer returned unexpected data while we "     \
812
                         "expected SETTINGS frame.  Perhaps, peer does not "  \
813
                         "support HTTP/2 properly."
814
815
// We use this currently to determine when an attempt is made to use the http2
816
// protocol with a non-http2 peer.
817
35
inline int Http2Session::OnNghttpError(nghttp2_session* handle,
818
                                       const char* message,
819
                                       size_t len,
820
                                       void* user_data) {
821
  // Unfortunately, this is currently the only way for us to know if
822
  // the session errored because the peer is not an http2 peer.
823
35
  Http2Session* session = static_cast<Http2Session*>(user_data);
824
    DEBUG_HTTP2SESSION2(session, "Error '%.*s'", len, message);
825
35
  if (strncmp(message, BAD_PEER_MESSAGE, len) == 0) {
826
1
    Environment* env = session->env();
827
1
    Isolate* isolate = env->isolate();
828
1
    HandleScope scope(isolate);
829
1
    Local<Context> context = env->context();
830
    Context::Scope context_scope(context);
831
832
    Local<Value> argv[1] = {
833
      Integer::New(isolate, NGHTTP2_ERR_PROTO),
834
2
    };
835
2
    session->MakeCallback(env->error_string(), arraysize(argv), argv);
836
  }
837
35
  return 0;
838
}
839
840
// Once all of the DATA frames for a Stream have been sent, the GetTrailers
841
// method calls out to JavaScript to fetch the trailing headers that need
842
// to be sent.
843
466
inline void Http2Session::GetTrailers(Http2Stream* stream, uint32_t* flags) {
844

466
  if (!stream->IsDestroyed() && stream->HasTrailers()) {
845
21
    Http2Stream::SubmitTrailers submit_trailers{this, stream, flags};
846
21
    stream->OnTrailers(submit_trailers);
847
  }
848
466
}
849
850
851
21
Http2Stream::SubmitTrailers::SubmitTrailers(
852
    Http2Session* session,
853
    Http2Stream* stream,
854
    uint32_t* flags)
855
21
  : session_(session), stream_(stream), flags_(flags) { }
856
857
858
19
inline void Http2Stream::SubmitTrailers::Submit(nghttp2_nv* trailers,
859
                                                size_t length) const {
860
19
  Http2Scope h2scope(session_);
861
19
  if (length == 0)
862
34
    return;
863
  DEBUG_HTTP2SESSION2(session_, "sending trailers for stream %d, count: %d",
864
                      stream_->id(), length);
865
4
  *flags_ |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
866

4
  CHECK_EQ(
867
4
      nghttp2_submit_trailer(**session_, stream_->id(), trailers, length), 0);
868
}
869
870
871
// Called by OnFrameReceived to notify JavaScript land that a complete
872
// HEADERS frame has been received and processed. This method converts the
873
// received headers into a JavaScript array and pushes those out to JS.
874
820
inline void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
875
820
  Isolate* isolate = env()->isolate();
876
820
  HandleScope scope(isolate);
877
820
  Local<Context> context = env()->context();
878
820
  Context::Scope context_scope(context);
879
880
820
  int32_t id = GetFrameID(frame);
881
  DEBUG_HTTP2SESSION2(this, "handle headers frame for stream %d", id);
882
820
  Http2Stream* stream = FindStream(id);
883
884
  // If the stream has already been destroyed, ignore.
885
820
  if (stream->IsDestroyed())
886
820
    return;
887
888
820
  nghttp2_header* headers = stream->headers();
889
820
  size_t count = stream->headers_count();
890
891
  Local<String> name_str;
892
  Local<String> value_str;
893
894
820
  Local<Array> holder = Array::New(isolate);
895
820
  Local<Function> fn = env()->push_values_to_array_function();
896
13940
  Local<Value> argv[NODE_PUSH_VAL_TO_ARRAY_MAX * 2];
897
898
  // The headers are passed in above as a queue of nghttp2_header structs.
899
  // The following converts that into a JS array with the structure:
900
  // [name1, value1, name2, value2, name3, value3, name3, value4] and so on.
901
  // That array is passed up to the JS layer and converted into an Object form
902
  // like {name1: value1, name2: value2, name3: [value3, value4]}. We do it
903
  // this way for performance reasons (it's faster to generate and pass an
904
  // array than it is to generate and pass the object).
905
820
  size_t n = 0;
906
2465
  while (count > 0) {
907
825
    size_t j = 0;
908

4366
    while (count > 0 && j < arraysize(argv) / 2) {
909
2716
      nghttp2_header item = headers[n++];
910
      // The header name and value are passed as external one-byte strings
911
      name_str =
912
5432
          ExternalHeader::New<true>(env(), item.name).ToLocalChecked();
913
      value_str =
914
5432
          ExternalHeader::New<false>(env(), item.value).ToLocalChecked();
915
5432
      argv[j * 2] = name_str;
916
5432
      argv[j * 2 + 1] = value_str;
917
2716
      count--;
918
2716
      j++;
919
    }
920
    // For performance, we pass name and value pairs to array.protototype.push
921
    // in batches of size NODE_PUSH_VAL_TO_ARRAY_MAX * 2 until there are no
922
    // more items to push.
923
825
    if (j > 0) {
924
3300
      fn->Call(env()->context(), holder, j * 2, argv).ToLocalChecked();
925
    }
926
  }
927
928
  Local<Value> args[5] = {
929
    stream->object(),
930
    Integer::New(isolate, id),
931
820
    Integer::New(isolate, stream->headers_category()),
932
    Integer::New(isolate, frame->hd.flags),
933
    holder
934
5740
  };
935
1640
  MakeCallback(env()->onheaders_string(), arraysize(args), args);
936
}
937
938
939
// Called by OnFrameReceived when a complete PRIORITY frame has been
940
// received. Notifies JS land about the priority change. Note that priorities
941
// are considered advisory only, so this has no real effect other than to
942
// simply let user code know that the priority has changed.
943
13
inline void Http2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
944
13
  Isolate* isolate = env()->isolate();
945
13
  HandleScope scope(isolate);
946
13
  Local<Context> context = env()->context();
947
  Context::Scope context_scope(context);
948
949
13
  nghttp2_priority priority_frame = frame->priority;
950
13
  int32_t id = GetFrameID(frame);
951
  DEBUG_HTTP2SESSION2(this, "handle priority frame for stream %d", id);
952
  // Priority frame stream ID should never be <= 0. nghttp2 handles this for us
953
13
  nghttp2_priority_spec spec = priority_frame.pri_spec;
954
955
  Local<Value> argv[4] = {
956
    Integer::New(isolate, id),
957
    Integer::New(isolate, spec.stream_id),
958
    Integer::New(isolate, spec.weight),
959
    Boolean::New(isolate, spec.exclusive)
960
65
  };
961
26
  MakeCallback(env()->onpriority_string(), arraysize(argv), argv);
962
13
}
963
964
965
// Called by OnFrameReceived when a complete DATA frame has been received.
966
// If we know that this is the last DATA frame (because the END_STREAM flag
967
// is set), then we'll terminate the readable side of the StreamBase. If
968
// the StreamBase is flowing, we'll push the chunks of data out to JS land.
969
3066
inline void Http2Session::HandleDataFrame(const nghttp2_frame* frame) {
970
3066
  int32_t id = GetFrameID(frame);
971
  DEBUG_HTTP2SESSION2(this, "handling data frame for stream %d", id);
972
3066
  Http2Stream* stream = FindStream(id);
973
974
  // If the stream has already been destroyed, do nothing
975
3066
  if (stream->IsDestroyed())
976
3066
    return;
977
978
3066
  if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
979
446
    stream->AddChunk(nullptr, 0);
980
  }
981
982
3066
  if (stream->IsReading())
983
3002
    stream->FlushDataChunks();
984
}
985
986
987
// Called by OnFrameReceived when a complete GOAWAY frame has been received.
988
11
inline void Http2Session::HandleGoawayFrame(const nghttp2_frame* frame) {
989
11
  Isolate* isolate = env()->isolate();
990
11
  HandleScope scope(isolate);
991
11
  Local<Context> context = env()->context();
992
  Context::Scope context_scope(context);
993
994
11
  nghttp2_goaway goaway_frame = frame->goaway;
995
  DEBUG_HTTP2SESSION(this, "handling goaway frame");
996
997
  Local<Value> argv[3] = {
998
    Integer::NewFromUnsigned(isolate, goaway_frame.error_code),
999
    Integer::New(isolate, goaway_frame.last_stream_id),
1000
    Undefined(isolate)
1001
44
  };
1002
1003
11
  size_t length = goaway_frame.opaque_data_len;
1004
11
  if (length > 0) {
1005
    argv[2] = Buffer::Copy(isolate,
1006
                           reinterpret_cast<char*>(goaway_frame.opaque_data),
1007
2
                           length).ToLocalChecked();
1008
  }
1009
1010
22
  MakeCallback(env()->ongoawaydata_string(), arraysize(argv), argv);
1011
11
}
1012
1013
// Called by OnFrameReceived when a complete PING frame has been received.
1014
6
inline void Http2Session::HandlePingFrame(const nghttp2_frame* frame) {
1015
6
  bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
1016
6
  if (ack) {
1017
3
    Http2Ping* ping = PopPing();
1018
3
    if (ping != nullptr)
1019
3
      ping->Done(true, frame->ping.opaque_data);
1020
  }
1021
6
}
1022
1023
// Called by OnFrameReceived when a complete SETTINGS frame has been received.
1024
753
inline void Http2Session::HandleSettingsFrame(const nghttp2_frame* frame) {
1025
753
  Isolate* isolate = env()->isolate();
1026
753
  HandleScope scope(isolate);
1027
753
  Local<Context> context = env()->context();
1028
  Context::Scope context_scope(context);
1029
1030
753
  bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
1031
1032
1506
  Local<Value> argv[1] = { Boolean::New(isolate, ack) };
1033
1506
  MakeCallback(env()->onsettings_string(), arraysize(argv), argv);
1034
753
}
1035
1036
// If the underlying nghttp2_session struct has data pending in its outbound
1037
// queue, MaybeScheduleWrite will schedule a SendPendingData() call to occcur
1038
// on the next iteration of the Node.js event loop (using the SetImmediate
1039
// queue), but only if a write has not already been scheduled.
1040
2745
void Http2Session::MaybeScheduleWrite() {
1041
2745
  CHECK_EQ(flags_ & SESSION_STATE_WRITE_SCHEDULED, 0);
1042

2745
  if (session_ != nullptr && nghttp2_session_want_write(session_)) {
1043
954
    flags_ |= SESSION_STATE_WRITE_SCHEDULED;
1044
2862
    env()->SetImmediate([](Environment* env, void* data) {
1045
954
      Http2Session* session = static_cast<Http2Session*>(data);
1046

1908
      if (session->session_ == nullptr ||
1047
954
          !(session->flags_ & SESSION_STATE_WRITE_SCHEDULED)) {
1048
        // This can happen e.g. when a stream was reset before this turn
1049
        // of the event loop, in which case SendPendingData() is called early,
1050
        // or the session was destroyed in the meantime.
1051
993
        return;
1052
      }
1053
1054
      // Sending data may call arbitrary JS code, so keep track of
1055
      // async context.
1056
915
      InternalCallbackScope callback_scope(session);
1057
915
      session->SendPendingData();
1058
3777
    }, static_cast<void*>(this), object());
1059
  }
1060
2745
}
1061
1062
// Prompts nghttp2 to begin serializing it's pending data and pushes each
1063
// chunk out to the i/o socket to be sent. This is a particularly hot method
1064
// that will generally be called at least twice be event loop iteration.
1065
// This is a potential performance optimization target later.
1066
2559
void Http2Session::SendPendingData() {
1067
  DEBUG_HTTP2SESSION(this, "sending pending data");
1068
  // Do not attempt to send data on the socket if the destroying flag has
1069
  // been set. That means everything is shutting down and the socket
1070
  // will not be usable.
1071
2559
  if (IsDestroyed())
1072
819
    return;
1073
2151
  flags_ &= ~SESSION_STATE_WRITE_SCHEDULED;
1074
1075
  // SendPendingData should not be called recursively.
1076
2151
  if (flags_ & SESSION_STATE_SENDING)
1077
3
    return;
1078
2148
  flags_ |= SESSION_STATE_SENDING;
1079
1080
2148
  WriteWrap* req = nullptr;
1081
2148
  char* dest = nullptr;
1082
2148
  size_t destRemaining = 0;
1083
2148
  size_t destLength = 0;             // amount of data stored in dest
1084
2148
  size_t destOffset = 0;             // current write offset of dest
1085
1086
  const uint8_t* src;                // pointer to the serialized data
1087
2148
  ssize_t srcLength = 0;             // length of serialized data chunk
1088
1089
  // While srcLength is greater than zero
1090
9783
  while ((srcLength = nghttp2_session_mem_send(session_, &src)) > 0) {
1091
5487
    if (req == nullptr) {
1092
1453
      req = AllocateSend();
1093
1453
      destRemaining = req->ExtraSize();
1094
1453
      dest = req->Extra();
1095
    }
1096
    DEBUG_HTTP2SESSION2(this, "nghttp2 has %d bytes to send", srcLength);
1097
5487
    size_t srcRemaining = srcLength;
1098
5487
    size_t srcOffset = 0;
1099
1100
    // The amount of data we have to copy is greater than the space
1101
    // remaining. Copy what we can into the remaining space, send it,
1102
    // the proceed with the rest.
1103
11191
    while (srcRemaining > destRemaining) {
1104
      DEBUG_HTTP2SESSION2(this, "pushing %d bytes to the socket",
1105
                          destLength + destRemaining);
1106
217
      memcpy(dest + destOffset, src + srcOffset, destRemaining);
1107
217
      destLength += destRemaining;
1108
217
      Send(req, dest, destLength);
1109
217
      destOffset = 0;
1110
217
      destLength = 0;
1111
217
      srcRemaining -= destRemaining;
1112
217
      srcOffset += destRemaining;
1113
217
      req = AllocateSend();
1114
217
      destRemaining = req->ExtraSize();
1115
217
      dest = req->Extra();
1116
    }
1117
1118
5487
    if (srcRemaining > 0) {
1119
5487
      memcpy(dest + destOffset, src + srcOffset, srcRemaining);
1120
5487
      destLength += srcRemaining;
1121
5487
      destOffset += srcRemaining;
1122
5487
      destRemaining -= srcRemaining;
1123
5487
      srcRemaining = 0;
1124
5487
      srcOffset = 0;
1125
    }
1126
  }
1127
2148
  CHECK_NE(srcLength, NGHTTP2_ERR_NOMEM);
1128

2148
  if (destLength > 0 && srcLength >= 0) {
1129
    DEBUG_HTTP2SESSION2(this, "pushing %d bytes to the socket", destLength);
1130
1453
    Send(req, dest, destLength);
1131
  }
1132
  DEBUG_HTTP2SESSION2(this, "wants data in return? %d",
1133
                      nghttp2_session_want_read(session_));
1134
1135
2148
  flags_ &= ~SESSION_STATE_SENDING;
1136
}
1137
1138
// Creates a new Http2Stream and submits a new http2 request.
1139
457
inline Http2Stream* Http2Session::SubmitRequest(
1140
    nghttp2_priority_spec* prispec,
1141
    nghttp2_nv* nva,
1142
    size_t len,
1143
    int32_t* ret,
1144
    int options) {
1145
  DEBUG_HTTP2SESSION(this, "submitting request");
1146
457
  Http2Scope h2scope(this);
1147
457
  Http2Stream* stream = nullptr;
1148
914
  Http2Stream::Provider::Stream prov(options);
1149
457
  *ret = nghttp2_submit_request(session_, prispec, nva, len, *prov, nullptr);
1150
457
  CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
1151
457
  if (*ret > 0)
1152
456
    stream = new Http2Stream(this, *ret, NGHTTP2_HCAT_HEADERS, options);
1153
914
  return stream;
1154
}
1155
1156
2180
inline void Http2Session::SetChunksSinceLastWrite(size_t n) {
1157
2180
  chunks_sent_since_last_write_ = n;
1158
2180
}
1159
1160
// Allocates the data buffer used to pass outbound data to the i/o stream.
1161
1670
WriteWrap* Http2Session::AllocateSend() {
1162
1670
  HandleScope scope(env()->isolate());
1163
  Local<Object> obj =
1164
1670
      env()->write_wrap_constructor_function()
1165
6680
          ->NewInstance(env()->context()).ToLocalChecked();
1166
  // Base the amount allocated on the remote peers max frame size
1167
  uint32_t size =
1168
      nghttp2_session_get_remote_settings(
1169
          session(),
1170
1670
          NGHTTP2_SETTINGS_MAX_FRAME_SIZE);
1171
  // Max frame size + 9 bytes for the header
1172
1670
  return WriteWrap::New(env(), obj, stream_, size + 9);
1173
}
1174
1175
// Pushes chunks of data to the i/o stream.
1176
1670
void Http2Session::Send(WriteWrap* req, char* buf, size_t length) {
1177
  DEBUG_HTTP2SESSION2(this, "attempting to send %d bytes", length);
1178
1670
  if (stream_ == nullptr)
1179
1681
    return;
1180
1659
  chunks_sent_since_last_write_++;
1181
1659
  uv_buf_t actual = uv_buf_init(buf, length);
1182
1659
  if (stream_->DoWrite(req, &actual, 1, nullptr)) {
1183
6
    req->Dispose();
1184
  }
1185
}
1186
1187
// Allocates the data buffer used to receive inbound data from the i/o stream
1188
1954
void Http2Session::OnStreamAllocImpl(size_t suggested_size,
1189
                                     uv_buf_t* buf,
1190
                                     void* ctx) {
1191
1954
  Http2Session* session = static_cast<Http2Session*>(ctx);
1192
1954
  buf->base = session->stream_alloc();
1193
1954
  buf->len = kAllocBufferSize;
1194
1954
}
1195
1196
// Callback used to receive inbound data from the i/o stream
1197
1971
void Http2Session::OnStreamReadImpl(ssize_t nread,
1198
                                    const uv_buf_t* bufs,
1199
                                    uv_handle_type pending,
1200
                                    void* ctx) {
1201
1971
  Http2Session* session = static_cast<Http2Session*>(ctx);
1202
1971
  Http2Scope h2scope(session);
1203
  DEBUG_HTTP2SESSION2(session, "receiving %d bytes", nread);
1204
1971
  if (nread < 0) {
1205
    uv_buf_t tmp_buf;
1206
212
    tmp_buf.base = nullptr;
1207
212
    tmp_buf.len = 0;
1208
    session->prev_read_cb_.fn(nread,
1209
                              &tmp_buf,
1210
                              pending,
1211
212
                              session->prev_read_cb_.ctx);
1212
2183
    return;
1213
  }
1214
1759
  if (bufs->len > 0) {
1215
    // Only pass data on if nread > 0
1216
1759
    uv_buf_t buf[] { uv_buf_init((*bufs).base, nread) };
1217
1759
    ssize_t ret = session->Write(buf, 1);
1218
1219
    // Note: if ssize_t is not defined (e.g. on Win32), nghttp2 will typedef
1220
    // ssize_t to int. Cast here so that the < 0 check actually works on
1221
    // Windows.
1222
1759
    if (static_cast<int>(ret) < 0) {
1223
      DEBUG_HTTP2SESSION2(session, "fatal error receiving data: %d", ret);
1224
2
      Environment* env = session->env();
1225
2
      Isolate* isolate = env->isolate();
1226
2
      HandleScope scope(isolate);
1227
2
      Local<Context> context = env->context();
1228
      Context::Scope context_scope(context);
1229
1230
      Local<Value> argv[1] = {
1231
        Integer::New(isolate, ret),
1232
4
      };
1233
4
      session->MakeCallback(env->error_string(), arraysize(argv), argv);
1234
    } else {
1235
      DEBUG_HTTP2SESSION2(session, "processed %d bytes. wants more? %d", ret,
1236
                          nghttp2_session_want_read(**session));
1237
    }
1238
1759
  }
1239
}
1240
1241
24
void Http2Session::OnStreamDestructImpl(void* ctx) {
1242
24
  Http2Session* session = static_cast<Http2Session*>(ctx);
1243
24
  session->stream_ = nullptr;
1244
24
}
1245
1246
// Every Http2Session session is tightly bound to a single i/o StreamBase
1247
// (typically a net.Socket or tls.TLSSocket). The lifecycle of the two is
1248
// tightly coupled with all data transfer between the two happening at the
1249
// C++ layer via the StreamBase API.
1250
426
void Http2Session::Consume(Local<External> external) {
1251
426
  StreamBase* stream = static_cast<StreamBase*>(external->Value());
1252
426
  stream->Consume();
1253
426
  stream_ = stream;
1254
426
  prev_alloc_cb_ = stream->alloc_cb();
1255
426
  prev_read_cb_ = stream->read_cb();
1256
426
  stream->set_alloc_cb({ Http2Session::OnStreamAllocImpl, this });
1257
426
  stream->set_read_cb({ Http2Session::OnStreamReadImpl, this });
1258
426
  stream->set_destruct_cb({ Http2Session::OnStreamDestructImpl, this });
1259
  DEBUG_HTTP2SESSION(this, "i/o stream consumed");
1260
426
}
1261
1262
1263
926
Http2Stream::Http2Stream(
1264
    Http2Session* session,
1265
    int32_t id,
1266
    nghttp2_headers_category category,
1267
    int options) : AsyncWrap(session->env(),
1268
926
                             session->env()->http2stream_constructor_template()
1269
3704
                                 ->NewInstance(session->env()->context())
1270
                                     .ToLocalChecked(),
1271
                             AsyncWrap::PROVIDER_HTTP2STREAM),
1272
                   StreamBase(session->env()),
1273
                   session_(session),
1274
                   id_(id),
1275
2778
                   current_headers_category_(category) {
1276
926
  MakeWeak<Http2Stream>(this);
1277
1278
  // Limit the number of header pairs
1279
926
  max_header_pairs_ = session->GetMaxHeaderPairs();
1280
926
  if (max_header_pairs_ == 0)
1281
  max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
1282
926
  current_headers_.reserve(max_header_pairs_);
1283
1284
  // Limit the number of header octets
1285
  max_header_length_ =
1286
      std::min(
1287
        nghttp2_session_get_local_settings(
1288
          session->session(),
1289
926
          NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE),
1290
1852
      MAX_MAX_HEADER_LIST_SIZE);
1291
1292
926
  if (options & STREAM_OPTION_GET_TRAILERS)
1293
2
    flags_ |= NGHTTP2_STREAM_FLAG_TRAILERS;
1294
1295
926
  if (options & STREAM_OPTION_EMPTY_PAYLOAD)
1296
302
    Shutdown();
1297
926
  session->AddStream(this);
1298
926
}
1299
1300
1301
2772
Http2Stream::~Http2Stream() {
1302
924
  if (session_ != nullptr) {
1303
924
    session_->RemoveStream(id_);
1304
924
    session_ = nullptr;
1305
  }
1306
1307
1848
  if (!object().IsEmpty())
1308
924
    ClearWrap(object());
1309
924
  persistent().Reset();
1310
1848
  CHECK(persistent().IsEmpty());
1311
1848
}
1312
1313
// Notify the Http2Stream that a new block of HEADERS is being processed.
1314
394
void Http2Stream::StartHeaders(nghttp2_headers_category category) {
1315
  DEBUG_HTTP2STREAM2(this, "starting headers, category: %d", id_, category);
1316
394
  CHECK(!this->IsDestroyed());
1317
394
  current_headers_length_ = 0;
1318
394
  current_headers_.clear();
1319
394
  current_headers_category_ = category;
1320
394
}
1321
1322
1323
11
nghttp2_stream* Http2Stream::operator*() {
1324
11
  return nghttp2_session_find_stream(**session_, id_);
1325
}
1326
1327
1328
// Calls out to JavaScript land to fetch the actual trailer headers to send
1329
// for this stream.
1330
21
void Http2Stream::OnTrailers(const SubmitTrailers& submit_trailers) {
1331
  DEBUG_HTTP2STREAM(this, "prompting for trailers");
1332
21
  CHECK(!this->IsDestroyed());
1333
21
  Isolate* isolate = env()->isolate();
1334
21
  HandleScope scope(isolate);
1335
21
  Local<Context> context = env()->context();
1336
  Context::Scope context_scope(context);
1337
1338
  Local<Value> ret =
1339
42
      MakeCallback(env()->ontrailers_string(), 0, nullptr).ToLocalChecked();
1340

21
  if (!ret.IsEmpty() && !IsDestroyed()) {
1341
20
    if (ret->IsArray()) {
1342
20
      Local<Array> headers = ret.As<Array>();
1343
20
      if (headers->Length() > 0) {
1344
19
        Headers trailers(isolate, context, headers);
1345
19
        submit_trailers.Submit(*trailers, trailers.length());
1346
      }
1347
    }
1348
21
  }
1349
21
}
1350
1351
849
inline bool Http2Stream::HasDataChunks(bool ignore_eos) {
1352
849
  return data_chunks_.size() > (ignore_eos ? 1 : 0);
1353
}
1354
1355
// Appends a chunk of received DATA frame data to this Http2Streams internal
1356
// queue. Note that we must memcpy each chunk because of the way that nghttp2
1357
// handles it's internal memory`.
1358
4522
inline void Http2Stream::AddChunk(const uint8_t* data, size_t len) {
1359
4522
  CHECK(!this->IsDestroyed());
1360
4522
  if (flags_ & NGHTTP2_STREAM_FLAG_EOS)
1361
4959
    return;
1362
4085
  char* buf = nullptr;
1363

4085
  if (len > 0 && data != nullptr) {
1364
3191
    buf = Malloc<char>(len);
1365
3191
    memcpy(buf, data, len);
1366
894
  } else if (data == nullptr) {
1367
894
    flags_ |= NGHTTP2_STREAM_FLAG_EOS;
1368
  }
1369
4085
  data_chunks_.emplace(uv_buf_init(buf, len));
1370
}
1371
1372
// The Http2Stream class is a subclass of StreamBase. The DoWrite method
1373
// receives outbound chunks of data to send as outbound DATA frames. These
1374
// are queued in an internal linked list of uv_buf_t structs that are sent
1375
// when nghttp2 is ready to serialize the data frame.
1376
2170
int Http2Stream::DoWrite(WriteWrap* req_wrap,
1377
                         uv_buf_t* bufs,
1378
                         size_t count,
1379
                         uv_stream_t* send_handle) {
1380
2170
  CHECK(!this->IsDestroyed());
1381
2170
  session_->SetChunksSinceLastWrite();
1382
1383
2170
  nghttp2_stream_write_t* req = new nghttp2_stream_write_t;
1384
2170
  req->data = req_wrap;
1385
1386
6510
  auto AfterWrite = [](nghttp2_stream_write_t* req, int status) {
1387
2170
    WriteWrap* wrap = static_cast<WriteWrap*>(req->data);
1388
2170
    wrap->Done(status);
1389
2170
    delete req;
1390
6510
  };
1391
2170
  req_wrap->Dispatched();
1392
2170
  Write(req, bufs, count, AfterWrite);
1393
2170
  return 0;
1394
}
1395
1396
1397
885
inline void Http2Stream::Close(int32_t code) {
1398
885
  CHECK(!this->IsDestroyed());
1399
885
  flags_ |= NGHTTP2_STREAM_FLAG_CLOSED;
1400
885
  code_ = code;
1401
  DEBUG_HTTP2STREAM2(this, "closed with code %d", code);
1402
885
}
1403
1404
1405
1190
inline void Http2Stream::Shutdown() {
1406
1190
  CHECK(!this->IsDestroyed());
1407
1190
  Http2Scope h2scope(this);
1408
1190
  flags_ |= NGHTTP2_STREAM_FLAG_SHUT;
1409
1190
  CHECK_NE(nghttp2_session_resume_data(session_->session(), id_),
1410
           NGHTTP2_ERR_NOMEM);
1411
1190
  DEBUG_HTTP2STREAM(this, "writable side shutdown");
1412
1190
}
1413
1414
888
int Http2Stream::DoShutdown(ShutdownWrap* req_wrap) {
1415
888
  CHECK(!this->IsDestroyed());
1416
888
  req_wrap->Dispatched();
1417
888
  Shutdown();
1418
888
  req_wrap->Done(0);
1419
888
  return 0;
1420
}
1421
1422
// Destroy the Http2Stream and render it unusable. Actual resources for the
1423
// Stream will not be freed until the next tick of the Node.js event loop
1424
// using the SetImmediate queue.
1425
924
inline void Http2Stream::Destroy() {
1426
  // Do nothing if this stream instance is already destroyed
1427
924
  if (IsDestroyed())
1428
924
    return;
1429
924
  flags_ |= NGHTTP2_STREAM_FLAG_DESTROYED;
1430
1431
  DEBUG_HTTP2STREAM(this, "destroying stream");
1432
1433
  // Free any remaining incoming data chunks.
1434
2323
  while (!data_chunks_.empty()) {
1435
475
    uv_buf_t buf = data_chunks_.front();
1436
475
    free(buf.base);
1437
475
    data_chunks_.pop();
1438
  }
1439
1440
  // Wait until the start of the next loop to delete because there
1441
  // may still be some pending operations queued for this stream.
1442
2772
  env()->SetImmediate([](Environment* env, void* data) {
1443
924
    Http2Stream* stream = static_cast<Http2Stream*>(data);
1444
1445
    // Free any remaining outgoing data chunks here. This should be done
1446
    // here because it's possible for destroy to have been called while
1447
    // we still have qeueued outbound writes.
1448
1850
    while (!stream->queue_.empty()) {
1449
2
      nghttp2_stream_write* head = stream->queue_.front();
1450
2
      head->cb(head->req, UV_ECANCELED);
1451
2
      delete head;
1452
2
      stream->queue_.pop();
1453
    }
1454
1455
924
    delete stream;
1456
3696
  }, this, this->object());
1457
}
1458
1459
1460
// Uses the StreamBase API to push a single chunk of queued inbound DATA
1461
// to JS land.
1462
3606
void Http2Stream::OnDataChunk(uv_buf_t* chunk) {
1463
3606
  CHECK(!this->IsDestroyed());
1464
3606
  Isolate* isolate = env()->isolate();
1465
3606
  HandleScope scope(isolate);
1466
3606
  ssize_t len = -1;
1467
  Local<Object> buf;
1468
3606
  if (chunk != nullptr) {
1469
3184
    len = chunk->len;
1470
6368
    buf = Buffer::New(isolate, chunk->base, len).ToLocalChecked();
1471
  }
1472
3606
  EmitData(len, buf, this->object());
1473
3606
}
1474
1475
1476
6917
inline void Http2Stream::FlushDataChunks() {
1477
6917
  CHECK(!this->IsDestroyed());
1478
6917
  Http2Scope h2scope(this);
1479
6917
  if (!data_chunks_.empty()) {
1480
3606
    uv_buf_t buf = data_chunks_.front();
1481
3606
    data_chunks_.pop();
1482
3606
    if (buf.len > 0) {
1483
3184
      CHECK_EQ(nghttp2_session_consume_stream(session_->session(),
1484
                                              id_, buf.len), 0);
1485
3184
      OnDataChunk(&buf);
1486
    } else {
1487
422
      OnDataChunk(nullptr);
1488
    }
1489
6917
  }
1490
6917
}
1491
1492
1493
// Initiates a response on the Http2Stream using data provided via the
1494
// StreamBase Streams API.
1495
381
inline int Http2Stream::SubmitResponse(nghttp2_nv* nva,
1496
                                       size_t len,
1497
                                       int options) {
1498
381
  CHECK(!this->IsDestroyed());
1499
381
  Http2Scope h2scope(this);
1500
  DEBUG_HTTP2STREAM(this, "submitting response");
1501
381
  if (options & STREAM_OPTION_GET_TRAILERS)
1502
75
    flags_ |= NGHTTP2_STREAM_FLAG_TRAILERS;
1503
1504
381
  if (!IsWritable())
1505
69
    options |= STREAM_OPTION_EMPTY_PAYLOAD;
1506
1507
762
  Http2Stream::Provider::Stream prov(this, options);
1508
381
  int ret = nghttp2_submit_response(session_->session(), id_, nva, len, *prov);
1509
381
  CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
1510
762
  return ret;
1511
}
1512
1513
1514
// Initiate a response that contains data read from a file descriptor.
1515
10
inline int Http2Stream::SubmitFile(int fd,
1516
                                   nghttp2_nv* nva, size_t len,
1517
                                   int64_t offset,
1518
                                   int64_t length,
1519
                                   int options) {
1520
10
  CHECK(!this->IsDestroyed());
1521
10
  Http2Scope h2scope(this);
1522
  DEBUG_HTTP2STREAM(this, "submitting file");
1523
10
  if (options & STREAM_OPTION_GET_TRAILERS)
1524
    flags_ |= NGHTTP2_STREAM_FLAG_TRAILERS;
1525
1526
10
  if (offset > 0) fd_offset_ = offset;
1527
10
  if (length > -1) fd_length_ = length;
1528
1529
20
  Http2Stream::Provider::FD prov(this, options, fd);
1530
10
  int ret = nghttp2_submit_response(session_->session(), id_, nva, len, *prov);
1531
10
  CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
1532
20
  return ret;
1533
}
1534
1535
1536
// Submit informational headers for a stream.
1537
4
inline int Http2Stream::SubmitInfo(nghttp2_nv* nva, size_t len) {
1538
4
  CHECK(!this->IsDestroyed());
1539
4
  Http2Scope h2scope(this);
1540
  DEBUG_HTTP2STREAM2(this, "sending %d informational headers", len);
1541
  int ret = nghttp2_submit_headers(session_->session(),
1542
                                   NGHTTP2_FLAG_NONE,
1543
                                   id_, nullptr,
1544
4
                                   nva, len, nullptr);
1545
4
  CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
1546
4
  return ret;
1547
}
1548
1549
// Submit a PRIORITY frame to the connected peer.
1550
3
inline int Http2Stream::SubmitPriority(nghttp2_priority_spec* prispec,
1551
                                       bool silent) {
1552
3
  CHECK(!this->IsDestroyed());
1553
3
  Http2Scope h2scope(this);
1554
  DEBUG_HTTP2STREAM(this, "sending priority spec");
1555
  int ret = silent ?
1556
      nghttp2_session_change_stream_priority(session_->session(),
1557
                                             id_, prispec) :
1558
      nghttp2_submit_priority(session_->session(),
1559
                              NGHTTP2_FLAG_NONE,
1560
3
                              id_, prispec);
1561
3
  CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
1562
3
  return ret;
1563
}
1564
1565
// Closes the Http2Stream by submitting an RST_STREAM frame to the connected
1566
// peer.
1567
62
inline void Http2Stream::SubmitRstStream(const uint32_t code) {
1568
62
  CHECK(!this->IsDestroyed());
1569
62
  Http2Scope h2scope(this);
1570
  // Force a purge of any currently pending data here to make sure
1571
  // it is sent before closing the stream.
1572
62
  session_->SendPendingData();
1573
62
  CHECK_EQ(nghttp2_submit_rst_stream(**session_, NGHTTP2_FLAG_NONE,
1574
62
                                     id_, code), 0);
1575
62
}
1576
1577
1578
// Submit a push promise and create the associated Http2Stream if successful.
1579
8
inline Http2Stream* Http2Stream::SubmitPushPromise(nghttp2_nv* nva,
1580
                                                   size_t len,
1581
                                                   int32_t* ret,
1582
                                                   int options) {
1583
8
  CHECK(!this->IsDestroyed());
1584
8
  Http2Scope h2scope(this);
1585
  DEBUG_HTTP2STREAM(this, "sending push promise");
1586
  *ret = nghttp2_submit_push_promise(**session_, NGHTTP2_FLAG_NONE,
1587
8
                                     id_, nva, len, nullptr);
1588
8
  CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
1589
8
  Http2Stream* stream = nullptr;
1590
8
  if (*ret > 0)
1591
8
    stream = new Http2Stream(session_, *ret, NGHTTP2_HCAT_HEADERS, options);
1592
1593
8
  return stream;
1594
}
1595
1596
// Switch the StreamBase into flowing mode to begin pushing chunks of data
1597
// out to JS land.
1598
3915
inline int Http2Stream::ReadStart() {
1599
3915
  CHECK(!this->IsDestroyed());
1600
3915
  flags_ |= NGHTTP2_STREAM_FLAG_READ_START;
1601
3915
  flags_ &= ~NGHTTP2_STREAM_FLAG_READ_PAUSED;
1602
1603
  // Flush any queued data chunks immediately out to the JS layer
1604
3915
  FlushDataChunks();
1605
  DEBUG_HTTP2STREAM(this, "reading starting");
1606
3915
  return 0;
1607
}
1608
1609
// Switch the StreamBase into paused mode.
1610
266
inline int Http2Stream::ReadStop() {
1611
266
  CHECK(!this->IsDestroyed());
1612
266
  if (!IsReading())
1613
    return 0;
1614
266
  flags_ |= NGHTTP2_STREAM_FLAG_READ_PAUSED;
1615
  DEBUG_HTTP2STREAM(this, "reading stopped");
1616
266
  return 0;
1617
}
1618
1619
// Queue the given set of uv_but_t handles for writing to an
1620
// nghttp2_stream. The callback will be invoked once the chunks
1621
// of data have been flushed to the underlying nghttp2_session.
1622
// Note that this does *not* mean that the data has been flushed
1623
// to the socket yet.
1624
2170
inline int Http2Stream::Write(nghttp2_stream_write_t* req,
1625
                              const uv_buf_t bufs[],
1626
                              unsigned int nbufs,
1627
                              nghttp2_stream_write_cb cb) {
1628
2170
  CHECK(!this->IsDestroyed());
1629
2170
  Http2Scope h2scope(this);
1630
2170
  if (!IsWritable()) {
1631
    if (cb != nullptr)
1632
      cb(req, UV_EOF);
1633
    return 0;
1634
  }
1635
  DEBUG_HTTP2STREAM2(this, "queuing %d buffers to send", id_, nbufs);
1636
2170
  nghttp2_stream_write* item = new nghttp2_stream_write;
1637
2170
  item->cb = cb;
1638
2170
  item->req = req;
1639
2170
  item->nbufs = nbufs;
1640
2170
  item->bufs.AllocateSufficientStorage(nbufs);
1641
2170
  memcpy(*(item->bufs), bufs, nbufs * sizeof(*bufs));
1642
2170
  queue_.push(item);
1643
2170
  CHECK_NE(nghttp2_session_resume_data(**session_, id_), NGHTTP2_ERR_NOMEM);
1644
2170
  return 0;
1645
}
1646
1647
5654
inline size_t GetBufferLength(nghttp2_rcbuf* buf) {
1648
5654
  return nghttp2_rcbuf_get_buf(buf).len;
1649
}
1650
1651
// Ads a header to the Http2Stream. Note that the header name and value are
1652
// provided using a buffer structure provided by nghttp2 that allows us to
1653
// avoid unnecessary memcpy's. Those buffers are ref counted. The ref count
1654
// is incremented here and are decremented when the header name and values
1655
// are garbage collected later.
1656
2827
inline bool Http2Stream::AddHeader(nghttp2_rcbuf* name,
1657
                                   nghttp2_rcbuf* value,
1658
                                   uint8_t flags) {
1659
2827
  CHECK(!this->IsDestroyed());
1660
2827
  size_t length = GetBufferLength(name) + GetBufferLength(value) + 32;
1661

5653
  if (current_headers_.size() == max_header_pairs_ ||
1662
2826
      current_headers_length_ + length > max_header_length_) {
1663
2
    return false;
1664
  }
1665
2825
  nghttp2_header header;
1666
2825
  header.name = name;
1667
2825
  header.value = value;
1668
2825
  header.flags = flags;
1669
2825
  current_headers_.push_back(header);
1670
2825
  nghttp2_rcbuf_incref(name);
1671
2825
  nghttp2_rcbuf_incref(value);
1672
2825
  current_headers_length_ += length;
1673
2825
  return true;
1674
}
1675
1676
1677
4645
Http2Stream* GetStream(Http2Session* session,
1678
                       int32_t id,
1679
                       nghttp2_data_source* source) {
1680
4645
  Http2Stream* stream = static_cast<Http2Stream*>(source->ptr);
1681
4645
  if (stream == nullptr)
1682
2101
    stream = session->FindStream(id);
1683
4645
  CHECK_NE(stream, nullptr);
1684
4645
  CHECK_EQ(id, stream->id());
1685
4645
  return stream;
1686
}
1687
1688
// A Provider is the thing that provides outbound DATA frame data.
1689
391
Http2Stream::Provider::Provider(Http2Stream* stream, int options) {
1690
391
  CHECK(!stream->IsDestroyed());
1691
391
  provider_.source.ptr = stream;
1692
391
  empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
1693
391
}
1694
1695
457
Http2Stream::Provider::Provider(int options) {
1696
457
  provider_.source.ptr = nullptr;
1697
457
  empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
1698
457
}
1699
1700
848
Http2Stream::Provider::~Provider() {
1701
848
  provider_.source.ptr = nullptr;
1702
848
}
1703
1704
// The FD Provider pulls data from a file descriptor using libuv. All of the
1705
// data transfer occurs in C++, without any chunks being passed through JS
1706
// land.
1707
10
Http2Stream::Provider::FD::FD(Http2Stream* stream, int options, int fd)
1708
10
    : Http2Stream::Provider(stream, options) {
1709
10
  CHECK(!stream->IsDestroyed());
1710
10
  provider_.source.fd = fd;
1711
10
  provider_.read_callback = Http2Stream::Provider::FD::OnRead;
1712
10
}
1713
1714
Http2Stream::Provider::FD::FD(int options, int fd)
1715
    : Http2Stream::Provider(options) {
1716
  provider_.source.fd = fd;
1717
  provider_.read_callback = Http2Stream::Provider::FD::OnRead;
1718
}
1719
1720
202
ssize_t Http2Stream::Provider::FD::OnRead(nghttp2_session* handle,
1721
                                          int32_t id,
1722
                                          uint8_t* buf,
1723
                                          size_t length,
1724
                                          uint32_t* flags,
1725
                                          nghttp2_data_source* source,
1726
                                          void* user_data) {
1727
202
  Http2Session* session = static_cast<Http2Session*>(user_data);
1728
202
  Http2Stream* stream = session->FindStream(id);
1729
1730
  DEBUG_HTTP2SESSION2(session, "reading outbound file data for stream %d", id);
1731
202
  CHECK_EQ(id, stream->id());
1732
1733
202
  int fd = source->fd;
1734
202
  int64_t offset = stream->fd_offset_;
1735
202
  ssize_t numchars = 0;
1736
1737

215
  if (stream->fd_length_ >= 0 &&
1738
13
      stream->fd_length_ < static_cast<int64_t>(length))
1739
11
    length = stream->fd_length_;
1740
1741
  uv_buf_t data;
1742
202
  data.base = reinterpret_cast<char*>(buf);
1743
202
  data.len = length;
1744
1745
  uv_fs_t read_req;
1746
1747
202
  if (length > 0) {
1748
    // TODO(addaleax): Never use synchronous I/O on the main thread.
1749
    numchars = uv_fs_read(session->event_loop(),
1750
                          &read_req,
1751
                          fd, &data, 1,
1752
197
                          offset, nullptr);
1753
197
    uv_fs_req_cleanup(&read_req);
1754
  }
1755
1756
  // Close the stream with an error if reading fails
1757
202
  if (numchars < 0)
1758
1
    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1759
1760
  // Update the read offset for the next read
1761
201
  stream->fd_offset_ += numchars;
1762
201
  stream->fd_length_ -= numchars;
1763
1764
  DEBUG_HTTP2SESSION2(session, "sending %d bytes", numchars);
1765
1766
  // if numchars < length, assume that we are done.
1767

201
  if (static_cast<size_t>(numchars) < length || length <= 0) {
1768
    DEBUG_HTTP2SESSION2(session, "no more data for stream %d", id);
1769
9
    *flags |= NGHTTP2_DATA_FLAG_EOF;
1770
9
    session->GetTrailers(stream, flags);
1771
    // If the stream or session gets destroyed during the GetTrailers
1772
    // callback, check that here and close down the stream
1773
9
    if (stream->IsDestroyed())
1774
      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1775
9
    if (session->IsDestroyed())
1776
      return NGHTTP2_ERR_CALLBACK_FAILURE;
1777
  }
1778
1779
201
  return numchars;
1780
}
1781
1782
// The Stream Provider pulls data from a linked list of uv_buf_t structs
1783
// built via the StreamBase API and the Streams js API.
1784
457
Http2Stream::Provider::Stream::Stream(int options)
1785
457
    : Http2Stream::Provider(options) {
1786
457
  provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
1787
457
}
1788
1789
381
Http2Stream::Provider::Stream::Stream(Http2Stream* stream, int options)
1790
381
    : Http2Stream::Provider(stream, options) {
1791
381
  provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
1792
381
}
1793
1794
4645
ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
1795
                                              int32_t id,
1796
                                              uint8_t* buf,
1797
                                              size_t length,
1798
                                              uint32_t* flags,
1799
                                              nghttp2_data_source* source,
1800
                                              void* user_data) {
1801
4645
  Http2Session* session = static_cast<Http2Session*>(user_data);
1802
  DEBUG_HTTP2SESSION2(session, "reading outbound data for stream %d", id);
1803
4645
  Http2Stream* stream = GetStream(session, id, source);
1804
4645
  CHECK_EQ(id, stream->id());
1805
1806
4645
  size_t amount = 0;          // amount of data being sent in this data frame.
1807
1808
  uv_buf_t current;
1809
1810
4645
  if (!stream->queue_.empty()) {
1811
    DEBUG_HTTP2SESSION2(session, "stream %d has pending outbound data", id);
1812
2618
    nghttp2_stream_write* head = stream->queue_.front();
1813
2618
    current = head->bufs[stream->queue_index_];
1814
2618
    size_t clen = current.len - stream->queue_offset_;
1815
2618
    amount = std::min(clen, length);
1816
    DEBUG_HTTP2SESSION2(session, "sending %d bytes for data frame on stream %d",
1817
                        amount, id);
1818
2618
    if (amount > 0) {
1819
2616
      memcpy(buf, current.base + stream->queue_offset_, amount);
1820
2616
      stream->queue_offset_ += amount;
1821
    }
1822
2618
    if (stream->queue_offset_ == current.len) {
1823
2351
      stream->queue_index_++;
1824
2351
      stream->queue_offset_ = 0;
1825
    }
1826
2618
    if (stream->queue_index_ == head->nbufs) {
1827
2168
      head->cb(head->req, 0);
1828
2168
      delete head;
1829
2168
      stream->queue_.pop();
1830
2168
      stream->queue_offset_ = 0;
1831
2168
      stream->queue_index_ = 0;
1832
    }
1833
  }
1834
1835


4645
  if (amount == 0 && stream->IsWritable() && stream->queue_.empty()) {
1836
    DEBUG_HTTP2SESSION2(session, "deferring stream %d", id);
1837
1767
    return NGHTTP2_ERR_DEFERRED;
1838
  }
1839
1840

2878
  if (stream->queue_.empty() && !stream->IsWritable()) {
1841
    DEBUG_HTTP2SESSION2(session, "no more data for stream %d", id);
1842
457
    *flags |= NGHTTP2_DATA_FLAG_EOF;
1843
457
    session->GetTrailers(stream, flags);
1844
    // If the stream or session gets destroyed during the GetTrailers
1845
    // callback, check that here and close down the stream
1846
457
    if (stream->IsDestroyed())
1847
1
      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1848
456
    if (session->IsDestroyed())
1849
      return NGHTTP2_ERR_CALLBACK_FAILURE;
1850
  }
1851
2877
  return amount;
1852
}
1853
1854
1855
1856
// Implementation of the JavaScript API
1857
1858
// Fetches the string description of a nghttp2 error code and passes that
1859
// back to JS land
1860
47
void HttpErrorString(const FunctionCallbackInfo<Value>& args) {
1861
47
  Environment* env = Environment::GetCurrent(args);
1862
188
  uint32_t val = args[0]->Uint32Value(env->context()).ToChecked();
1863
  args.GetReturnValue().Set(
1864
      String::NewFromOneByte(
1865
          env->isolate(),
1866
47
          reinterpret_cast<const uint8_t*>(nghttp2_strerror(val)),
1867
141
          v8::NewStringType::kInternalized).ToLocalChecked());
1868
47
}
1869
1870
1871
// Serializes the settings object into a Buffer instance that
1872
// would be suitable, for instance, for creating the Base64
1873
// output for an HTTP2-Settings header field.
1874
15
void PackSettings(const FunctionCallbackInfo<Value>& args) {
1875
15
  Environment* env = Environment::GetCurrent(args);
1876
15
  Http2Settings settings(env);
1877
45
  args.GetReturnValue().Set(settings.Pack());
1878
15
}
1879
1880
// A TypedArray instance is shared between C++ and JS land to contain the
1881
// default SETTINGS. RefreshDefaultSettings updates that TypedArray with the
1882
// default values.
1883
3
void RefreshDefaultSettings(const FunctionCallbackInfo<Value>& args) {
1884
3
  Environment* env = Environment::GetCurrent(args);
1885
3
  Http2Settings::RefreshDefaults(env);
1886
3
}
1887
1888
// Sets the next stream ID the Http2Session. If successful, returns true.
1889
1
void Http2Session::SetNextStreamID(const FunctionCallbackInfo<Value>& args) {
1890
1
  Environment* env = Environment::GetCurrent(args);
1891
  Http2Session* session;
1892
1
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
1893
4
  int32_t id = args[0]->Int32Value(env->context()).ToChecked();
1894
1
  if (nghttp2_session_set_next_stream_id(**session, id) < 0) {
1895
    DEBUG_HTTP2SESSION2(session, "failed to set next stream id to %d", id);
1896
    return args.GetReturnValue().Set(false);
1897
  }
1898
2
  args.GetReturnValue().Set(true);
1899
  DEBUG_HTTP2SESSION2(session, "set next stream id to %d", id);
1900
}
1901
1902
// A TypedArray instance is shared between C++ and JS land to contain the
1903
// SETTINGS (either remote or local). RefreshSettings updates the current
1904
// values established for each of the settings so those can be read in JS land.
1905
template <get_setting fn>
1906
746
void Http2Session::RefreshSettings(const FunctionCallbackInfo<Value>& args) {
1907
746
  Environment* env = Environment::GetCurrent(args);
1908
  Http2Session* session;
1909

1492
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
1910
746
  Http2Settings::Update(env, session, fn);
1911
  DEBUG_HTTP2SESSION(session, "settings refreshed for session");
1912
}
1913
1914
// A TypedArray instance is shared between C++ and JS land to contain state
1915
// information of the current Http2Session. This updates the values in the
1916
// TypedRray so those can be read in JS land.
1917
3
void Http2Session::RefreshState(const FunctionCallbackInfo<Value>& args) {
1918
3
  Environment* env = Environment::GetCurrent(args);
1919
  Http2Session* session;
1920
6
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
1921
  DEBUG_HTTP2SESSION(session, "refreshing state");
1922
1923
  AliasedBuffer<double, v8::Float64Array>& buffer =
1924
3
      env->http2_state()->session_state_buffer;
1925
1926
3
  nghttp2_session* s = **session;
1927
1928
6
  buffer[IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE] =
1929
6
      nghttp2_session_get_effective_local_window_size(s);
1930
6
  buffer[IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH] =
1931
6
      nghttp2_session_get_effective_recv_data_length(s);
1932
6
  buffer[IDX_SESSION_STATE_NEXT_STREAM_ID] =
1933
6
      nghttp2_session_get_next_stream_id(s);
1934
6
  buffer[IDX_SESSION_STATE_LOCAL_WINDOW_SIZE] =
1935
6
      nghttp2_session_get_local_window_size(s);
1936
6
  buffer[IDX_SESSION_STATE_LAST_PROC_STREAM_ID] =
1937
6
      nghttp2_session_get_last_proc_stream_id(s);
1938
6
  buffer[IDX_SESSION_STATE_REMOTE_WINDOW_SIZE] =
1939
6
      nghttp2_session_get_remote_window_size(s);
1940
6
  buffer[IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE] =
1941
6
      nghttp2_session_get_outbound_queue_size(s);
1942
6
  buffer[IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE] =
1943
6
      nghttp2_session_get_hd_deflate_dynamic_table_size(s);
1944
6
  buffer[IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE] =
1945
6
      nghttp2_session_get_hd_inflate_dynamic_table_size(s);
1946
}
1947
1948
1949
// Constructor for new Http2Session instances.
1950
426
void Http2Session::New(const FunctionCallbackInfo<Value>& args) {
1951
426
  Environment* env = Environment::GetCurrent(args);
1952
426
  CHECK(args.IsConstructCall());
1953
1704
  int val = args[0]->IntegerValue(env->context()).ToChecked();
1954
426
  nghttp2_session_type type = static_cast<nghttp2_session_type>(val);
1955
426
  Http2Session* session = new Http2Session(env, args.This(), type);
1956
426
  session->get_async_id();  // avoid compiler warning
1957
  DEBUG_HTTP2SESSION(session, "session created");
1958
426
}
1959
1960
1961
// Binds the Http2Session with a StreamBase used for i/o
1962
426
void Http2Session::Consume(const FunctionCallbackInfo<Value>& args) {
1963
  Http2Session* session;
1964
852
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
1965
852
  CHECK(args[0]->IsExternal());
1966
852
  session->Consume(args[0].As<External>());
1967
}
1968
1969
// Destroys the Http2Session instance and renders it unusable
1970
419
void Http2Session::Destroy(const FunctionCallbackInfo<Value>& args) {
1971
  Http2Session* session;
1972
838
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
1973
  DEBUG_HTTP2SESSION(session, "destroying session");
1974
419
  Environment* env = Environment::GetCurrent(args);
1975
419
  Local<Context> context = env->context();
1976
1977
1257
  uint32_t code = args[0]->Uint32Value(context).ToChecked();
1978
1257
  bool socketDestroyed = args[1]->BooleanValue(context).ToChecked();
1979
1980
419
  session->Close(code, socketDestroyed);
1981
}
1982
1983
// Submits a SETTINGS frame for the Http2Session
1984
433
void Http2Session::Settings(const FunctionCallbackInfo<Value>& args) {
1985
  Http2Session* session;
1986
866
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
1987
433
  Environment* env = session->env();
1988
1989
433
  Http2Settings settings(env);
1990
433
  session->Http2Session::Settings(*settings, settings.length());
1991
433
  DEBUG_HTTP2SESSION(session, "settings submitted");
1992
}
1993
1994
// Submits a new request on the Http2Session and returns either an error code
1995
// or the Http2Stream object.
1996
457
void Http2Session::Request(const FunctionCallbackInfo<Value>& args) {
1997
  Http2Session* session;
1998
458
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
1999
457
  Environment* env = session->env();
2000
457
  Local<Context> context = env->context();
2001
457
  Isolate* isolate = env->isolate();
2002
2003
914
  Local<Array> headers = args[0].As<Array>();
2004
1371
  int options = args[1]->IntegerValue(context).ToChecked();
2005
457
  Http2Priority priority(env, args[2], args[3], args[4]);
2006
2007
457
  Headers list(isolate, context, headers);
2008
2009
  DEBUG_HTTP2SESSION(session, "request submitted");
2010
2011
457
  int32_t ret = 0;
2012
  Http2Stream* stream =
2013
      session->Http2Session::SubmitRequest(*priority, *list, list.length(),
2014
457
                                           &ret, options);
2015
2016
457
  if (ret <= 0) {
2017
    DEBUG_HTTP2SESSION2(session, "could not submit request: %s",
2018
                        nghttp2_strerror(ret));
2019
3
    return args.GetReturnValue().Set(ret);
2020
  }
2021
2022
  DEBUG_HTTP2SESSION2(session, "request submitted, new stream id %d",
2023
                      stream->id());
2024
1368
  args.GetReturnValue().Set(stream->object());
2025
}
2026
2027
// Submits a GOAWAY frame to signal that the Http2Session is in the process
2028
// of shutting down. Note that this function does not actually alter the
2029
// state of the Http2Session, it's simply a notification.
2030
401
void Http2Session::Goaway(uint32_t code,
2031
                          int32_t lastStreamID,
2032
                          uint8_t* data,
2033
                          size_t len) {
2034
401
  if (IsDestroyed())
2035
401
    return;
2036
2037
401
  Http2Scope h2scope(this);
2038
  // the last proc stream id is the most recently created Http2Stream.
2039
401
  if (lastStreamID <= 0)
2040
401
    lastStreamID = nghttp2_session_get_last_proc_stream_id(session_);
2041
  DEBUG_HTTP2SESSION(this, "submitting goaway");
2042
  nghttp2_submit_goaway(session_, NGHTTP2_FLAG_NONE,
2043
401
                        lastStreamID, code, data, len);
2044
}
2045
2046
// Submits a GOAWAY frame to signal that the Http2Session is in the process
2047
// of shutting down. The opaque data argument is an optional TypedArray that
2048
// can be used to send debugging data to the connected peer.
2049
401
void Http2Session::Goaway(const FunctionCallbackInfo<Value>& args) {
2050
401
  Environment* env = Environment::GetCurrent(args);
2051
401
  Local<Context> context = env->context();
2052
  Http2Session* session;
2053
802
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2054
2055
1203
  uint32_t code = args[0]->Uint32Value(context).ToChecked();
2056
1203
  int32_t lastStreamID = args[1]->Int32Value(context).ToChecked();
2057
401
  Local<Value> opaqueData = args[2];
2058
401
  uint8_t* data = nullptr;
2059
401
  size_t length = 0;
2060
2061
401
  if (Buffer::HasInstance(opaqueData)) {
2062
1
    data = reinterpret_cast<uint8_t*>(Buffer::Data(opaqueData));
2063
1
    length = Buffer::Length(opaqueData);
2064
  }
2065
2066
401
  session->Goaway(code, lastStreamID, data, length);
2067
}
2068
2069
// Update accounting of data chunks. This is used primarily to manage timeout
2070
// logic when using the FD Provider.
2071
10
void Http2Session::UpdateChunksSent(const FunctionCallbackInfo<Value>& args) {
2072
10
  Environment* env = Environment::GetCurrent(args);
2073
10
  Isolate* isolate = env->isolate();
2074
10
  HandleScope scope(isolate);
2075
  Http2Session* session;
2076
20
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2077
2078
10
  uint32_t length = session->chunks_sent_since_last_write_;
2079
2080
10
  session->object()->Set(env->context(),
2081
                         env->chunks_sent_since_last_write_string(),
2082
60
                         Integer::NewFromUnsigned(isolate, length)).FromJust();
2083
2084
20
  args.GetReturnValue().Set(length);
2085
}
2086
2087
// Submits an RST_STREAM frame effectively closing the Http2Stream. Note that
2088
// this *WILL* alter the state of the stream, causing the OnStreamClose
2089
// callback to the triggered.
2090
60
void Http2Stream::RstStream(const FunctionCallbackInfo<Value>& args) {
2091
60
  Environment* env = Environment::GetCurrent(args);
2092
60
  Local<Context> context = env->context();
2093
  Http2Stream* stream;
2094
120
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2095
180
  uint32_t code = args[0]->Uint32Value(context).ToChecked();
2096
  DEBUG_HTTP2STREAM2(stream, "sending rst_stream with code %d", code);
2097
60
  stream->SubmitRstStream(code);
2098
}
2099
2100
// Initiates a response on the Http2Stream using the StreamBase API to provide
2101
// outbound DATA frames.
2102
381
void Http2Stream::Respond(const FunctionCallbackInfo<Value>& args) {
2103
381
  Environment* env = Environment::GetCurrent(args);
2104
381
  Local<Context> context = env->context();
2105
381
  Isolate* isolate = env->isolate();
2106
  Http2Stream* stream;
2107
762
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2108
2109
762
  Local<Array> headers = args[0].As<Array>();
2110
1143
  int options = args[1]->IntegerValue(context).ToChecked();
2111
2112
381
  Headers list(isolate, context, headers);
2113
2114
  args.GetReturnValue().Set(
2115
1143
      stream->SubmitResponse(*list, list.length(), options));
2116
381
  DEBUG_HTTP2STREAM(stream, "response submitted");
2117
}
2118
2119
// Initiates a response on the Http2Stream using a file descriptor to provide
2120
// outbound DATA frames.
2121
10
void Http2Stream::RespondFD(const FunctionCallbackInfo<Value>& args) {
2122
10
  Environment* env = Environment::GetCurrent(args);
2123
10
  Local<Context> context = env->context();
2124
10
  Isolate* isolate = env->isolate();
2125
  Http2Stream* stream;
2126
20
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2127
2128
30
  int fd = args[0]->Int32Value(context).ToChecked();
2129
20
  Local<Array> headers = args[1].As<Array>();
2130
2131
30
  int64_t offset = args[2]->IntegerValue(context).ToChecked();
2132
30
  int64_t length = args[3]->IntegerValue(context).ToChecked();
2133
30
  int options = args[4]->IntegerValue(context).ToChecked();
2134
2135
10
  stream->session()->SetChunksSinceLastWrite();
2136
2137
10
  Headers list(isolate, context, headers);
2138
  args.GetReturnValue().Set(stream->SubmitFile(fd, *list, list.length(),
2139
30
                                               offset, length, options));
2140
10
  DEBUG_HTTP2STREAM2(stream, "file response submitted for fd %d", fd);
2141
}
2142
2143
// Submits informational headers on the Http2Stream
2144
4
void Http2Stream::Info(const FunctionCallbackInfo<Value>& args) {
2145
4
  Environment* env = Environment::GetCurrent(args);
2146
4
  Local<Context> context = env->context();
2147
4
  Isolate* isolate = env->isolate();
2148
  Http2Stream* stream;
2149
8
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2150
2151
8
  Local<Array> headers = args[0].As<Array>();
2152
2153
4
  Headers list(isolate, context, headers);
2154
12
  args.GetReturnValue().Set(stream->SubmitInfo(*list, list.length()));
2155
  DEBUG_HTTP2STREAM2(stream, "%d informational headers sent",
2156
4
                     headers->Length());
2157
}
2158
2159
// Grab the numeric id of the Http2Stream
2160
464
void Http2Stream::GetID(const FunctionCallbackInfo<Value>& args) {
2161
  Http2Stream* stream;
2162
928
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2163
1392
  args.GetReturnValue().Set(stream->id());
2164
}
2165
2166
// Destroy the Http2Stream, rendering it no longer usable
2167
888
void Http2Stream::Destroy(const FunctionCallbackInfo<Value>& args) {
2168
  Http2Stream* stream;
2169
1776
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2170
  DEBUG_HTTP2STREAM(stream, "destroying stream");
2171
888
  stream->Destroy();
2172
}
2173
2174
// Prompt the Http2Stream to begin sending data to the JS land.
2175
3695
void Http2Stream::FlushData(const FunctionCallbackInfo<Value>& args) {
2176
  Http2Stream* stream;
2177
7390
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2178
3695
  stream->ReadStart();
2179
  DEBUG_HTTP2STREAM(stream, "data flushed to js");
2180
}
2181
2182
// Initiate a Push Promise and create the associated Http2Stream
2183
8
void Http2Stream::PushPromise(const FunctionCallbackInfo<Value>& args) {
2184
8
  Environment* env = Environment::GetCurrent(args);
2185
8
  Local<Context> context = env->context();
2186
8
  Isolate* isolate = env->isolate();
2187
  Http2Stream* parent;
2188
8
  ASSIGN_OR_RETURN_UNWRAP(&parent, args.Holder());
2189
2190
16
  Local<Array> headers = args[0].As<Array>();
2191
24
  int options = args[1]->IntegerValue(context).ToChecked();
2192
2193
8
  Headers list(isolate, context, headers);
2194
2195
  DEBUG_HTTP2STREAM(parent, "creating push promise");
2196
2197
8
  int32_t ret = 0;
2198
  Http2Stream* stream = parent->SubmitPushPromise(*list, list.length(),
2199
8
                                                  &ret, options);
2200
8
  if (ret <= 0) {
2201
    DEBUG_HTTP2STREAM2(parent, "failed to create push stream: %d", ret);
2202
    return args.GetReturnValue().Set(ret);
2203
  }
2204
  DEBUG_HTTP2STREAM2(parent, "push stream %d created", stream->id());
2205
24
  args.GetReturnValue().Set(stream->object());
2206
}
2207
2208
// Send a PRIORITY frame
2209
3
void Http2Stream::Priority(const FunctionCallbackInfo<Value>& args) {
2210
3
  Environment* env = Environment::GetCurrent(args);
2211
3
  Local<Context> context = env->context();
2212
  Http2Stream* stream;
2213
6
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2214
2215
3
  Http2Priority priority(env, args[0], args[1], args[2]);
2216
9
  bool silent = args[3]->BooleanValue(context).ToChecked();
2217
2218
3
  CHECK_EQ(stream->SubmitPriority(*priority, silent), 0);
2219
  DEBUG_HTTP2STREAM(stream, "priority submitted");
2220
}
2221
2222
// A TypedArray shared by C++ and JS land is used to communicate state
2223
// information about the Http2Stream. This updates the values in that
2224
// TypedArray so that the state can be read by JS.
2225
11
void Http2Stream::RefreshState(const FunctionCallbackInfo<Value>& args) {
2226
11
  Environment* env = Environment::GetCurrent(args);
2227
  Http2Stream* stream;
2228
22
  ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2229
2230
  DEBUG_HTTP2STREAM(stream, "refreshing state");
2231
2232
  AliasedBuffer<double, v8::Float64Array>& buffer =
2233
11
      env->http2_state()->stream_state_buffer;
2234
2235
11
  nghttp2_stream* str = **stream;
2236
11
  nghttp2_session* s = **(stream->session());
2237
2238
11
  if (str == nullptr) {
2239
1
    buffer[IDX_STREAM_STATE] = NGHTTP2_STREAM_STATE_IDLE;
2240
    buffer[IDX_STREAM_STATE_WEIGHT] =
2241
        buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2242
        buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2243
        buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2244
1
        buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] = 0;
2245
  } else {
2246
20
    buffer[IDX_STREAM_STATE] =
2247
20
        nghttp2_stream_get_state(str);
2248
20
    buffer[IDX_STREAM_STATE_WEIGHT] =
2249
20
        nghttp2_stream_get_weight(str);
2250
20
    buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2251
20
        nghttp2_stream_get_sum_dependency_weight(str);
2252
20
    buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2253
20
        nghttp2_session_get_stream_local_close(s, stream->id());
2254
20
    buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2255
20
        nghttp2_session_get_stream_remote_close(s, stream->id());
2256
20
    buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] =
2257
20
        nghttp2_session_get_stream_local_window_size(s, stream->id());
2258
  }
2259
}
2260
2261
// Submits a PING frame to be sent to the connected peer.
2262
4
void Http2Session::Ping(const FunctionCallbackInfo<Value>& args) {
2263
4
  Environment* env = Environment::GetCurrent(args);
2264
  Http2Session* session;
2265
5
  ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2266
2267
  // A PING frame may have exactly 8 bytes of payload data. If not provided,
2268
  // then the current hrtime will be used as the payload.
2269
4
  uint8_t* payload = nullptr;
2270
4
  if (Buffer::HasInstance(args[0])) {
2271
2
    payload = reinterpret_cast<uint8_t*>(Buffer::Data(args[0]));
2272
2
    CHECK_EQ(Buffer::Length(args[0]), 8);
2273
  }
2274
2275
4
  Http2Session::Http2Ping* ping = new Http2Ping(session);
2276
4
  Local<Object> obj = ping->object();
2277
16
  obj->Set(env->context(), env->ondone_string(), args[1]).FromJust();
2278
2279
  // To prevent abuse, we strictly limit the number of unacknowledged PING
2280
  // frames that may be sent at any given time. This is configurable in the
2281
  // Options when creating a Http2Session.
2282
4
  if (!session->AddPing(ping)) {
2283
1
    ping->Done(false);
2284
2
    return args.GetReturnValue().Set(false);
2285
  }
2286
2287
  // The Ping itself is an Async resource. When the acknowledgement is recieved,
2288
  // the callback will be invoked and a notification sent out to JS land. The
2289
  // notification will include the duration of the ping, allowing the round
2290
  // trip to be measured.
2291
3
  ping->Send(payload);
2292
6
  args.GetReturnValue().Set(true);
2293
}
2294
2295
3
Http2Session::Http2Ping* Http2Session::PopPing() {
2296
3
  Http2Ping* ping = nullptr;
2297
3
  if (!outstanding_pings_.empty()) {
2298
3
    ping = outstanding_pings_.front();
2299
3
    outstanding_pings_.pop();
2300
  }
2301
3
  return ping;
2302
}
2303
2304
4
bool Http2Session::AddPing(Http2Session::Http2Ping* ping) {
2305
4
  if (outstanding_pings_.size() == max_outstanding_pings_)
2306
1
    return false;
2307
3
  outstanding_pings_.push(ping);
2308
3
  return true;
2309
}
2310
2311
2312
4
Http2Session::Http2Ping::Http2Ping(
2313
    Http2Session* session)
2314
        : AsyncWrap(session->env(),
2315
4
                    session->env()->http2ping_constructor_template()
2316
16
                        ->NewInstance(session->env()->context())
2317
                            .ToLocalChecked(),
2318
                    AsyncWrap::PROVIDER_HTTP2PING),
2319
          session_(session),
2320
12
          startTime_(uv_hrtime()) { }
2321
2322
12
Http2Session::Http2Ping::~Http2Ping() {
2323
8
  if (!object().IsEmpty())
2324
4
    ClearWrap(object());
2325
4
  persistent().Reset();
2326
8
  CHECK(persistent().IsEmpty());
2327
8
}
2328
2329
3
void Http2Session::Http2Ping::Send(uint8_t* payload) {
2330
  uint8_t data[8];
2331
3
  if (payload == nullptr) {
2332
1
    memcpy(&data, &startTime_, arraysize(data));
2333
1
    payload = data;
2334
  }
2335
3
  Http2Scope h2scope(session_);
2336
3
  CHECK_EQ(nghttp2_submit_ping(**session_, NGHTTP2_FLAG_NONE, payload), 0);
2337
3
}
2338
2339
4
void Http2Session::Http2Ping::Done(bool ack, const uint8_t* payload) {
2340
4
  uint64_t end = uv_hrtime();
2341
4
  double duration = (end - startTime_) / 1e6;
2342
2343
4
  Local<Value> buf = Undefined(env()->isolate());
2344
4
  if (payload != nullptr) {
2345
    buf = Buffer::Copy(env()->isolate(),
2346
                       reinterpret_cast<const char*>(payload),
2347
6
                       8).ToLocalChecked();
2348
  }
2349
2350
  Local<Value> argv[3] = {
2351
    Boolean::New(env()->isolate(), ack),
2352
    Number::New(env()->isolate(), duration),
2353
    buf
2354
12
  };
2355
4
  MakeCallback(env()->ondone_string(), arraysize(argv), argv);
2356
4
  delete this;
2357
4
}
2358
2359
2360
// Set up the process.binding('http2') binding.
2361
151
void Initialize(Local<Object> target,
2362
                Local<Value> unused,
2363
                Local<Context> context,
2364
                void* priv) {
2365
151
  Environment* env = Environment::GetCurrent(context);
2366
151
  Isolate* isolate = env->isolate();
2367
151
  HandleScope scope(isolate);
2368
2369
302
  std::unique_ptr<http2_state> state(new http2_state(isolate));
2370
2371
#define SET_STATE_TYPEDARRAY(name, field)             \
2372
  target->Set(context,                                \
2373
              FIXED_ONE_BYTE_STRING(isolate, (name)), \
2374
              (field)).FromJust()
2375
2376
  // Initialize the buffer used for padding callbacks
2377
604
  SET_STATE_TYPEDARRAY(
2378
    "paddingBuffer", state->padding_buffer.GetJSArray());
2379
  // Initialize the buffer used to store the session state
2380
604
  SET_STATE_TYPEDARRAY(
2381
    "sessionState", state->session_state_buffer.GetJSArray());
2382
  // Initialize the buffer used to store the stream state
2383
604
  SET_STATE_TYPEDARRAY(
2384
    "streamState", state->stream_state_buffer.GetJSArray());
2385
604
  SET_STATE_TYPEDARRAY(
2386
    "settingsBuffer", state->settings_buffer.GetJSArray());
2387
604
  SET_STATE_TYPEDARRAY(
2388
    "optionsBuffer", state->options_buffer.GetJSArray());
2389
#undef SET_STATE_TYPEDARRAY
2390
2391
151
  env->set_http2_state(std::move(state));
2392
2393
453
  NODE_DEFINE_CONSTANT(target, PADDING_BUF_FRAME_LENGTH);
2394
453
  NODE_DEFINE_CONSTANT(target, PADDING_BUF_MAX_PAYLOAD_LENGTH);
2395
453
  NODE_DEFINE_CONSTANT(target, PADDING_BUF_RETURN_VALUE);
2396
2397
  // Method to fetch the nghttp2 string description of an nghttp2 error code
2398
151
  env->SetMethod(target, "nghttp2ErrorString", HttpErrorString);
2399
2400
  Local<String> http2SessionClassName =
2401
151
    FIXED_ONE_BYTE_STRING(isolate, "Http2Session");
2402
2403
151
  Local<FunctionTemplate> ping = FunctionTemplate::New(env->isolate());
2404
302
  ping->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Ping"));
2405
151
  AsyncWrap::AddWrapMethods(env, ping);
2406
151
  Local<ObjectTemplate> pingt = ping->InstanceTemplate();
2407
151
  pingt->SetInternalFieldCount(1);
2408
151
  env->set_http2ping_constructor_template(pingt);
2409
2410
151
  Local<FunctionTemplate> stream = FunctionTemplate::New(env->isolate());
2411
302
  stream->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Stream"));
2412
151
  env->SetProtoMethod(stream, "id", Http2Stream::GetID);
2413
151
  env->SetProtoMethod(stream, "destroy", Http2Stream::Destroy);
2414
151
  env->SetProtoMethod(stream, "flushData", Http2Stream::FlushData);
2415
151
  env->SetProtoMethod(stream, "priority", Http2Stream::Priority);
2416
151
  env->SetProtoMethod(stream, "pushPromise", Http2Stream::PushPromise);
2417
151
  env->SetProtoMethod(stream, "info", Http2Stream::Info);
2418
151
  env->SetProtoMethod(stream, "respondFD", Http2Stream::RespondFD);
2419
151
  env->SetProtoMethod(stream, "respond", Http2Stream::Respond);
2420
151
  env->SetProtoMethod(stream, "rstStream", Http2Stream::RstStream);
2421
151
  env->SetProtoMethod(stream, "refreshState", Http2Stream::RefreshState);
2422
151
  AsyncWrap::AddWrapMethods(env, stream);
2423
151
  StreamBase::AddMethods<Http2Stream>(env, stream, StreamBase::kFlagHasWritev);
2424
151
  Local<ObjectTemplate> streamt = stream->InstanceTemplate();
2425
151
  streamt->SetInternalFieldCount(1);
2426
151
  env->set_http2stream_constructor_template(streamt);
2427
  target->Set(context,
2428
              FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Stream"),
2429
604
              stream->GetFunction()).FromJust();
2430
2431
  Local<FunctionTemplate> session =
2432
151
      env->NewFunctionTemplate(Http2Session::New);
2433
151
  session->SetClassName(http2SessionClassName);
2434
302
  session->InstanceTemplate()->SetInternalFieldCount(1);
2435
151
  AsyncWrap::AddWrapMethods(env, session);
2436
151
  env->SetProtoMethod(session, "ping", Http2Session::Ping);
2437
151
  env->SetProtoMethod(session, "consume", Http2Session::Consume);
2438
151
  env->SetProtoMethod(session, "destroy", Http2Session::Destroy);
2439
151
  env->SetProtoMethod(session, "goaway", Http2Session::Goaway);
2440
151
  env->SetProtoMethod(session, "settings", Http2Session::Settings);
2441
151
  env->SetProtoMethod(session, "request", Http2Session::Request);
2442
  env->SetProtoMethod(session, "setNextStreamID",
2443
151
                      Http2Session::SetNextStreamID);
2444
  env->SetProtoMethod(session, "updateChunksSent",
2445
151
                      Http2Session::UpdateChunksSent);
2446
151
  env->SetProtoMethod(session, "refreshState", Http2Session::RefreshState);
2447
  env->SetProtoMethod(
2448
      session, "localSettings",
2449
151
      Http2Session::RefreshSettings<nghttp2_session_get_local_settings>);
2450
  env->SetProtoMethod(
2451
      session, "remoteSettings",
2452
151
      Http2Session::RefreshSettings<nghttp2_session_get_remote_settings>);
2453
  target->Set(context,
2454
              http2SessionClassName,
2455
453
              session->GetFunction()).FromJust();
2456
2457
151
  Local<Object> constants = Object::New(isolate);
2458
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SESSION_SERVER);
2459
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SESSION_CLIENT);
2460
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_STATE_IDLE);
2461
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_STATE_OPEN);
2462
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_STATE_RESERVED_LOCAL);
2463
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_STATE_RESERVED_REMOTE);
2464
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL);
2465
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE);
2466
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_STATE_CLOSED);
2467
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_NO_ERROR);
2468
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_PROTOCOL_ERROR);
2469
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_INTERNAL_ERROR);
2470
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLOW_CONTROL_ERROR);
2471
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_TIMEOUT);
2472
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_CLOSED);
2473
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_FRAME_SIZE_ERROR);
2474
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_REFUSED_STREAM);
2475
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_CANCEL);
2476
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_COMPRESSION_ERROR);
2477
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_CONNECT_ERROR);
2478
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_ENHANCE_YOUR_CALM);
2479
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_INADEQUATE_SECURITY);
2480
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_HTTP_1_1_REQUIRED);
2481
2482
604
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_REQUEST);
2483
604
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_RESPONSE);
2484
604
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_PUSH_RESPONSE);
2485
604
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_HEADERS);
2486
604
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_NV_FLAG_NONE);
2487
604
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_NV_FLAG_NO_INDEX);
2488
604
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_DEFERRED);
2489
604
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE);
2490
604
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_INVALID_ARGUMENT);
2491
604
  NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_STREAM_CLOSED);
2492
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_ERR_FRAME_SIZE_ERROR);
2493
2494
604
  NODE_DEFINE_HIDDEN_CONSTANT(constants, STREAM_OPTION_EMPTY_PAYLOAD);
2495
604
  NODE_DEFINE_HIDDEN_CONSTANT(constants, STREAM_OPTION_GET_TRAILERS);
2496
2497
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_NONE);
2498
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_END_STREAM);
2499
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_END_HEADERS);
2500
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_ACK);
2501
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_PADDED);
2502
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_PRIORITY);
2503
2504
453
  NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_HEADER_TABLE_SIZE);
2505
453
  NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_ENABLE_PUSH);
2506
453
  NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE);
2507
453
  NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_MAX_FRAME_SIZE);
2508
453
  NODE_DEFINE_CONSTANT(constants, MAX_MAX_FRAME_SIZE);
2509
453
  NODE_DEFINE_CONSTANT(constants, MIN_MAX_FRAME_SIZE);
2510
453
  NODE_DEFINE_CONSTANT(constants, MAX_INITIAL_WINDOW_SIZE);
2511
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_DEFAULT_WEIGHT);
2512
2513
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE);
2514
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_ENABLE_PUSH);
2515
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
2516
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
2517
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_MAX_FRAME_SIZE);
2518
453
  NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE);
2519
2520
453
  NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_NONE);
2521
453
  NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_MAX);
2522
453
  NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_CALLBACK);
2523
2524
#define STRING_CONSTANT(NAME, VALUE)                                          \
2525
  NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_HEADER_" # NAME, VALUE);
2526
45300
HTTP_KNOWN_HEADERS(STRING_CONSTANT)
2527
#undef STRING_CONSTANT
2528
2529
#define STRING_CONSTANT(NAME, VALUE)                                          \
2530
  NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_METHOD_" # NAME, VALUE);
2531
23556
HTTP_KNOWN_METHODS(STRING_CONSTANT)
2532
#undef STRING_CONSTANT
2533
2534
#define V(name, _) NODE_DEFINE_CONSTANT(constants, HTTP_STATUS_##name);
2535
28539
HTTP_STATUS_CODES(V)
2536
#undef V
2537
2538
151
  env->SetMethod(target, "refreshDefaultSettings", RefreshDefaultSettings);
2539
151
  env->SetMethod(target, "packSettings", PackSettings);
2540
2541
  target->Set(context,
2542
              FIXED_ONE_BYTE_STRING(isolate, "constants"),
2543
604
              constants).FromJust();
2544
151
}
2545
}  // namespace http2
2546
}  // namespace node
2547
2548

13564
NODE_BUILTIN_MODULE_CONTEXT_AWARE(http2, node::http2::Initialize)