1 #include "aliased_buffer.h"
2 #include "debug_utils.h"
3 #include "node.h"
4 #include "node_buffer.h"
5 #include "node_http2.h"
6 #include "node_http2_state.h"
7 #include "node_internals.h"
8 #include "node_perf.h"
9 #include "node_revert.h"
10 #include "util-inl.h"
11
12 #include <algorithm>
13
14 namespace node {
15
16 using v8::ArrayBuffer;
17 using v8::ArrayBufferCreationMode;
18 using v8::Boolean;
19 using v8::Context;
20 using v8::Float64Array;
21 using v8::Function;
22 using v8::Integer;
23 using v8::NewStringType;
24 using v8::Number;
25 using v8::ObjectTemplate;
26 using v8::String;
27 using v8::Uint32;
28 using v8::Uint32Array;
29 using v8::Uint8Array;
30 using v8::Undefined;
31
32 using node::performance::PerformanceEntry;
33 namespace http2 {
34
35 namespace {
36
37 const char zero_bytes_256[256] = {};
38
GetStream(Http2Session * session,int32_t id,nghttp2_data_source * source)39 inline Http2Stream* GetStream(Http2Session* session,
40 int32_t id,
41 nghttp2_data_source* source) {
42 Http2Stream* stream = static_cast<Http2Stream*>(source->ptr);
43 if (stream == nullptr)
44 stream = session->FindStream(id);
45 CHECK_NOT_NULL(stream);
46 CHECK_EQ(id, stream->id());
47 return stream;
48 }
49
50 } // anonymous namespace
51
52 // These configure the callbacks required by nghttp2 itself. There are
53 // two sets of callback functions, one that is used if a padding callback
54 // is set, and other that does not include the padding callback.
55 const Http2Session::Callbacks Http2Session::callback_struct_saved[2] = {
56 Callbacks(false),
57 Callbacks(true)};
58
59 // The Http2Scope object is used to queue a write to the i/o stream. It is
60 // used whenever any action is take on the underlying nghttp2 API that may
61 // push data into nghttp2 outbound data queue.
62 //
63 // For example:
64 //
65 // Http2Scope h2scope(session);
66 // nghttp2_submit_ping(**session, ... );
67 //
68 // When the Http2Scope passes out of scope and is deconstructed, it will
69 // call Http2Session::MaybeScheduleWrite().
Http2Scope(Http2Stream * stream)70 Http2Scope::Http2Scope(Http2Stream* stream) : Http2Scope(stream->session()) {}
71
Http2Scope(Http2Session * session)72 Http2Scope::Http2Scope(Http2Session* session) {
73 if (session == nullptr)
74 return;
75
76 if (session->flags_ & (SESSION_STATE_HAS_SCOPE |
77 SESSION_STATE_WRITE_SCHEDULED)) {
78 // There is another scope further below on the stack, or it is already
79 // known that a write is scheduled. In either case, there is nothing to do.
80 return;
81 }
82 session->flags_ |= SESSION_STATE_HAS_SCOPE;
83 session_ = session;
84
85 // Always keep the session object alive for at least as long as
86 // this scope is active.
87 session_handle_ = session->object();
88 CHECK(!session_handle_.IsEmpty());
89 }
90
~Http2Scope()91 Http2Scope::~Http2Scope() {
92 if (session_ == nullptr)
93 return;
94
95 session_->flags_ &= ~SESSION_STATE_HAS_SCOPE;
96 session_->MaybeScheduleWrite();
97 }
98
99 // The Http2Options object is used during the construction of Http2Session
100 // instances to configure an appropriate nghttp2_options struct. The class
101 // uses a single TypedArray instance that is shared with the JavaScript side
102 // to more efficiently pass values back and forth.
Http2Options(Environment * env,nghttp2_session_type type)103 Http2Options::Http2Options(Environment* env, nghttp2_session_type type) {
104 nghttp2_option_new(&options_);
105
106 // Make sure closed connections aren't kept around, taking up memory.
107 // Note that this breaks the priority tree, which we don't use.
108 nghttp2_option_set_no_closed_streams(options_, 1);
109
110 // We manually handle flow control within a session in order to
111 // implement backpressure -- that is, we only send WINDOW_UPDATE
112 // frames to the remote peer as data is actually consumed by user
113 // code. This ensures that the flow of data over the connection
114 // does not move too quickly and limits the amount of data we
115 // are required to buffer.
116 nghttp2_option_set_no_auto_window_update(options_, 1);
117
118 // Enable built in support for receiving ALTSVC and ORIGIN frames (but
119 // only on client side sessions
120 if (type == NGHTTP2_SESSION_CLIENT) {
121 nghttp2_option_set_builtin_recv_extension_type(options_, NGHTTP2_ALTSVC);
122 nghttp2_option_set_builtin_recv_extension_type(options_, NGHTTP2_ORIGIN);
123 }
124
125 AliasedBuffer<uint32_t, Uint32Array>& buffer =
126 env->http2_state()->options_buffer;
127 uint32_t flags = buffer[IDX_OPTIONS_FLAGS];
128
129 if (flags & (1 << IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE)) {
130 nghttp2_option_set_max_deflate_dynamic_table_size(
131 options_,
132 buffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE]);
133 }
134
135 if (flags & (1 << IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS)) {
136 nghttp2_option_set_max_reserved_remote_streams(
137 options_,
138 buffer[IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS]);
139 }
140
141 if (flags & (1 << IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH)) {
142 nghttp2_option_set_max_send_header_block_length(
143 options_,
144 buffer[IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH]);
145 }
146
147 // Recommended default
148 nghttp2_option_set_peer_max_concurrent_streams(options_, 100);
149 if (flags & (1 << IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS)) {
150 nghttp2_option_set_peer_max_concurrent_streams(
151 options_,
152 buffer[IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS]);
153 }
154
155 if (IsReverted(SECURITY_REVERT_CVE_2019_9512))
156 nghttp2_option_set_max_outbound_ack(options_, 10000);
157
158 // The padding strategy sets the mechanism by which we determine how much
159 // additional frame padding to apply to DATA and HEADERS frames. Currently
160 // this is set on a per-session basis, but eventually we may switch to
161 // a per-stream setting, giving users greater control
162 if (flags & (1 << IDX_OPTIONS_PADDING_STRATEGY)) {
163 padding_strategy_type strategy =
164 static_cast<padding_strategy_type>(
165 buffer.GetValue(IDX_OPTIONS_PADDING_STRATEGY));
166 SetPaddingStrategy(strategy);
167 }
168
169 // The max header list pairs option controls the maximum number of
170 // header pairs the session may accept. This is a hard limit.. that is,
171 // if the remote peer sends more than this amount, the stream will be
172 // automatically closed with an RST_STREAM.
173 if (flags & (1 << IDX_OPTIONS_MAX_HEADER_LIST_PAIRS)) {
174 SetMaxHeaderPairs(buffer[IDX_OPTIONS_MAX_HEADER_LIST_PAIRS]);
175 }
176
177 // The HTTP2 specification places no limits on the number of HTTP2
178 // PING frames that can be sent. In order to prevent PINGS from being
179 // abused as an attack vector, however, we place a strict upper limit
180 // on the number of unacknowledged PINGS that can be sent at any given
181 // time.
182 if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_PINGS)) {
183 SetMaxOutstandingPings(buffer[IDX_OPTIONS_MAX_OUTSTANDING_PINGS]);
184 }
185
186 // The HTTP2 specification places no limits on the number of HTTP2
187 // SETTINGS frames that can be sent. In order to prevent PINGS from being
188 // abused as an attack vector, however, we place a strict upper limit
189 // on the number of unacknowledged SETTINGS that can be sent at any given
190 // time.
191 if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS)) {
192 SetMaxOutstandingSettings(buffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS]);
193 }
194
195 // The HTTP2 specification places no limits on the amount of memory
196 // that a session can consume. In order to prevent abuse, we place a
197 // cap on the amount of memory a session can consume at any given time.
198 // this is a credit based system. Existing streams may cause the limit
199 // to be temporarily exceeded but once over the limit, new streams cannot
200 // created.
201 // Important: The maxSessionMemory option in javascript is expressed in
202 // terms of MB increments (i.e. the value 1 == 1 MB)
203 if (flags & (1 << IDX_OPTIONS_MAX_SESSION_MEMORY)) {
204 SetMaxSessionMemory(buffer[IDX_OPTIONS_MAX_SESSION_MEMORY] * 1e6);
205 }
206
207 if (flags & (1 << IDX_OPTIONS_MAX_SETTINGS)) {
208 nghttp2_option_set_max_settings(
209 options_,
210 static_cast<size_t>(buffer[IDX_OPTIONS_MAX_SETTINGS]));
211 }
212 }
213
Init()214 void Http2Session::Http2Settings::Init() {
215 AliasedBuffer<uint32_t, Uint32Array>& buffer =
216 env()->http2_state()->settings_buffer;
217 uint32_t flags = buffer[IDX_SETTINGS_COUNT];
218
219 size_t n = 0;
220
221 #define GRABSETTING(N, trace) \
222 if (flags & (1 << IDX_SETTINGS_##N)) { \
223 uint32_t val = buffer[IDX_SETTINGS_##N]; \
224 if (session_ != nullptr) \
225 Debug(session_, "setting " trace ": %d\n", val); \
226 entries_[n++] = \
227 nghttp2_settings_entry {NGHTTP2_SETTINGS_##N, val}; \
228 }
229
230 GRABSETTING(HEADER_TABLE_SIZE, "header table size");
231 GRABSETTING(MAX_CONCURRENT_STREAMS, "max concurrent streams");
232 GRABSETTING(MAX_FRAME_SIZE, "max frame size");
233 GRABSETTING(INITIAL_WINDOW_SIZE, "initial window size");
234 GRABSETTING(MAX_HEADER_LIST_SIZE, "max header list size");
235 GRABSETTING(ENABLE_PUSH, "enable push");
236 GRABSETTING(ENABLE_CONNECT_PROTOCOL, "enable connect protocol");
237
238 #undef GRABSETTING
239
240 count_ = n;
241 }
242
243 // The Http2Settings class is used to configure a SETTINGS frame that is
244 // to be sent to the connected peer. The settings are set using a TypedArray
245 // that is shared with the JavaScript side.
Http2Settings(Environment * env,Http2Session * session,Local<Object> obj,uint64_t start_time)246 Http2Session::Http2Settings::Http2Settings(Environment* env,
247 Http2Session* session,
248 Local<Object> obj,
249 uint64_t start_time)
250 : AsyncWrap(env, obj, PROVIDER_HTTP2SETTINGS),
251 session_(session),
252 startTime_(start_time) {
253 Init();
254 }
255
256 // Generates a Buffer that contains the serialized payload of a SETTINGS
257 // frame. This can be used, for instance, to create the Base64-encoded
258 // content of an Http2-Settings header field.
Pack()259 Local<Value> Http2Session::Http2Settings::Pack() {
260 const size_t len = count_ * 6;
261 Local<Value> buf = Buffer::New(env(), len).ToLocalChecked();
262 ssize_t ret =
263 nghttp2_pack_settings_payload(
264 reinterpret_cast<uint8_t*>(Buffer::Data(buf)), len,
265 &entries_[0], count_);
266 if (ret >= 0)
267 return buf;
268 else
269 return Undefined(env()->isolate());
270 }
271
272 // Updates the shared TypedArray with the current remote or local settings for
273 // the session.
Update(Environment * env,Http2Session * session,get_setting fn)274 void Http2Session::Http2Settings::Update(Environment* env,
275 Http2Session* session,
276 get_setting fn) {
277 AliasedBuffer<uint32_t, Uint32Array>& buffer =
278 env->http2_state()->settings_buffer;
279 buffer[IDX_SETTINGS_HEADER_TABLE_SIZE] =
280 fn(**session, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE);
281 buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS] =
282 fn(**session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
283 buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] =
284 fn(**session, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
285 buffer[IDX_SETTINGS_MAX_FRAME_SIZE] =
286 fn(**session, NGHTTP2_SETTINGS_MAX_FRAME_SIZE);
287 buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] =
288 fn(**session, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE);
289 buffer[IDX_SETTINGS_ENABLE_PUSH] =
290 fn(**session, NGHTTP2_SETTINGS_ENABLE_PUSH);
291 buffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL] =
292 fn(**session, NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL);
293 }
294
295 // Initializes the shared TypedArray with the default settings values.
RefreshDefaults(Environment * env)296 void Http2Session::Http2Settings::RefreshDefaults(Environment* env) {
297 AliasedBuffer<uint32_t, Uint32Array>& buffer =
298 env->http2_state()->settings_buffer;
299
300 buffer[IDX_SETTINGS_HEADER_TABLE_SIZE] =
301 DEFAULT_SETTINGS_HEADER_TABLE_SIZE;
302 buffer[IDX_SETTINGS_ENABLE_PUSH] =
303 DEFAULT_SETTINGS_ENABLE_PUSH;
304 buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] =
305 DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE;
306 buffer[IDX_SETTINGS_MAX_FRAME_SIZE] =
307 DEFAULT_SETTINGS_MAX_FRAME_SIZE;
308 buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] =
309 DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE;
310 buffer[IDX_SETTINGS_COUNT] =
311 (1 << IDX_SETTINGS_HEADER_TABLE_SIZE) |
312 (1 << IDX_SETTINGS_ENABLE_PUSH) |
313 (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE) |
314 (1 << IDX_SETTINGS_MAX_FRAME_SIZE) |
315 (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE);
316 }
317
318
Send()319 void Http2Session::Http2Settings::Send() {
320 Http2Scope h2scope(session_);
321 CHECK_EQ(nghttp2_submit_settings(**session_, NGHTTP2_FLAG_NONE,
322 &entries_[0], count_), 0);
323 }
324
Done(bool ack)325 void Http2Session::Http2Settings::Done(bool ack) {
326 uint64_t end = uv_hrtime();
327 double duration = (end - startTime_) / 1e6;
328
329 Local<Value> argv[2] = {
330 Boolean::New(env()->isolate(), ack),
331 Number::New(env()->isolate(), duration)
332 };
333 MakeCallback(env()->ondone_string(), arraysize(argv), argv);
334 delete this;
335 }
336
337 // The Http2Priority class initializes an appropriate nghttp2_priority_spec
338 // struct used when either creating a stream or updating its priority
339 // settings.
Http2Priority(Environment * env,Local<Value> parent,Local<Value> weight,Local<Value> exclusive)340 Http2Priority::Http2Priority(Environment* env,
341 Local<Value> parent,
342 Local<Value> weight,
343 Local<Value> exclusive) {
344 Local<Context> context = env->context();
345 int32_t parent_ = parent->Int32Value(context).ToChecked();
346 int32_t weight_ = weight->Int32Value(context).ToChecked();
347 bool exclusive_ = exclusive->BooleanValue(context).ToChecked();
348 Debug(env, DebugCategory::HTTP2STREAM,
349 "Http2Priority: parent: %d, weight: %d, exclusive: %d\n",
350 parent_, weight_, exclusive_);
351 nghttp2_priority_spec_init(&spec, parent_, weight_, exclusive_ ? 1 : 0);
352 }
353
354
TypeName() const355 const char* Http2Session::TypeName() const {
356 switch (session_type_) {
357 case NGHTTP2_SESSION_SERVER: return "server";
358 case NGHTTP2_SESSION_CLIENT: return "client";
359 default:
360 // This should never happen
361 ABORT();
362 }
363 }
364
365 // The Headers class initializes a proper array of nghttp2_nv structs
366 // containing the header name value pairs.
Headers(Isolate * isolate,Local<Context> context,Local<Array> headers)367 Headers::Headers(Isolate* isolate,
368 Local<Context> context,
369 Local<Array> headers) {
370 Local<Value> header_string = headers->Get(context, 0).ToLocalChecked();
371 Local<Value> header_count = headers->Get(context, 1).ToLocalChecked();
372 count_ = header_count.As<Uint32>()->Value();
373 int header_string_len = header_string.As<String>()->Length();
374
375 if (count_ == 0) {
376 CHECK_EQ(header_string_len, 0);
377 return;
378 }
379
380 // Allocate a single buffer with count_ nghttp2_nv structs, followed
381 // by the raw header data as passed from JS. This looks like:
382 // | possible padding | nghttp2_nv | nghttp2_nv | ... | header contents |
383 buf_.AllocateSufficientStorage((alignof(nghttp2_nv) - 1) +
384 count_ * sizeof(nghttp2_nv) +
385 header_string_len);
386 // Make sure the start address is aligned appropriately for an nghttp2_nv*.
387 char* start = reinterpret_cast<char*>(
388 ROUND_UP(reinterpret_cast<uintptr_t>(*buf_), alignof(nghttp2_nv)));
389 char* header_contents = start + (count_ * sizeof(nghttp2_nv));
390 nghttp2_nv* const nva = reinterpret_cast<nghttp2_nv*>(start);
391
392 CHECK_LE(header_contents + header_string_len, *buf_ + buf_.length());
393 CHECK_EQ(header_string.As<String>()->WriteOneByte(
394 isolate,
395 reinterpret_cast<uint8_t*>(header_contents),
396 0,
397 header_string_len,
398 String::NO_NULL_TERMINATION),
399 header_string_len);
400
401 size_t n = 0;
402 char* p;
403 for (p = header_contents; p < header_contents + header_string_len; n++) {
404 if (n >= count_) {
405 // This can happen if a passed header contained a null byte. In that
406 // case, just provide nghttp2 with an invalid header to make it reject
407 // the headers list.
408 static uint8_t zero = '\0';
409 nva[0].name = nva[0].value = &zero;
410 nva[0].namelen = nva[0].valuelen = 1;
411 count_ = 1;
412 return;
413 }
414
415 nva[n].flags = NGHTTP2_NV_FLAG_NONE;
416 nva[n].name = reinterpret_cast<uint8_t*>(p);
417 nva[n].namelen = strlen(p);
418 p += nva[n].namelen + 1;
419 nva[n].value = reinterpret_cast<uint8_t*>(p);
420 nva[n].valuelen = strlen(p);
421 p += nva[n].valuelen + 1;
422 }
423 }
424
Origins(Isolate * isolate,Local<Context> context,Local<String> origin_string,size_t origin_count)425 Origins::Origins(Isolate* isolate,
426 Local<Context> context,
427 Local<String> origin_string,
428 size_t origin_count) : count_(origin_count) {
429 int origin_string_len = origin_string->Length();
430 if (count_ == 0) {
431 CHECK_EQ(origin_string_len, 0);
432 return;
433 }
434
435 // Allocate a single buffer with count_ nghttp2_nv structs, followed
436 // by the raw header data as passed from JS. This looks like:
437 // | possible padding | nghttp2_nv | nghttp2_nv | ... | header contents |
438 buf_.AllocateSufficientStorage((alignof(nghttp2_origin_entry) - 1) +
439 count_ * sizeof(nghttp2_origin_entry) +
440 origin_string_len);
441
442 // Make sure the start address is aligned appropriately for an nghttp2_nv*.
443 char* start = reinterpret_cast<char*>(
444 ROUND_UP(reinterpret_cast<uintptr_t>(*buf_),
445 alignof(nghttp2_origin_entry)));
446 char* origin_contents = start + (count_ * sizeof(nghttp2_origin_entry));
447 nghttp2_origin_entry* const nva =
448 reinterpret_cast<nghttp2_origin_entry*>(start);
449
450 CHECK_LE(origin_contents + origin_string_len, *buf_ + buf_.length());
451 CHECK_EQ(origin_string->WriteOneByte(
452 isolate,
453 reinterpret_cast<uint8_t*>(origin_contents),
454 0,
455 origin_string_len,
456 String::NO_NULL_TERMINATION),
457 origin_string_len);
458
459 size_t n = 0;
460 char* p;
461 for (p = origin_contents; p < origin_contents + origin_string_len; n++) {
462 if (n >= count_) {
463 static uint8_t zero = '\0';
464 nva[0].origin = &zero;
465 nva[0].origin_len = 1;
466 count_ = 1;
467 return;
468 }
469
470 nva[n].origin = reinterpret_cast<uint8_t*>(p);
471 nva[n].origin_len = strlen(p);
472 p += nva[n].origin_len + 1;
473 }
474 }
475
476 // Sets the various callback functions that nghttp2 will use to notify us
477 // about significant events while processing http2 stuff.
Callbacks(bool kHasGetPaddingCallback)478 Http2Session::Callbacks::Callbacks(bool kHasGetPaddingCallback) {
479 CHECK_EQ(nghttp2_session_callbacks_new(&callbacks), 0);
480
481 nghttp2_session_callbacks_set_on_begin_headers_callback(
482 callbacks, OnBeginHeadersCallback);
483 nghttp2_session_callbacks_set_on_header_callback2(
484 callbacks, OnHeaderCallback);
485 nghttp2_session_callbacks_set_on_frame_recv_callback(
486 callbacks, OnFrameReceive);
487 nghttp2_session_callbacks_set_on_stream_close_callback(
488 callbacks, OnStreamClose);
489 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
490 callbacks, OnDataChunkReceived);
491 nghttp2_session_callbacks_set_on_frame_not_send_callback(
492 callbacks, OnFrameNotSent);
493 nghttp2_session_callbacks_set_on_invalid_header_callback2(
494 callbacks, OnInvalidHeader);
495 nghttp2_session_callbacks_set_error_callback(
496 callbacks, OnNghttpError);
497 nghttp2_session_callbacks_set_send_data_callback(
498 callbacks, OnSendData);
499 nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
500 callbacks, OnInvalidFrame);
501 nghttp2_session_callbacks_set_on_frame_send_callback(
502 callbacks, OnFrameSent);
503
504 if (kHasGetPaddingCallback) {
505 nghttp2_session_callbacks_set_select_padding_callback(
506 callbacks, OnSelectPadding);
507 }
508 }
509
510
~Callbacks()511 Http2Session::Callbacks::~Callbacks() {
512 nghttp2_session_callbacks_del(callbacks);
513 }
514
515 // Track memory allocated by nghttp2 using a custom allocator.
516 class Http2Session::MemoryAllocatorInfo {
517 public:
MemoryAllocatorInfo(Http2Session * session)518 explicit MemoryAllocatorInfo(Http2Session* session)
519 : info({ session, H2Malloc, H2Free, H2Calloc, H2Realloc }) {}
520
H2Malloc(size_t size,void * user_data)521 static void* H2Malloc(size_t size, void* user_data) {
522 return H2Realloc(nullptr, size, user_data);
523 }
524
H2Calloc(size_t nmemb,size_t size,void * user_data)525 static void* H2Calloc(size_t nmemb, size_t size, void* user_data) {
526 size_t real_size = MultiplyWithOverflowCheck(nmemb, size);
527 void* mem = H2Malloc(real_size, user_data);
528 if (mem != nullptr)
529 memset(mem, 0, real_size);
530 return mem;
531 }
532
H2Free(void * ptr,void * user_data)533 static void H2Free(void* ptr, void* user_data) {
534 if (ptr == nullptr) return; // free(null); happens quite often.
535 void* result = H2Realloc(ptr, 0, user_data);
536 CHECK_EQ(result, nullptr);
537 }
538
H2Realloc(void * ptr,size_t size,void * user_data)539 static void* H2Realloc(void* ptr, size_t size, void* user_data) {
540 Http2Session* session = static_cast<Http2Session*>(user_data);
541 size_t previous_size = 0;
542 char* original_ptr = nullptr;
543
544 // We prepend each allocated buffer with a size_t containing the full
545 // size of the allocation.
546 if (size > 0) size += sizeof(size_t);
547
548 if (ptr != nullptr) {
549 // We are free()ing or re-allocating.
550 original_ptr = static_cast<char*>(ptr) - sizeof(size_t);
551 previous_size = *reinterpret_cast<size_t*>(original_ptr);
552 // This means we called StopTracking() on this pointer before.
553 if (previous_size == 0) {
554 // Fall back to the standard Realloc() function.
555 char* ret = UncheckedRealloc(original_ptr, size);
556 if (ret != nullptr)
557 ret += sizeof(size_t);
558 return ret;
559 }
560 }
561 CHECK_GE(session->current_nghttp2_memory_, previous_size);
562
563 // TODO(addaleax): Add the following, and handle NGHTTP2_ERR_NOMEM properly
564 // everywhere:
565 //
566 // if (size > previous_size &&
567 // !session->IsAvailableSessionMemory(size - previous_size)) {
568 // return nullptr;
569 //}
570
571 char* mem = UncheckedRealloc(original_ptr, size);
572
573 if (mem != nullptr) {
574 // Adjust the memory info counter.
575 session->current_nghttp2_memory_ += size - previous_size;
576 *reinterpret_cast<size_t*>(mem) = size;
577 mem += sizeof(size_t);
578 } else if (size == 0) {
579 session->current_nghttp2_memory_ -= previous_size;
580 }
581
582 return mem;
583 }
584
StopTracking(Http2Session * session,void * ptr)585 static void StopTracking(Http2Session* session, void* ptr) {
586 size_t* original_ptr = reinterpret_cast<size_t*>(
587 static_cast<char*>(ptr) - sizeof(size_t));
588 session->current_nghttp2_memory_ -= *original_ptr;
589 *original_ptr = 0;
590 }
591
operator *()592 inline nghttp2_mem* operator*() { return &info; }
593
594 nghttp2_mem info;
595 };
596
StopTrackingRcbuf(nghttp2_rcbuf * buf)597 void Http2Session::StopTrackingRcbuf(nghttp2_rcbuf* buf) {
598 MemoryAllocatorInfo::StopTracking(this, buf);
599 }
600
Http2Session(Environment * env,Local<Object> wrap,nghttp2_session_type type)601 Http2Session::Http2Session(Environment* env,
602 Local<Object> wrap,
603 nghttp2_session_type type)
604 : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_HTTP2SESSION),
605 session_type_(type) {
606 MakeWeak();
607 statistics_.start_time = uv_hrtime();
608
609 // Capture the configuration options for this session
610 Http2Options opts(env, type);
611
612 max_session_memory_ = opts.GetMaxSessionMemory();
613
614 uint32_t maxHeaderPairs = opts.GetMaxHeaderPairs();
615 max_header_pairs_ =
616 type == NGHTTP2_SESSION_SERVER
617 ? std::max(maxHeaderPairs, 4U) // minimum # of request headers
618 : std::max(maxHeaderPairs, 1U); // minimum # of response headers
619
620 max_outstanding_pings_ = opts.GetMaxOutstandingPings();
621 max_outstanding_settings_ = opts.GetMaxOutstandingSettings();
622
623 padding_strategy_ = opts.GetPaddingStrategy();
624
625 bool hasGetPaddingCallback =
626 padding_strategy_ != PADDING_STRATEGY_NONE;
627
628 nghttp2_session_callbacks* callbacks
629 = callback_struct_saved[hasGetPaddingCallback ? 1 : 0].callbacks;
630
631 auto fn = type == NGHTTP2_SESSION_SERVER ?
632 nghttp2_session_server_new3 :
633 nghttp2_session_client_new3;
634
635 MemoryAllocatorInfo allocator_info(this);
636
637 // This should fail only if the system is out of memory, which
638 // is going to cause lots of other problems anyway, or if any
639 // of the options are out of acceptable range, which we should
640 // be catching before it gets this far. Either way, crash if this
641 // fails.
642 CHECK_EQ(fn(&session_, callbacks, this, *opts, *allocator_info), 0);
643
644 outgoing_storage_.reserve(1024);
645 outgoing_buffers_.reserve(32);
646
647 {
648 // Make the js_fields_ property accessible to JS land.
649 Local<ArrayBuffer> ab =
650 ArrayBuffer::New(env->isolate(), js_fields_, kSessionUint8FieldCount);
651 Local<Uint8Array> uint8_arr =
652 Uint8Array::New(ab, 0, kSessionUint8FieldCount);
653 USE(wrap->Set(env->context(), env->fields_string(), uint8_arr));
654 }
655 }
656
~Http2Session()657 Http2Session::~Http2Session() {
658 CHECK_EQ(flags_ & SESSION_STATE_HAS_SCOPE, 0);
659 Debug(this, "freeing nghttp2 session");
660 for (const auto& stream : streams_)
661 stream.second->session_ = nullptr;
662 nghttp2_session_del(session_);
663 CHECK_EQ(current_nghttp2_memory_, 0);
664 free(stream_buf_allocation_.base);
665 }
666
diagnostic_name() const667 std::string Http2Session::diagnostic_name() const {
668 return std::string("Http2Session ") + TypeName() + " (" +
669 std::to_string(static_cast<int64_t>(get_async_id())) + ")";
670 }
671
HasHttp2Observer(Environment * env)672 inline bool HasHttp2Observer(Environment* env) {
673 AliasedBuffer<uint32_t, Uint32Array>& observers =
674 env->performance_state()->observers;
675 return observers[performance::NODE_PERFORMANCE_ENTRY_TYPE_HTTP2] != 0;
676 }
677
EmitStatistics()678 void Http2Stream::EmitStatistics() {
679 if (!HasHttp2Observer(env()))
680 return;
681 Http2StreamPerformanceEntry* entry =
682 new Http2StreamPerformanceEntry(env(), id_, statistics_);
683 env()->SetImmediate([](Environment* env, void* data) {
684 // This takes ownership, the entry is destroyed at the end of this scope.
685 std::unique_ptr<Http2StreamPerformanceEntry> entry {
686 static_cast<Http2StreamPerformanceEntry*>(data) };
687 if (!HasHttp2Observer(env))
688 return;
689 HandleScope handle_scope(env->isolate());
690 AliasedBuffer<double, Float64Array>& buffer =
691 env->http2_state()->stream_stats_buffer;
692 buffer[IDX_STREAM_STATS_ID] = entry->id();
693 if (entry->first_byte() != 0) {
694 buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTE] =
695 (entry->first_byte() - entry->startTimeNano()) / 1e6;
696 } else {
697 buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTE] = 0;
698 }
699 if (entry->first_header() != 0) {
700 buffer[IDX_STREAM_STATS_TIMETOFIRSTHEADER] =
701 (entry->first_header() - entry->startTimeNano()) / 1e6;
702 } else {
703 buffer[IDX_STREAM_STATS_TIMETOFIRSTHEADER] = 0;
704 }
705 if (entry->first_byte_sent() != 0) {
706 buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTESENT] =
707 (entry->first_byte_sent() - entry->startTimeNano()) / 1e6;
708 } else {
709 buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTESENT] = 0;
710 }
711 buffer[IDX_STREAM_STATS_SENTBYTES] = entry->sent_bytes();
712 buffer[IDX_STREAM_STATS_RECEIVEDBYTES] = entry->received_bytes();
713 entry->Notify(entry->ToObject());
714 }, static_cast<void*>(entry));
715 }
716
EmitStatistics()717 void Http2Session::EmitStatistics() {
718 if (!HasHttp2Observer(env()))
719 return;
720 Http2SessionPerformanceEntry* entry =
721 new Http2SessionPerformanceEntry(env(), statistics_, session_type_);
722 env()->SetImmediate([](Environment* env, void* data) {
723 // This takes ownership, the entr is destroyed at the end of this scope.
724 std::unique_ptr<Http2SessionPerformanceEntry> entry {
725 static_cast<Http2SessionPerformanceEntry*>(data) };
726 if (!HasHttp2Observer(env))
727 return;
728 HandleScope handle_scope(env->isolate());
729 AliasedBuffer<double, Float64Array>& buffer =
730 env->http2_state()->session_stats_buffer;
731 buffer[IDX_SESSION_STATS_TYPE] = entry->type();
732 buffer[IDX_SESSION_STATS_PINGRTT] = entry->ping_rtt() / 1e6;
733 buffer[IDX_SESSION_STATS_FRAMESRECEIVED] = entry->frame_count();
734 buffer[IDX_SESSION_STATS_FRAMESSENT] = entry->frame_sent();
735 buffer[IDX_SESSION_STATS_STREAMCOUNT] = entry->stream_count();
736 buffer[IDX_SESSION_STATS_STREAMAVERAGEDURATION] =
737 entry->stream_average_duration();
738 buffer[IDX_SESSION_STATS_DATA_SENT] = entry->data_sent();
739 buffer[IDX_SESSION_STATS_DATA_RECEIVED] = entry->data_received();
740 buffer[IDX_SESSION_STATS_MAX_CONCURRENT_STREAMS] =
741 entry->max_concurrent_streams();
742 entry->Notify(entry->ToObject());
743 }, static_cast<void*>(entry));
744 }
745
746 // Closes the session and frees the associated resources
Close(uint32_t code,bool socket_closed)747 void Http2Session::Close(uint32_t code, bool socket_closed) {
748 Debug(this, "closing session");
749
750 if (flags_ & SESSION_STATE_CLOSING)
751 return;
752 flags_ |= SESSION_STATE_CLOSING;
753
754 // Stop reading on the i/o stream
755 if (stream_ != nullptr) {
756 flags_ |= SESSION_STATE_READING_STOPPED;
757 stream_->ReadStop();
758 }
759
760 // If the socket is not closed, then attempt to send a closing GOAWAY
761 // frame. There is no guarantee that this GOAWAY will be received by
762 // the peer but the HTTP/2 spec recommends sending it anyway. We'll
763 // make a best effort.
764 if (!socket_closed) {
765 Debug(this, "terminating session with code %d", code);
766 CHECK_EQ(nghttp2_session_terminate_session(session_, code), 0);
767 SendPendingData();
768 } else if (stream_ != nullptr) {
769 stream_->RemoveStreamListener(this);
770 }
771
772 flags_ |= SESSION_STATE_CLOSED;
773
774 // If there are outstanding pings, those will need to be canceled, do
775 // so on the next iteration of the event loop to avoid calling out into
776 // javascript since this may be called during garbage collection.
777 while (!outstanding_pings_.empty()) {
778 Http2Session::Http2Ping* ping = PopPing();
779 env()->SetImmediate([](Environment* env, void* data) {
780 static_cast<Http2Session::Http2Ping*>(data)->Done(false);
781 }, static_cast<void*>(ping));
782 }
783
784 statistics_.end_time = uv_hrtime();
785 EmitStatistics();
786 }
787
788 // Locates an existing known stream by ID. nghttp2 has a similar method
789 // but this is faster and does not fail if the stream is not found.
FindStream(int32_t id)790 inline Http2Stream* Http2Session::FindStream(int32_t id) {
791 auto s = streams_.find(id);
792 return s != streams_.end() ? s->second : nullptr;
793 }
794
CanAddStream()795 inline bool Http2Session::CanAddStream() {
796 uint32_t maxConcurrentStreams =
797 nghttp2_session_get_local_settings(
798 session_, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
799 size_t maxSize =
800 std::min(streams_.max_size(), static_cast<size_t>(maxConcurrentStreams));
801 // We can add a new stream so long as we are less than the current
802 // maximum on concurrent streams and there's enough available memory
803 return streams_.size() < maxSize &&
804 IsAvailableSessionMemory(sizeof(Http2Stream));
805 }
806
AddStream(Http2Stream * stream)807 inline void Http2Session::AddStream(Http2Stream* stream) {
808 CHECK_GE(++statistics_.stream_count, 0);
809 streams_[stream->id()] = stream;
810 size_t size = streams_.size();
811 if (size > statistics_.max_concurrent_streams)
812 statistics_.max_concurrent_streams = size;
813 IncrementCurrentSessionMemory(sizeof(*stream));
814 }
815
816
RemoveStream(Http2Stream * stream)817 inline void Http2Session::RemoveStream(Http2Stream* stream) {
818 if (streams_.empty() || stream == nullptr)
819 return; // Nothing to remove, item was never added?
820 streams_.erase(stream->id());
821 DecrementCurrentSessionMemory(sizeof(*stream));
822 }
823
824 // Used as one of the Padding Strategy functions. Will attempt to ensure
825 // that the total frame size, including header bytes, are 8-byte aligned.
826 // If maxPayloadLen is smaller than the number of bytes necessary to align,
827 // will return maxPayloadLen instead.
OnDWordAlignedPadding(size_t frameLen,size_t maxPayloadLen)828 ssize_t Http2Session::OnDWordAlignedPadding(size_t frameLen,
829 size_t maxPayloadLen) {
830 size_t r = (frameLen + 9) % 8;
831 if (r == 0) return frameLen; // If already a multiple of 8, return.
832
833 size_t pad = frameLen + (8 - r);
834
835 // If maxPayloadLen happens to be less than the calculated pad length,
836 // use the max instead, even tho this means the frame will not be
837 // aligned.
838 pad = std::min(maxPayloadLen, pad);
839 Debug(this, "using frame size padding: %d", pad);
840 return pad;
841 }
842
843 // Used as one of the Padding Strategy functions. Uses the maximum amount
844 // of padding allowed for the current frame.
OnMaxFrameSizePadding(size_t frameLen,size_t maxPayloadLen)845 ssize_t Http2Session::OnMaxFrameSizePadding(size_t frameLen,
846 size_t maxPayloadLen) {
847 Debug(this, "using max frame size padding: %d", maxPayloadLen);
848 return maxPayloadLen;
849 }
850
851 // Used as one of the Padding Strategy functions. Uses a callback to JS land
852 // to determine the amount of padding for the current frame. This option is
853 // rather more expensive because of the JS boundary cross. It generally should
854 // not be the preferred option.
OnCallbackPadding(size_t frameLen,size_t maxPayloadLen)855 ssize_t Http2Session::OnCallbackPadding(size_t frameLen,
856 size_t maxPayloadLen) {
857 if (frameLen == 0) return 0;
858 Debug(this, "using callback to determine padding");
859 Isolate* isolate = env()->isolate();
860 HandleScope handle_scope(isolate);
861 Local<Context> context = env()->context();
862 Context::Scope context_scope(context);
863
864 AliasedBuffer<uint32_t, Uint32Array>& buffer =
865 env()->http2_state()->padding_buffer;
866 buffer[PADDING_BUF_FRAME_LENGTH] = frameLen;
867 buffer[PADDING_BUF_MAX_PAYLOAD_LENGTH] = maxPayloadLen;
868 buffer[PADDING_BUF_RETURN_VALUE] = frameLen;
869 MakeCallback(env()->ongetpadding_string(), 0, nullptr);
870 uint32_t retval = buffer[PADDING_BUF_RETURN_VALUE];
871 retval = std::min(retval, static_cast<uint32_t>(maxPayloadLen));
872 retval = std::max(retval, static_cast<uint32_t>(frameLen));
873 Debug(this, "using padding size %d", retval);
874 return retval;
875 }
876
877
878 // Write data received from the i/o stream to the underlying nghttp2_session.
879 // On each call to nghttp2_session_mem_recv, nghttp2 will begin calling the
880 // various callback functions. Each of these will typically result in a call
881 // out to JavaScript so this particular function is rather hot and can be
882 // quite expensive. This is a potential performance optimization target later.
ConsumeHTTP2Data()883 ssize_t Http2Session::ConsumeHTTP2Data() {
884 CHECK_NOT_NULL(stream_buf_.base);
885 CHECK_LE(stream_buf_offset_, stream_buf_.len);
886 size_t read_len = stream_buf_.len - stream_buf_offset_;
887
888 // multiple side effects.
889 Debug(this, "receiving %d bytes [wants data? %d]",
890 read_len,
891 nghttp2_session_want_read(session_));
892 flags_ &= ~SESSION_STATE_NGHTTP2_RECV_PAUSED;
893 ssize_t ret =
894 nghttp2_session_mem_recv(session_,
895 reinterpret_cast<uint8_t*>(stream_buf_.base) +
896 stream_buf_offset_,
897 read_len);
898 CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
899
900 if (flags_ & SESSION_STATE_NGHTTP2_RECV_PAUSED) {
901 CHECK_NE(flags_ & SESSION_STATE_READING_STOPPED, 0);
902
903 CHECK_GT(ret, 0);
904 CHECK_LE(static_cast<size_t>(ret), read_len);
905
906 // Mark the remainder of the data as available for later consumption.
907 // Even if all bytes were received, a paused stream may delay the
908 // nghttp2_on_frame_recv_callback which may have an END_STREAM flag.
909 stream_buf_offset_ += ret;
910 return ret;
911 }
912
913 // We are done processing the current input chunk.
914 DecrementCurrentSessionMemory(stream_buf_.len);
915 stream_buf_offset_ = 0;
916 stream_buf_ab_.Reset();
917 free(stream_buf_allocation_.base);
918 stream_buf_allocation_ = uv_buf_init(nullptr, 0);
919 stream_buf_ = uv_buf_init(nullptr, 0);
920
921 if (ret < 0)
922 return ret;
923
924 // Send any data that was queued up while processing the received data.
925 if (!IsDestroyed()) {
926 SendPendingData();
927 }
928 return ret;
929 }
930
931
GetFrameID(const nghttp2_frame * frame)932 inline int32_t GetFrameID(const nghttp2_frame* frame) {
933 // If this is a push promise, we want to grab the id of the promised stream
934 return (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
935 frame->push_promise.promised_stream_id :
936 frame->hd.stream_id;
937 }
938
939
940 // Called by nghttp2 at the start of receiving a HEADERS frame. We use this
941 // callback to determine if a new stream is being created or if we are simply
942 // adding a new block of headers to an existing stream. The header pairs
943 // themselves are set in the OnHeaderCallback
OnBeginHeadersCallback(nghttp2_session * handle,const nghttp2_frame * frame,void * user_data)944 int Http2Session::OnBeginHeadersCallback(nghttp2_session* handle,
945 const nghttp2_frame* frame,
946 void* user_data) {
947 Http2Session* session = static_cast<Http2Session*>(user_data);
948 int32_t id = GetFrameID(frame);
949 Debug(session, "beginning headers for stream %d", id);
950
951 Http2Stream* stream = session->FindStream(id);
952 // The common case is that we're creating a new stream. The less likely
953 // case is that we're receiving a set of trailers
954 if (LIKELY(stream == nullptr)) {
955 if (UNLIKELY(!session->CanAddStream() ||
956 Http2Stream::New(session, id, frame->headers.cat) ==
957 nullptr)) {
958 if (session->rejected_stream_count_++ > 100 &&
959 !IsReverted(SECURITY_REVERT_CVE_2019_9514)) {
960 return NGHTTP2_ERR_CALLBACK_FAILURE;
961 }
962 // Too many concurrent streams being opened
963 nghttp2_submit_rst_stream(**session, NGHTTP2_FLAG_NONE, id,
964 NGHTTP2_ENHANCE_YOUR_CALM);
965 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
966 }
967
968 session->rejected_stream_count_ = 0;
969 } else if (!stream->IsDestroyed()) {
970 stream->StartHeaders(frame->headers.cat);
971 }
972 return 0;
973 }
974
975 // Called by nghttp2 for each header name/value pair in a HEADERS block.
976 // This had to have been preceded by a call to OnBeginHeadersCallback so
977 // the Http2Stream is guaranteed to already exist.
OnHeaderCallback(nghttp2_session * handle,const nghttp2_frame * frame,nghttp2_rcbuf * name,nghttp2_rcbuf * value,uint8_t flags,void * user_data)978 int Http2Session::OnHeaderCallback(nghttp2_session* handle,
979 const nghttp2_frame* frame,
980 nghttp2_rcbuf* name,
981 nghttp2_rcbuf* value,
982 uint8_t flags,
983 void* user_data) {
984 Http2Session* session = static_cast<Http2Session*>(user_data);
985 int32_t id = GetFrameID(frame);
986 Http2Stream* stream = session->FindStream(id);
987 // If stream is null at this point, either something odd has happened
988 // or the stream was closed locally while header processing was occurring.
989 // either way, do not proceed and close the stream.
990 if (UNLIKELY(stream == nullptr))
991 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
992
993 // If the stream has already been destroyed, ignore.
994 if (!stream->IsDestroyed() && !stream->AddHeader(name, value, flags)) {
995 // This will only happen if the connected peer sends us more
996 // than the allowed number of header items at any given time
997 stream->SubmitRstStream(NGHTTP2_ENHANCE_YOUR_CALM);
998 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
999 }
1000 return 0;
1001 }
1002
1003
1004 // Called by nghttp2 when a complete HTTP2 frame has been received. There are
1005 // only a handful of frame types that we care about handling here.
OnFrameReceive(nghttp2_session * handle,const nghttp2_frame * frame,void * user_data)1006 int Http2Session::OnFrameReceive(nghttp2_session* handle,
1007 const nghttp2_frame* frame,
1008 void* user_data) {
1009 Http2Session* session = static_cast<Http2Session*>(user_data);
1010 session->statistics_.frame_count++;
1011 Debug(session, "complete frame received: type: %d",
1012 frame->hd.type);
1013 switch (frame->hd.type) {
1014 case NGHTTP2_DATA:
1015 return session->HandleDataFrame(frame);
1016 case NGHTTP2_PUSH_PROMISE:
1017 // Intentional fall-through, handled just like headers frames
1018 case NGHTTP2_HEADERS:
1019 session->HandleHeadersFrame(frame);
1020 break;
1021 case NGHTTP2_SETTINGS:
1022 session->HandleSettingsFrame(frame);
1023 break;
1024 case NGHTTP2_PRIORITY:
1025 session->HandlePriorityFrame(frame);
1026 break;
1027 case NGHTTP2_GOAWAY:
1028 session->HandleGoawayFrame(frame);
1029 break;
1030 case NGHTTP2_PING:
1031 session->HandlePingFrame(frame);
1032 break;
1033 case NGHTTP2_ALTSVC:
1034 session->HandleAltSvcFrame(frame);
1035 break;
1036 case NGHTTP2_ORIGIN:
1037 session->HandleOriginFrame(frame);
1038 break;
1039 default:
1040 break;
1041 }
1042 return 0;
1043 }
1044
OnInvalidFrame(nghttp2_session * handle,const nghttp2_frame * frame,int lib_error_code,void * user_data)1045 int Http2Session::OnInvalidFrame(nghttp2_session* handle,
1046 const nghttp2_frame* frame,
1047 int lib_error_code,
1048 void* user_data) {
1049 Http2Session* session = static_cast<Http2Session*>(user_data);
1050
1051 Debug(session, "invalid frame received, code: %d", lib_error_code);
1052 if (session->invalid_frame_count_++ > 1000 &&
1053 !IsReverted(SECURITY_REVERT_CVE_2019_9514)) {
1054 return 1;
1055 }
1056
1057 // If the error is fatal or if error code is ERR_STREAM_CLOSED... emit error
1058 if (nghttp2_is_fatal(lib_error_code) ||
1059 lib_error_code == NGHTTP2_ERR_STREAM_CLOSED) {
1060 Environment* env = session->env();
1061 Isolate* isolate = env->isolate();
1062 HandleScope scope(isolate);
1063 Local<Context> context = env->context();
1064 Context::Scope context_scope(context);
1065 Local<Value> arg = Integer::New(isolate, lib_error_code);
1066 session->MakeCallback(env->error_string(), 1, &arg);
1067 }
1068 return 0;
1069 }
1070
1071 // If nghttp2 is unable to send a queued up frame, it will call this callback
1072 // to let us know. If the failure occurred because we are in the process of
1073 // closing down the session or stream, we go ahead and ignore it. We don't
1074 // really care about those and there's nothing we can reasonably do about it
1075 // anyway. Other types of failures are reported up to JavaScript. This should
1076 // be exceedingly rare.
OnFrameNotSent(nghttp2_session * handle,const nghttp2_frame * frame,int error_code,void * user_data)1077 int Http2Session::OnFrameNotSent(nghttp2_session* handle,
1078 const nghttp2_frame* frame,
1079 int error_code,
1080 void* user_data) {
1081 Http2Session* session = static_cast<Http2Session*>(user_data);
1082 Environment* env = session->env();
1083 Debug(session, "frame type %d was not sent, code: %d",
1084 frame->hd.type, error_code);
1085
1086 // Do not report if the frame was not sent due to the session closing
1087 if (error_code == NGHTTP2_ERR_SESSION_CLOSING ||
1088 error_code == NGHTTP2_ERR_STREAM_CLOSED ||
1089 error_code == NGHTTP2_ERR_STREAM_CLOSING ||
1090 session->js_fields_[kSessionFrameErrorListenerCount] == 0) {
1091 return 0;
1092 }
1093
1094 Isolate* isolate = env->isolate();
1095 HandleScope scope(isolate);
1096 Local<Context> context = env->context();
1097 Context::Scope context_scope(context);
1098
1099 Local<Value> argv[3] = {
1100 Integer::New(isolate, frame->hd.stream_id),
1101 Integer::New(isolate, frame->hd.type),
1102 Integer::New(isolate, error_code)
1103 };
1104 session->MakeCallback(env->onframeerror_string(), arraysize(argv), argv);
1105 return 0;
1106 }
1107
OnFrameSent(nghttp2_session * handle,const nghttp2_frame * frame,void * user_data)1108 int Http2Session::OnFrameSent(nghttp2_session* handle,
1109 const nghttp2_frame* frame,
1110 void* user_data) {
1111 Http2Session* session = static_cast<Http2Session*>(user_data);
1112 session->statistics_.frame_sent += 1;
1113 return 0;
1114 }
1115
1116 // Called by nghttp2 when a stream closes.
OnStreamClose(nghttp2_session * handle,int32_t id,uint32_t code,void * user_data)1117 int Http2Session::OnStreamClose(nghttp2_session* handle,
1118 int32_t id,
1119 uint32_t code,
1120 void* user_data) {
1121 Http2Session* session = static_cast<Http2Session*>(user_data);
1122 Environment* env = session->env();
1123 Isolate* isolate = env->isolate();
1124 HandleScope scope(isolate);
1125 Local<Context> context = env->context();
1126 Context::Scope context_scope(context);
1127 Debug(session, "stream %d closed with code: %d", id, code);
1128 Http2Stream* stream = session->FindStream(id);
1129 // Intentionally ignore the callback if the stream does not exist or has
1130 // already been destroyed
1131 if (stream == nullptr || stream->IsDestroyed())
1132 return 0;
1133
1134 stream->Close(code);
1135 // It is possible for the stream close to occur before the stream is
1136 // ever passed on to the javascript side. If that happens, skip straight
1137 // to destroying the stream. We can check this by looking for the
1138 // onstreamclose function. If it exists, then the stream has already
1139 // been passed on to javascript.
1140 Local<Value> fn =
1141 stream->object()->Get(context, env->onstreamclose_string())
1142 .ToLocalChecked();
1143
1144 if (!fn->IsFunction()) {
1145 stream->Destroy();
1146 return 0;
1147 }
1148
1149 Local<Value> arg = Integer::NewFromUnsigned(isolate, code);
1150 stream->MakeCallback(fn.As<Function>(), 1, &arg);
1151 return 0;
1152 }
1153
1154 // Called by nghttp2 when an invalid header has been received. For now, we
1155 // ignore these. If this callback was not provided, nghttp2 would handle
1156 // invalid headers strictly and would shut down the stream. We are intentionally
1157 // being more lenient here although we may want to revisit this choice later.
OnInvalidHeader(nghttp2_session * session,const nghttp2_frame * frame,nghttp2_rcbuf * name,nghttp2_rcbuf * value,uint8_t flags,void * user_data)1158 int Http2Session::OnInvalidHeader(nghttp2_session* session,
1159 const nghttp2_frame* frame,
1160 nghttp2_rcbuf* name,
1161 nghttp2_rcbuf* value,
1162 uint8_t flags,
1163 void* user_data) {
1164 // Ignore invalid header fields by default.
1165 return 0;
1166 }
1167
1168 // When nghttp2 receives a DATA frame, it will deliver the data payload to
1169 // us in discrete chunks. We push these into a linked list stored in the
1170 // Http2Sttream which is flushed out to JavaScript as quickly as possible.
1171 // This can be a particularly hot path.
OnDataChunkReceived(nghttp2_session * handle,uint8_t flags,int32_t id,const uint8_t * data,size_t len,void * user_data)1172 int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
1173 uint8_t flags,
1174 int32_t id,
1175 const uint8_t* data,
1176 size_t len,
1177 void* user_data) {
1178 Http2Session* session = static_cast<Http2Session*>(user_data);
1179 Debug(session, "buffering data chunk for stream %d, size: "
1180 "%d, flags: %d", id, len, flags);
1181 Environment* env = session->env();
1182 HandleScope scope(env->isolate());
1183
1184 // We should never actually get a 0-length chunk so this check is
1185 // only a precaution at this point.
1186 if (len == 0)
1187 return 0;
1188
1189 // Notify nghttp2 that we've consumed a chunk of data on the connection
1190 // so that it can send a WINDOW_UPDATE frame. This is a critical part of
1191 // the flow control process in http2
1192 CHECK_EQ(nghttp2_session_consume_connection(handle, len), 0);
1193 Http2Stream* stream = session->FindStream(id);
1194 // If the stream has been destroyed, ignore this chunk
1195 if (stream->IsDestroyed())
1196 return 0;
1197
1198 stream->statistics_.received_bytes += len;
1199
1200 // Repeatedly ask the stream's owner for memory, and copy the read data
1201 // into those buffers.
1202 // The typical case is actually the exception here; Http2StreamListeners
1203 // know about the HTTP2 session associated with this stream, so they know
1204 // about the larger from-socket read buffer, so they do not require copying.
1205 do {
1206 uv_buf_t buf = stream->EmitAlloc(len);
1207 ssize_t avail = len;
1208 if (static_cast<ssize_t>(buf.len) < avail)
1209 avail = buf.len;
1210
1211 // `buf.base == nullptr` is the default Http2StreamListener's way
1212 // of saying that it wants a pointer to the raw original.
1213 // Since it has access to the original socket buffer from which the data
1214 // was read in the first place, it can use that to minimize ArrayBuffer
1215 // allocations.
1216 if (LIKELY(buf.base == nullptr))
1217 buf.base = reinterpret_cast<char*>(const_cast<uint8_t*>(data));
1218 else
1219 memcpy(buf.base, data, avail);
1220 data += avail;
1221 len -= avail;
1222 stream->EmitRead(avail, buf);
1223
1224 // If the stream owner (e.g. the JS Http2Stream) wants more data, just
1225 // tell nghttp2 that all data has been consumed. Otherwise, defer until
1226 // more data is being requested.
1227 if (stream->IsReading())
1228 nghttp2_session_consume_stream(handle, id, avail);
1229 else
1230 stream->inbound_consumed_data_while_paused_ += avail;
1231
1232 // If we have a gathered a lot of data for output, try sending it now.
1233 if (session->outgoing_length_ > 4096 ||
1234 stream->available_outbound_length_ > 4096) {
1235 session->SendPendingData();
1236 }
1237 } while (len != 0);
1238
1239 // If we are currently waiting for a write operation to finish, we should
1240 // tell nghttp2 that we want to wait before we process more input data.
1241 if (session->flags_ & SESSION_STATE_WRITE_IN_PROGRESS) {
1242 CHECK_NE(session->flags_ & SESSION_STATE_READING_STOPPED, 0);
1243 session->flags_ |= SESSION_STATE_NGHTTP2_RECV_PAUSED;
1244 Debug(session, "receive paused");
1245 return NGHTTP2_ERR_PAUSE;
1246 }
1247
1248 return 0;
1249 }
1250
1251 // Called by nghttp2 when it needs to determine how much padding to use in
1252 // a DATA or HEADERS frame.
OnSelectPadding(nghttp2_session * handle,const nghttp2_frame * frame,size_t maxPayloadLen,void * user_data)1253 ssize_t Http2Session::OnSelectPadding(nghttp2_session* handle,
1254 const nghttp2_frame* frame,
1255 size_t maxPayloadLen,
1256 void* user_data) {
1257 Http2Session* session = static_cast<Http2Session*>(user_data);
1258 ssize_t padding = frame->hd.length;
1259
1260 switch (session->padding_strategy_) {
1261 case PADDING_STRATEGY_NONE:
1262 // Fall-through
1263 break;
1264 case PADDING_STRATEGY_MAX:
1265 padding = session->OnMaxFrameSizePadding(padding, maxPayloadLen);
1266 break;
1267 case PADDING_STRATEGY_ALIGNED:
1268 padding = session->OnDWordAlignedPadding(padding, maxPayloadLen);
1269 break;
1270 case PADDING_STRATEGY_CALLBACK:
1271 padding = session->OnCallbackPadding(padding, maxPayloadLen);
1272 break;
1273 }
1274 return padding;
1275 }
1276
1277 #define BAD_PEER_MESSAGE "Remote peer returned unexpected data while we " \
1278 "expected SETTINGS frame. Perhaps, peer does not " \
1279 "support HTTP/2 properly."
1280
1281 // We use this currently to determine when an attempt is made to use the http2
1282 // protocol with a non-http2 peer.
OnNghttpError(nghttp2_session * handle,const char * message,size_t len,void * user_data)1283 int Http2Session::OnNghttpError(nghttp2_session* handle,
1284 const char* message,
1285 size_t len,
1286 void* user_data) {
1287 // Unfortunately, this is currently the only way for us to know if
1288 // the session errored because the peer is not an http2 peer.
1289 Http2Session* session = static_cast<Http2Session*>(user_data);
1290 Debug(session, "Error '%.*s'", len, message);
1291 if (strncmp(message, BAD_PEER_MESSAGE, len) == 0) {
1292 Environment* env = session->env();
1293 Isolate* isolate = env->isolate();
1294 HandleScope scope(isolate);
1295 Local<Context> context = env->context();
1296 Context::Scope context_scope(context);
1297 Local<Value> arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1298 session->MakeCallback(env->error_string(), 1, &arg);
1299 }
1300 return 0;
1301 }
1302
OnStreamAlloc(size_t size)1303 uv_buf_t Http2StreamListener::OnStreamAlloc(size_t size) {
1304 // See the comments in Http2Session::OnDataChunkReceived
1305 // (which is the only possible call site for this method).
1306 return uv_buf_init(nullptr, size);
1307 }
1308
OnStreamRead(ssize_t nread,const uv_buf_t & buf)1309 void Http2StreamListener::OnStreamRead(ssize_t nread, const uv_buf_t& buf) {
1310 Http2Stream* stream = static_cast<Http2Stream*>(stream_);
1311 Http2Session* session = stream->session();
1312 Environment* env = stream->env();
1313 HandleScope handle_scope(env->isolate());
1314 Context::Scope context_scope(env->context());
1315
1316 if (nread < 0) {
1317 PassReadErrorToPreviousListener(nread);
1318 return;
1319 }
1320
1321 Local<ArrayBuffer> ab;
1322 if (session->stream_buf_ab_.IsEmpty()) {
1323 ab = ArrayBuffer::New(env->isolate(),
1324 session->stream_buf_allocation_.base,
1325 session->stream_buf_allocation_.len,
1326 v8::ArrayBufferCreationMode::kInternalized);
1327 session->stream_buf_allocation_ = uv_buf_init(nullptr, 0);
1328 session->stream_buf_ab_.Reset(env->isolate(), ab);
1329 } else {
1330 ab = session->stream_buf_ab_.Get(env->isolate());
1331 }
1332
1333 // There is a single large array buffer for the entire data read from the
1334 // network; create a slice of that array buffer and emit it as the
1335 // received data buffer.
1336 size_t offset = buf.base - session->stream_buf_.base;
1337
1338 // Verify that the data offset is inside the current read buffer.
1339 CHECK_GE(offset, session->stream_buf_offset_);
1340 CHECK_LE(offset, session->stream_buf_.len);
1341 CHECK_LE(offset + buf.len, session->stream_buf_.len);
1342
1343 Local<Object> buffer =
1344 Buffer::New(env, ab, offset, nread).ToLocalChecked();
1345
1346 stream->CallJSOnreadMethod(nread, buffer);
1347 }
1348
1349
1350 // Called by OnFrameReceived to notify JavaScript land that a complete
1351 // HEADERS frame has been received and processed. This method converts the
1352 // received headers into a JavaScript array and pushes those out to JS.
HandleHeadersFrame(const nghttp2_frame * frame)1353 void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
1354 Isolate* isolate = env()->isolate();
1355 HandleScope scope(isolate);
1356 Local<Context> context = env()->context();
1357 Context::Scope context_scope(context);
1358
1359 int32_t id = GetFrameID(frame);
1360 Debug(this, "handle headers frame for stream %d", id);
1361 Http2Stream* stream = FindStream(id);
1362
1363 // If the stream has already been destroyed, ignore.
1364 if (stream->IsDestroyed())
1365 return;
1366
1367 std::vector<nghttp2_header> headers(stream->move_headers());
1368 DecrementCurrentSessionMemory(stream->current_headers_length_);
1369 stream->current_headers_length_ = 0;
1370
1371 Local<String> name_str;
1372 Local<String> value_str;
1373
1374 Local<Array> holder = Array::New(isolate);
1375 Local<Function> fn = env()->push_values_to_array_function();
1376 Local<Value> argv[NODE_PUSH_VAL_TO_ARRAY_MAX * 2];
1377
1378 // The headers are passed in above as a queue of nghttp2_header structs.
1379 // The following converts that into a JS array with the structure:
1380 // [name1, value1, name2, value2, name3, value3, name3, value4] and so on.
1381 // That array is passed up to the JS layer and converted into an Object form
1382 // like {name1: value1, name2: value2, name3: [value3, value4]}. We do it
1383 // this way for performance reasons (it's faster to generate and pass an
1384 // array than it is to generate and pass the object).
1385 size_t n = 0;
1386 while (n < headers.size()) {
1387 size_t j = 0;
1388 while (n < headers.size() && j < arraysize(argv) / 2) {
1389 nghttp2_header item = headers[n++];
1390 // The header name and value are passed as external one-byte strings
1391 name_str =
1392 ExternalHeader::New<true>(this, item.name).ToLocalChecked();
1393 value_str =
1394 ExternalHeader::New<false>(this, item.value).ToLocalChecked();
1395 argv[j * 2] = name_str;
1396 argv[j * 2 + 1] = value_str;
1397 j++;
1398 }
1399 // For performance, we pass name and value pairs to array.protototype.push
1400 // in batches of size NODE_PUSH_VAL_TO_ARRAY_MAX * 2 until there are no
1401 // more items to push.
1402 if (j > 0) {
1403 fn->Call(env()->context(), holder, j * 2, argv).ToLocalChecked();
1404 }
1405 }
1406
1407 Local<Value> args[5] = {
1408 stream->object(),
1409 Integer::New(isolate, id),
1410 Integer::New(isolate, stream->headers_category()),
1411 Integer::New(isolate, frame->hd.flags),
1412 holder
1413 };
1414 MakeCallback(env()->onheaders_string(), arraysize(args), args);
1415 }
1416
1417
1418 // Called by OnFrameReceived when a complete PRIORITY frame has been
1419 // received. Notifies JS land about the priority change. Note that priorities
1420 // are considered advisory only, so this has no real effect other than to
1421 // simply let user code know that the priority has changed.
HandlePriorityFrame(const nghttp2_frame * frame)1422 void Http2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
1423 if (js_fields_[kSessionPriorityListenerCount] == 0) return;
1424 Isolate* isolate = env()->isolate();
1425 HandleScope scope(isolate);
1426 Local<Context> context = env()->context();
1427 Context::Scope context_scope(context);
1428
1429 nghttp2_priority priority_frame = frame->priority;
1430 int32_t id = GetFrameID(frame);
1431 Debug(this, "handle priority frame for stream %d", id);
1432 // Priority frame stream ID should never be <= 0. nghttp2 handles this for us
1433 nghttp2_priority_spec spec = priority_frame.pri_spec;
1434
1435 Local<Value> argv[4] = {
1436 Integer::New(isolate, id),
1437 Integer::New(isolate, spec.stream_id),
1438 Integer::New(isolate, spec.weight),
1439 Boolean::New(isolate, spec.exclusive)
1440 };
1441 MakeCallback(env()->onpriority_string(), arraysize(argv), argv);
1442 }
1443
1444
1445 // Called by OnFrameReceived when a complete DATA frame has been received.
1446 // If we know that this was the last DATA frame (because the END_STREAM flag
1447 // is set), then we'll terminate the readable side of the StreamBase.
HandleDataFrame(const nghttp2_frame * frame)1448 int Http2Session::HandleDataFrame(const nghttp2_frame* frame) {
1449 int32_t id = GetFrameID(frame);
1450 Debug(this, "handling data frame for stream %d", id);
1451 Http2Stream* stream = FindStream(id);
1452
1453 if (!stream->IsDestroyed() && frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
1454 stream->EmitRead(UV_EOF);
1455 } else if (frame->hd.length == 0 &&
1456 !IsReverted(SECURITY_REVERT_CVE_2019_9518)) {
1457 return 1; // Consider 0-length frame without END_STREAM an error.
1458 }
1459 return 0;
1460 }
1461
1462
1463 // Called by OnFrameReceived when a complete GOAWAY frame has been received.
HandleGoawayFrame(const nghttp2_frame * frame)1464 void Http2Session::HandleGoawayFrame(const nghttp2_frame* frame) {
1465 Isolate* isolate = env()->isolate();
1466 HandleScope scope(isolate);
1467 Local<Context> context = env()->context();
1468 Context::Scope context_scope(context);
1469
1470 nghttp2_goaway goaway_frame = frame->goaway;
1471 Debug(this, "handling goaway frame");
1472
1473 Local<Value> argv[3] = {
1474 Integer::NewFromUnsigned(isolate, goaway_frame.error_code),
1475 Integer::New(isolate, goaway_frame.last_stream_id),
1476 Undefined(isolate)
1477 };
1478
1479 size_t length = goaway_frame.opaque_data_len;
1480 if (length > 0) {
1481 argv[2] = Buffer::Copy(isolate,
1482 reinterpret_cast<char*>(goaway_frame.opaque_data),
1483 length).ToLocalChecked();
1484 }
1485
1486 MakeCallback(env()->ongoawaydata_string(), arraysize(argv), argv);
1487 }
1488
1489 // Called by OnFrameReceived when a complete ALTSVC frame has been received.
HandleAltSvcFrame(const nghttp2_frame * frame)1490 void Http2Session::HandleAltSvcFrame(const nghttp2_frame* frame) {
1491 if (!(js_fields_[kBitfield] & (1 << kSessionHasAltsvcListeners))) return;
1492 Isolate* isolate = env()->isolate();
1493 HandleScope scope(isolate);
1494 Local<Context> context = env()->context();
1495 Context::Scope context_scope(context);
1496
1497 int32_t id = GetFrameID(frame);
1498
1499 nghttp2_extension ext = frame->ext;
1500 nghttp2_ext_altsvc* altsvc = static_cast<nghttp2_ext_altsvc*>(ext.payload);
1501 Debug(this, "handling altsvc frame");
1502
1503 Local<Value> argv[3] = {
1504 Integer::New(isolate, id),
1505 String::NewFromOneByte(isolate,
1506 altsvc->origin,
1507 NewStringType::kNormal,
1508 altsvc->origin_len).ToLocalChecked(),
1509 String::NewFromOneByte(isolate,
1510 altsvc->field_value,
1511 NewStringType::kNormal,
1512 altsvc->field_value_len).ToLocalChecked(),
1513 };
1514
1515 MakeCallback(env()->onaltsvc_string(), arraysize(argv), argv);
1516 }
1517
HandleOriginFrame(const nghttp2_frame * frame)1518 void Http2Session::HandleOriginFrame(const nghttp2_frame* frame) {
1519 Isolate* isolate = env()->isolate();
1520 HandleScope scope(isolate);
1521 Local<Context> context = env()->context();
1522 Context::Scope context_scope(context);
1523
1524 Debug(this, "handling origin frame");
1525
1526 nghttp2_extension ext = frame->ext;
1527 nghttp2_ext_origin* origin = static_cast<nghttp2_ext_origin*>(ext.payload);
1528
1529 Local<Value> holder = Array::New(isolate);
1530 Local<Function> fn = env()->push_values_to_array_function();
1531 Local<Value> argv[NODE_PUSH_VAL_TO_ARRAY_MAX];
1532
1533 size_t n = 0;
1534 while (n < origin->nov) {
1535 size_t j = 0;
1536 while (n < origin->nov && j < arraysize(argv)) {
1537 auto entry = origin->ov[n++];
1538 argv[j++] =
1539 String::NewFromOneByte(isolate,
1540 entry.origin,
1541 NewStringType::kNormal,
1542 entry.origin_len).ToLocalChecked();
1543 }
1544 if (j > 0)
1545 fn->Call(context, holder, j, argv).ToLocalChecked();
1546 }
1547
1548 MakeCallback(env()->onorigin_string(), 1, &holder);
1549 }
1550
1551 // Called by OnFrameReceived when a complete PING frame has been received.
HandlePingFrame(const nghttp2_frame * frame)1552 void Http2Session::HandlePingFrame(const nghttp2_frame* frame) {
1553 Isolate* isolate = env()->isolate();
1554 HandleScope scope(isolate);
1555 Local<Context> context = env()->context();
1556 Context::Scope context_scope(context);
1557 Local<Value> arg;
1558 bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
1559 if (ack) {
1560 Http2Ping* ping = PopPing();
1561
1562 if (ping == nullptr) {
1563 // PING Ack is unsolicited. Treat as a connection error. The HTTP/2
1564 // spec does not require this, but there is no legitimate reason to
1565 // receive an unsolicited PING ack on a connection. Either the peer
1566 // is buggy or malicious, and we're not going to tolerate such
1567 // nonsense.
1568 arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1569 MakeCallback(env()->error_string(), 1, &arg);
1570 return;
1571 }
1572
1573 ping->Done(true, frame->ping.opaque_data);
1574 return;
1575 }
1576
1577 if (!(js_fields_[kBitfield] & (1 << kSessionHasPingListeners))) return;
1578 // Notify the session that a ping occurred
1579 arg = Buffer::Copy(env(),
1580 reinterpret_cast<const char*>(frame->ping.opaque_data),
1581 8).ToLocalChecked();
1582 MakeCallback(env()->onping_string(), 1, &arg);
1583 }
1584
1585 // Called by OnFrameReceived when a complete SETTINGS frame has been received.
HandleSettingsFrame(const nghttp2_frame * frame)1586 void Http2Session::HandleSettingsFrame(const nghttp2_frame* frame) {
1587 bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
1588 if (!ack) {
1589 js_fields_[kBitfield] &= ~(1 << kSessionRemoteSettingsIsUpToDate);
1590 if (!(js_fields_[kBitfield] & (1 << kSessionHasRemoteSettingsListeners)))
1591 return;
1592 // This is not a SETTINGS acknowledgement, notify and return
1593 MakeCallback(env()->onsettings_string(), 0, nullptr);
1594 return;
1595 }
1596
1597 // If this is an acknowledgement, we should have an Http2Settings
1598 // object for it.
1599 Http2Settings* settings = PopSettings();
1600 if (settings != nullptr) {
1601 settings->Done(true);
1602 return;
1603 }
1604 // SETTINGS Ack is unsolicited. Treat as a connection error. The HTTP/2
1605 // spec does not require this, but there is no legitimate reason to
1606 // receive an unsolicited SETTINGS ack on a connection. Either the peer
1607 // is buggy or malicious, and we're not going to tolerate such
1608 // nonsense.
1609 // Note that nghttp2 currently prevents this from happening for SETTINGS
1610 // frames, so this block is purely defensive just in case that behavior
1611 // changes. Specifically, unlike unsolicited PING acks, unsolicited
1612 // SETTINGS acks should *never* make it this far.
1613 Isolate* isolate = env()->isolate();
1614 HandleScope scope(isolate);
1615 Local<Context> context = env()->context();
1616 Context::Scope context_scope(context);
1617 Local<Value> arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1618 MakeCallback(env()->error_string(), 1, &arg);
1619 }
1620
1621 // Callback used when data has been written to the stream.
OnStreamAfterWrite(WriteWrap * w,int status)1622 void Http2Session::OnStreamAfterWrite(WriteWrap* w, int status) {
1623 Debug(this, "write finished with status %d", status);
1624
1625 CHECK_NE(flags_ & SESSION_STATE_WRITE_IN_PROGRESS, 0);
1626 flags_ &= ~SESSION_STATE_WRITE_IN_PROGRESS;
1627
1628 // Inform all pending writes about their completion.
1629 ClearOutgoing(status);
1630
1631 if ((flags_ & SESSION_STATE_READING_STOPPED) &&
1632 !(flags_ & SESSION_STATE_WRITE_IN_PROGRESS) &&
1633 nghttp2_session_want_read(session_)) {
1634 flags_ &= ~SESSION_STATE_READING_STOPPED;
1635 stream_->ReadStart();
1636 }
1637
1638 // If there is more incoming data queued up, consume it.
1639 if (stream_buf_offset_ > 0) {
1640 ConsumeHTTP2Data();
1641 }
1642
1643 if (!(flags_ & SESSION_STATE_WRITE_SCHEDULED)) {
1644 // Schedule a new write if nghttp2 wants to send data.
1645 MaybeScheduleWrite();
1646 }
1647 }
1648
1649 // If the underlying nghttp2_session struct has data pending in its outbound
1650 // queue, MaybeScheduleWrite will schedule a SendPendingData() call to occur
1651 // on the next iteration of the Node.js event loop (using the SetImmediate
1652 // queue), but only if a write has not already been scheduled.
MaybeScheduleWrite()1653 void Http2Session::MaybeScheduleWrite() {
1654 CHECK_EQ(flags_ & SESSION_STATE_WRITE_SCHEDULED, 0);
1655 if (UNLIKELY(session_ == nullptr))
1656 return;
1657
1658 if (nghttp2_session_want_write(session_)) {
1659 HandleScope handle_scope(env()->isolate());
1660 Debug(this, "scheduling write");
1661 flags_ |= SESSION_STATE_WRITE_SCHEDULED;
1662 env()->SetImmediate([](Environment* env, void* data) {
1663 Http2Session* session = static_cast<Http2Session*>(data);
1664 if (session->session_ == nullptr ||
1665 !(session->flags_ & SESSION_STATE_WRITE_SCHEDULED)) {
1666 // This can happen e.g. when a stream was reset before this turn
1667 // of the event loop, in which case SendPendingData() is called early,
1668 // or the session was destroyed in the meantime.
1669 return;
1670 }
1671
1672 // Sending data may call arbitrary JS code, so keep track of
1673 // async context.
1674 HandleScope handle_scope(env->isolate());
1675 InternalCallbackScope callback_scope(session);
1676 session->SendPendingData();
1677 }, static_cast<void*>(this), object());
1678 }
1679 }
1680
MaybeStopReading()1681 void Http2Session::MaybeStopReading() {
1682 if (flags_ & SESSION_STATE_READING_STOPPED) return;
1683 int want_read = nghttp2_session_want_read(session_);
1684 Debug(this, "wants read? %d", want_read);
1685 if (want_read == 0 || (flags_ & SESSION_STATE_WRITE_IN_PROGRESS)) {
1686 flags_ |= SESSION_STATE_READING_STOPPED;
1687 stream_->ReadStop();
1688 }
1689 }
1690
1691 // Unset the sending state, finish up all current writes, and reset
1692 // storage for data and metadata that was associated with these writes.
ClearOutgoing(int status)1693 void Http2Session::ClearOutgoing(int status) {
1694 CHECK_NE(flags_ & SESSION_STATE_SENDING, 0);
1695
1696 flags_ &= ~SESSION_STATE_SENDING;
1697
1698 if (outgoing_buffers_.size() > 0) {
1699 outgoing_storage_.clear();
1700 outgoing_length_ = 0;
1701
1702 std::vector<nghttp2_stream_write> current_outgoing_buffers_;
1703 current_outgoing_buffers_.swap(outgoing_buffers_);
1704 for (const nghttp2_stream_write& wr : current_outgoing_buffers_) {
1705 WriteWrap* wrap = wr.req_wrap;
1706 if (wrap != nullptr)
1707 wrap->Done(status);
1708 }
1709 }
1710
1711 // Now that we've finished sending queued data, if there are any pending
1712 // RstStreams we should try sending again and then flush them one by one.
1713 if (pending_rst_streams_.size() > 0) {
1714 std::vector<int32_t> current_pending_rst_streams;
1715 pending_rst_streams_.swap(current_pending_rst_streams);
1716
1717 SendPendingData();
1718
1719 for (int32_t stream_id : current_pending_rst_streams) {
1720 Http2Stream* stream = FindStream(stream_id);
1721 if (LIKELY(stream != nullptr))
1722 stream->FlushRstStream();
1723 }
1724 }
1725 }
1726
PushOutgoingBuffer(nghttp2_stream_write && write)1727 void Http2Session::PushOutgoingBuffer(nghttp2_stream_write&& write) {
1728 outgoing_length_ += write.buf.len;
1729 outgoing_buffers_.emplace_back(std::move(write));
1730 }
1731
1732 // Queue a given block of data for sending. This always creates a copy,
1733 // so it is used for the cases in which nghttp2 requests sending of a
1734 // small chunk of data.
CopyDataIntoOutgoing(const uint8_t * src,size_t src_length)1735 void Http2Session::CopyDataIntoOutgoing(const uint8_t* src, size_t src_length) {
1736 size_t offset = outgoing_storage_.size();
1737 outgoing_storage_.resize(offset + src_length);
1738 memcpy(&outgoing_storage_[offset], src, src_length);
1739
1740 // Store with a base of `nullptr` initially, since future resizes
1741 // of the outgoing_buffers_ vector may invalidate the pointer.
1742 // The correct base pointers will be set later, before writing to the
1743 // underlying socket.
1744 PushOutgoingBuffer(nghttp2_stream_write {
1745 uv_buf_init(nullptr, src_length)
1746 });
1747 }
1748
1749 // Prompts nghttp2 to begin serializing it's pending data and pushes each
1750 // chunk out to the i/o socket to be sent. This is a particularly hot method
1751 // that will generally be called at least twice be event loop iteration.
1752 // This is a potential performance optimization target later.
1753 // Returns non-zero value if a write is already in progress.
SendPendingData()1754 uint8_t Http2Session::SendPendingData() {
1755 Debug(this, "sending pending data");
1756 // Do not attempt to send data on the socket if the destroying flag has
1757 // been set. That means everything is shutting down and the socket
1758 // will not be usable.
1759 if (IsDestroyed())
1760 return 0;
1761 flags_ &= ~SESSION_STATE_WRITE_SCHEDULED;
1762
1763 // SendPendingData should not be called recursively.
1764 if (flags_ & SESSION_STATE_SENDING)
1765 return 1;
1766 // This is cleared by ClearOutgoing().
1767 flags_ |= SESSION_STATE_SENDING;
1768
1769 ssize_t src_length;
1770 const uint8_t* src;
1771
1772 CHECK_EQ(outgoing_buffers_.size(), 0);
1773 CHECK_EQ(outgoing_storage_.size(), 0);
1774
1775 // Part One: Gather data from nghttp2
1776
1777 while ((src_length = nghttp2_session_mem_send(session_, &src)) > 0) {
1778 Debug(this, "nghttp2 has %d bytes to send", src_length);
1779 CopyDataIntoOutgoing(src, src_length);
1780 }
1781
1782 CHECK_NE(src_length, NGHTTP2_ERR_NOMEM);
1783
1784 if (stream_ == nullptr) {
1785 // It would seem nice to bail out earlier, but `nghttp2_session_mem_send()`
1786 // does take care of things like closing the individual streams after
1787 // a socket has been torn down, so we still need to call it.
1788 ClearOutgoing(UV_ECANCELED);
1789 return 0;
1790 }
1791
1792 // Part Two: Pass Data to the underlying stream
1793
1794 size_t count = outgoing_buffers_.size();
1795 if (count == 0) {
1796 ClearOutgoing(0);
1797 return 0;
1798 }
1799 MaybeStackBuffer<uv_buf_t, 32> bufs;
1800 bufs.AllocateSufficientStorage(count);
1801
1802 // Set the buffer base pointers for copied data that ended up in the
1803 // sessions's own storage since it might have shifted around during gathering.
1804 // (Those are marked by having .base == nullptr.)
1805 size_t offset = 0;
1806 size_t i = 0;
1807 for (const nghttp2_stream_write& write : outgoing_buffers_) {
1808 statistics_.data_sent += write.buf.len;
1809 if (write.buf.base == nullptr) {
1810 bufs[i++] = uv_buf_init(
1811 reinterpret_cast<char*>(outgoing_storage_.data() + offset),
1812 write.buf.len);
1813 offset += write.buf.len;
1814 } else {
1815 bufs[i++] = write.buf;
1816 }
1817 }
1818
1819 chunks_sent_since_last_write_++;
1820
1821 CHECK_EQ(flags_ & SESSION_STATE_WRITE_IN_PROGRESS, 0);
1822 flags_ |= SESSION_STATE_WRITE_IN_PROGRESS;
1823 StreamWriteResult res = underlying_stream()->Write(*bufs, count);
1824 if (!res.async) {
1825 flags_ &= ~SESSION_STATE_WRITE_IN_PROGRESS;
1826 ClearOutgoing(res.err);
1827 }
1828
1829 MaybeStopReading();
1830
1831 return 0;
1832 }
1833
1834
1835 // This callback is called from nghttp2 when it wants to send DATA frames for a
1836 // given Http2Stream, when we set the `NGHTTP2_DATA_FLAG_NO_COPY` flag earlier
1837 // in the Http2Stream::Provider::Stream::OnRead callback.
1838 // We take the write information directly out of the stream's data queue.
OnSendData(nghttp2_session * session_,nghttp2_frame * frame,const uint8_t * framehd,size_t length,nghttp2_data_source * source,void * user_data)1839 int Http2Session::OnSendData(
1840 nghttp2_session* session_,
1841 nghttp2_frame* frame,
1842 const uint8_t* framehd,
1843 size_t length,
1844 nghttp2_data_source* source,
1845 void* user_data) {
1846 Http2Session* session = static_cast<Http2Session*>(user_data);
1847 Http2Stream* stream = GetStream(session, frame->hd.stream_id, source);
1848
1849 // Send the frame header + a byte that indicates padding length.
1850 session->CopyDataIntoOutgoing(framehd, 9);
1851 if (frame->data.padlen > 0) {
1852 uint8_t padding_byte = frame->data.padlen - 1;
1853 CHECK_EQ(padding_byte, frame->data.padlen - 1);
1854 session->CopyDataIntoOutgoing(&padding_byte, 1);
1855 }
1856
1857 Debug(session, "nghttp2 has %d bytes to send directly", length);
1858 while (length > 0) {
1859 // nghttp2 thinks that there is data available (length > 0), which means
1860 // we told it so, which means that we *should* have data available.
1861 CHECK(!stream->queue_.empty());
1862
1863 nghttp2_stream_write& write = stream->queue_.front();
1864 if (write.buf.len <= length) {
1865 // This write does not suffice by itself, so we can consume it completely.
1866 length -= write.buf.len;
1867 session->PushOutgoingBuffer(std::move(write));
1868 stream->queue_.pop();
1869 continue;
1870 }
1871
1872 // Slice off `length` bytes of the first write in the queue.
1873 session->PushOutgoingBuffer(nghttp2_stream_write {
1874 uv_buf_init(write.buf.base, length)
1875 });
1876 write.buf.base += length;
1877 write.buf.len -= length;
1878 break;
1879 }
1880
1881 if (frame->data.padlen > 0) {
1882 // Send padding if that was requested.
1883 session->PushOutgoingBuffer(nghttp2_stream_write {
1884 uv_buf_init(const_cast<char*>(zero_bytes_256), frame->data.padlen - 1)
1885 });
1886 }
1887
1888 return 0;
1889 }
1890
1891 // Creates a new Http2Stream and submits a new http2 request.
SubmitRequest(nghttp2_priority_spec * prispec,nghttp2_nv * nva,size_t len,int32_t * ret,int options)1892 Http2Stream* Http2Session::SubmitRequest(
1893 nghttp2_priority_spec* prispec,
1894 nghttp2_nv* nva,
1895 size_t len,
1896 int32_t* ret,
1897 int options) {
1898 Debug(this, "submitting request");
1899 Http2Scope h2scope(this);
1900 Http2Stream* stream = nullptr;
1901 Http2Stream::Provider::Stream prov(options);
1902 *ret = nghttp2_submit_request(session_, prispec, nva, len, *prov, nullptr);
1903 CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
1904 if (LIKELY(*ret > 0))
1905 stream = Http2Stream::New(this, *ret, NGHTTP2_HCAT_HEADERS, options);
1906 return stream;
1907 }
1908
1909 // Callback used to receive inbound data from the i/o stream
OnStreamRead(ssize_t nread,const uv_buf_t & buf_)1910 void Http2Session::OnStreamRead(ssize_t nread, const uv_buf_t& buf_) {
1911 HandleScope handle_scope(env()->isolate());
1912 Context::Scope context_scope(env()->context());
1913 Http2Scope h2scope(this);
1914 CHECK_NOT_NULL(stream_);
1915 Debug(this, "receiving %d bytes", nread);
1916
1917 // Only pass data on if nread > 0
1918 if (nread <= 0) {
1919 free(buf_.base);
1920 if (nread < 0) {
1921 PassReadErrorToPreviousListener(nread);
1922 }
1923 return;
1924 }
1925
1926 uv_buf_t buf = buf_;
1927
1928 statistics_.data_received += nread;
1929
1930 if (UNLIKELY(stream_buf_offset_ > 0)) {
1931 // This is a very unlikely case, and should only happen if the ReadStart()
1932 // call in OnStreamAfterWrite() immediately provides data. If that does
1933 // happen, we concatenate the data we received with the already-stored
1934 // pending input data, slicing off the already processed part.
1935 char* new_buf = Malloc(stream_buf_.len - stream_buf_offset_ + nread);
1936 memcpy(new_buf,
1937 stream_buf_.base + stream_buf_offset_,
1938 stream_buf_.len - stream_buf_offset_);
1939 memcpy(new_buf + stream_buf_.len - stream_buf_offset_,
1940 buf.base,
1941 nread);
1942 free(buf.base);
1943 nread = stream_buf_.len - stream_buf_offset_ + nread;
1944 buf = uv_buf_init(new_buf, nread);
1945 stream_buf_offset_ = 0;
1946 stream_buf_ab_.Reset();
1947
1948 // We have now fully processed the stream_buf_ input chunk (by moving the
1949 // remaining part into buf, which will be accounted for below).
1950 DecrementCurrentSessionMemory(stream_buf_.len);
1951 }
1952
1953 // Shrink to the actual amount of used data.
1954 buf.base = Realloc(buf.base, nread);
1955 IncrementCurrentSessionMemory(nread);
1956
1957 // Remember the current buffer, so that OnDataChunkReceived knows the
1958 // offset of a DATA frame's data into the socket read buffer.
1959 stream_buf_ = uv_buf_init(buf.base, nread);
1960
1961 // Verify that currently: There is memory allocated into which
1962 // the data has been read, and that memory buffer is at least as large
1963 // as the amount of data we have read, but we have not yet made an
1964 // ArrayBuffer out of it.
1965 CHECK_LE(static_cast<size_t>(nread), stream_buf_.len);
1966
1967 Isolate* isolate = env()->isolate();
1968
1969 // Store this so we can create an ArrayBuffer for read data from it.
1970 // DATA frames will be emitted as slices of that ArrayBuffer to avoid having
1971 // to copy memory.
1972 stream_buf_allocation_ = buf;
1973
1974 ssize_t ret = ConsumeHTTP2Data();
1975
1976 if (UNLIKELY(ret < 0)) {
1977 Debug(this, "fatal error receiving data: %d", ret);
1978 Local<Value> arg = Integer::New(isolate, ret);
1979 MakeCallback(env()->error_string(), 1, &arg);
1980 return;
1981 }
1982
1983 MaybeStopReading();
1984 }
1985
HasWritesOnSocketForStream(Http2Stream * stream)1986 bool Http2Session::HasWritesOnSocketForStream(Http2Stream* stream) {
1987 for (const nghttp2_stream_write& wr : outgoing_buffers_) {
1988 if (wr.req_wrap != nullptr && wr.req_wrap->stream() == stream)
1989 return true;
1990 }
1991 return false;
1992 }
1993
1994 // Every Http2Session session is tightly bound to a single i/o StreamBase
1995 // (typically a net.Socket or tls.TLSSocket). The lifecycle of the two is
1996 // tightly coupled with all data transfer between the two happening at the
1997 // C++ layer via the StreamBase API.
Consume(Local<External> external)1998 void Http2Session::Consume(Local<External> external) {
1999 StreamBase* stream = static_cast<StreamBase*>(external->Value());
2000 stream->PushStreamListener(this);
2001 Debug(this, "i/o stream consumed");
2002 }
2003
New(Http2Session * session,int32_t id,nghttp2_headers_category category,int options)2004 Http2Stream* Http2Stream::New(Http2Session* session,
2005 int32_t id,
2006 nghttp2_headers_category category,
2007 int options) {
2008 Local<Object> obj;
2009 if (!session->env()
2010 ->http2stream_constructor_template()
2011 ->NewInstance(session->env()->context())
2012 .ToLocal(&obj)) {
2013 return nullptr;
2014 }
2015 return new Http2Stream(session, obj, id, category, options);
2016 }
2017
Http2Stream(Http2Session * session,Local<Object> obj,int32_t id,nghttp2_headers_category category,int options)2018 Http2Stream::Http2Stream(Http2Session* session,
2019 Local<Object> obj,
2020 int32_t id,
2021 nghttp2_headers_category category,
2022 int options)
2023 : AsyncWrap(session->env(), obj, AsyncWrap::PROVIDER_HTTP2STREAM),
2024 StreamBase(session->env()),
2025 session_(session),
2026 id_(id),
2027 current_headers_category_(category) {
2028 MakeWeak();
2029 statistics_.start_time = uv_hrtime();
2030
2031 // Limit the number of header pairs
2032 max_header_pairs_ = session->GetMaxHeaderPairs();
2033 if (max_header_pairs_ == 0) {
2034 max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
2035 }
2036 current_headers_.reserve(std::min(max_header_pairs_, 12u));
2037
2038 // Limit the number of header octets
2039 max_header_length_ =
2040 std::min(
2041 nghttp2_session_get_local_settings(
2042 session->session(),
2043 NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE),
2044 MAX_MAX_HEADER_LIST_SIZE);
2045
2046 if (options & STREAM_OPTION_GET_TRAILERS)
2047 flags_ |= NGHTTP2_STREAM_FLAG_TRAILERS;
2048
2049 PushStreamListener(&stream_listener_);
2050
2051 if (options & STREAM_OPTION_EMPTY_PAYLOAD)
2052 Shutdown();
2053 session->AddStream(this);
2054 }
2055
2056
~Http2Stream()2057 Http2Stream::~Http2Stream() {
2058 for (nghttp2_header& header : current_headers_) {
2059 nghttp2_rcbuf_decref(header.name);
2060 nghttp2_rcbuf_decref(header.value);
2061 }
2062
2063 if (session_ == nullptr)
2064 return;
2065 Debug(this, "tearing down stream");
2066 session_->DecrementCurrentSessionMemory(current_headers_length_);
2067 session_->RemoveStream(this);
2068 session_ = nullptr;
2069 }
2070
diagnostic_name() const2071 std::string Http2Stream::diagnostic_name() const {
2072 return "HttpStream " + std::to_string(id()) + " (" +
2073 std::to_string(static_cast<int64_t>(get_async_id())) + ") [" +
2074 session()->diagnostic_name() + "]";
2075 }
2076
2077 // Notify the Http2Stream that a new block of HEADERS is being processed.
StartHeaders(nghttp2_headers_category category)2078 void Http2Stream::StartHeaders(nghttp2_headers_category category) {
2079 Debug(this, "starting headers, category: %d", id_, category);
2080 CHECK(!this->IsDestroyed());
2081 session_->DecrementCurrentSessionMemory(current_headers_length_);
2082 current_headers_length_ = 0;
2083 current_headers_.clear();
2084 current_headers_category_ = category;
2085 }
2086
2087
operator *()2088 nghttp2_stream* Http2Stream::operator*() {
2089 return nghttp2_session_find_stream(**session_, id_);
2090 }
2091
Close(int32_t code)2092 void Http2Stream::Close(int32_t code) {
2093 CHECK(!this->IsDestroyed());
2094 flags_ |= NGHTTP2_STREAM_FLAG_CLOSED;
2095 code_ = code;
2096 Debug(this, "closed with code %d", code);
2097 }
2098
DoShutdown(ShutdownWrap * req_wrap)2099 int Http2Stream::DoShutdown(ShutdownWrap* req_wrap) {
2100 if (IsDestroyed())
2101 return UV_EPIPE;
2102
2103 {
2104 Http2Scope h2scope(this);
2105 flags_ |= NGHTTP2_STREAM_FLAG_SHUT;
2106 CHECK_NE(nghttp2_session_resume_data(**session_, id_),
2107 NGHTTP2_ERR_NOMEM);
2108 Debug(this, "writable side shutdown");
2109 }
2110 return 1;
2111 }
2112
2113 // Destroy the Http2Stream and render it unusable. Actual resources for the
2114 // Stream will not be freed until the next tick of the Node.js event loop
2115 // using the SetImmediate queue.
Destroy()2116 void Http2Stream::Destroy() {
2117 // Do nothing if this stream instance is already destroyed
2118 if (IsDestroyed())
2119 return;
2120 if (session_->HasPendingRstStream(id_))
2121 FlushRstStream();
2122 flags_ |= NGHTTP2_STREAM_FLAG_DESTROYED;
2123
2124 Debug(this, "destroying stream");
2125
2126 // Wait until the start of the next loop to delete because there
2127 // may still be some pending operations queued for this stream.
2128 env()->SetImmediate([](Environment* env, void* data) {
2129 Http2Stream* stream = static_cast<Http2Stream*>(data);
2130 // Free any remaining outgoing data chunks here. This should be done
2131 // here because it's possible for destroy to have been called while
2132 // we still have queued outbound writes.
2133 while (!stream->queue_.empty()) {
2134 nghttp2_stream_write& head = stream->queue_.front();
2135 if (head.req_wrap != nullptr)
2136 head.req_wrap->Done(UV_ECANCELED);
2137 stream->queue_.pop();
2138 }
2139
2140 // We can destroy the stream now if there are no writes for it
2141 // already on the socket. Otherwise, we'll wait for the garbage collector
2142 // to take care of cleaning up.
2143 if (stream->session() == nullptr ||
2144 !stream->session()->HasWritesOnSocketForStream(stream))
2145 delete stream;
2146 }, this, this->object());
2147
2148 statistics_.end_time = uv_hrtime();
2149 session_->statistics_.stream_average_duration =
2150 ((statistics_.end_time - statistics_.start_time) /
2151 session_->statistics_.stream_count) / 1e6;
2152 EmitStatistics();
2153 }
2154
2155
2156 // Initiates a response on the Http2Stream using data provided via the
2157 // StreamBase Streams API.
SubmitResponse(nghttp2_nv * nva,size_t len,int options)2158 int Http2Stream::SubmitResponse(nghttp2_nv* nva, size_t len, int options) {
2159 CHECK(!this->IsDestroyed());
2160 Http2Scope h2scope(this);
2161 Debug(this, "submitting response");
2162 if (options & STREAM_OPTION_GET_TRAILERS)
2163 flags_ |= NGHTTP2_STREAM_FLAG_TRAILERS;
2164
2165 if (!IsWritable())
2166 options |= STREAM_OPTION_EMPTY_PAYLOAD;
2167
2168 Http2Stream::Provider::Stream prov(this, options);
2169 int ret = nghttp2_submit_response(**session_, id_, nva, len, *prov);
2170 CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2171 return ret;
2172 }
2173
2174
2175 // Submit informational headers for a stream.
SubmitInfo(nghttp2_nv * nva,size_t len)2176 int Http2Stream::SubmitInfo(nghttp2_nv* nva, size_t len) {
2177 CHECK(!this->IsDestroyed());
2178 Http2Scope h2scope(this);
2179 Debug(this, "sending %d informational headers", len);
2180 int ret = nghttp2_submit_headers(**session_,
2181 NGHTTP2_FLAG_NONE,
2182 id_, nullptr,
2183 nva, len, nullptr);
2184 CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2185 return ret;
2186 }
2187
OnTrailers()2188 void Http2Stream::OnTrailers() {
2189 Debug(this, "let javascript know we are ready for trailers");
2190 CHECK(!this->IsDestroyed());
2191 Isolate* isolate = env()->isolate();
2192 HandleScope scope(isolate);
2193 Local<Context> context = env()->context();
2194 Context::Scope context_scope(context);
2195 flags_ &= ~NGHTTP2_STREAM_FLAG_TRAILERS;
2196 MakeCallback(env()->ontrailers_string(), 0, nullptr);
2197 }
2198
2199 // Submit informational headers for a stream.
SubmitTrailers(nghttp2_nv * nva,size_t len)2200 int Http2Stream::SubmitTrailers(nghttp2_nv* nva, size_t len) {
2201 CHECK(!this->IsDestroyed());
2202 Http2Scope h2scope(this);
2203 Debug(this, "sending %d trailers", len);
2204 int ret;
2205 // Sending an empty trailers frame poses problems in Safari, Edge & IE.
2206 // Instead we can just send an empty data frame with NGHTTP2_FLAG_END_STREAM
2207 // to indicate that the stream is ready to be closed.
2208 if (len == 0) {
2209 Http2Stream::Provider::Stream prov(this, 0);
2210 ret = nghttp2_submit_data(**session_, NGHTTP2_FLAG_END_STREAM, id_, *prov);
2211 } else {
2212 ret = nghttp2_submit_trailer(**session_, id_, nva, len);
2213 }
2214 CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2215 return ret;
2216 }
2217
2218 // Submit a PRIORITY frame to the connected peer.
SubmitPriority(nghttp2_priority_spec * prispec,bool silent)2219 int Http2Stream::SubmitPriority(nghttp2_priority_spec* prispec,
2220 bool silent) {
2221 CHECK(!this->IsDestroyed());
2222 Http2Scope h2scope(this);
2223 Debug(this, "sending priority spec");
2224 int ret = silent ?
2225 nghttp2_session_change_stream_priority(**session_,
2226 id_, prispec) :
2227 nghttp2_submit_priority(**session_,
2228 NGHTTP2_FLAG_NONE,
2229 id_, prispec);
2230 CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2231 return ret;
2232 }
2233
2234 // Closes the Http2Stream by submitting an RST_STREAM frame to the connected
2235 // peer.
SubmitRstStream(const uint32_t code)2236 void Http2Stream::SubmitRstStream(const uint32_t code) {
2237 CHECK(!this->IsDestroyed());
2238 code_ = code;
2239 // If possible, force a purge of any currently pending data here to make sure
2240 // it is sent before closing the stream. If it returns non-zero then we need
2241 // to wait until the current write finishes and try again to avoid nghttp2
2242 // behaviour where it prioritizes RstStream over everything else.
2243 if (session_->SendPendingData() != 0) {
2244 session_->AddPendingRstStream(id_);
2245 return;
2246 }
2247
2248 FlushRstStream();
2249 }
2250
FlushRstStream()2251 void Http2Stream::FlushRstStream() {
2252 if (IsDestroyed())
2253 return;
2254 Http2Scope h2scope(this);
2255 CHECK_EQ(nghttp2_submit_rst_stream(**session_, NGHTTP2_FLAG_NONE,
2256 id_, code_), 0);
2257 }
2258
2259
2260 // Submit a push promise and create the associated Http2Stream if successful.
SubmitPushPromise(nghttp2_nv * nva,size_t len,int32_t * ret,int options)2261 Http2Stream* Http2Stream::SubmitPushPromise(nghttp2_nv* nva,
2262 size_t len,
2263 int32_t* ret,
2264 int options) {
2265 CHECK(!this->IsDestroyed());
2266 Http2Scope h2scope(this);
2267 Debug(this, "sending push promise");
2268 *ret = nghttp2_submit_push_promise(**session_, NGHTTP2_FLAG_NONE,
2269 id_, nva, len, nullptr);
2270 CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
2271 Http2Stream* stream = nullptr;
2272 if (*ret > 0)
2273 stream = Http2Stream::New(session_, *ret, NGHTTP2_HCAT_HEADERS, options);
2274
2275 return stream;
2276 }
2277
2278 // Switch the StreamBase into flowing mode to begin pushing chunks of data
2279 // out to JS land.
ReadStart()2280 int Http2Stream::ReadStart() {
2281 Http2Scope h2scope(this);
2282 CHECK(!this->IsDestroyed());
2283 flags_ |= NGHTTP2_STREAM_FLAG_READ_START;
2284 flags_ &= ~NGHTTP2_STREAM_FLAG_READ_PAUSED;
2285
2286 Debug(this, "reading starting");
2287
2288 // Tell nghttp2 about our consumption of the data that was handed
2289 // off to JS land.
2290 nghttp2_session_consume_stream(**session_,
2291 id_,
2292 inbound_consumed_data_while_paused_);
2293 inbound_consumed_data_while_paused_ = 0;
2294
2295 return 0;
2296 }
2297
2298 // Switch the StreamBase into paused mode.
ReadStop()2299 int Http2Stream::ReadStop() {
2300 CHECK(!this->IsDestroyed());
2301 if (!IsReading())
2302 return 0;
2303 flags_ |= NGHTTP2_STREAM_FLAG_READ_PAUSED;
2304 Debug(this, "reading stopped");
2305 return 0;
2306 }
2307
2308 // The Http2Stream class is a subclass of StreamBase. The DoWrite method
2309 // receives outbound chunks of data to send as outbound DATA frames. These
2310 // are queued in an internal linked list of uv_buf_t structs that are sent
2311 // when nghttp2 is ready to serialize the data frame.
2312 //
2313 // Queue the given set of uv_but_t handles for writing to an
2314 // nghttp2_stream. The WriteWrap's Done callback will be invoked once the
2315 // chunks of data have been flushed to the underlying nghttp2_session.
2316 // Note that this does *not* mean that the data has been flushed
2317 // to the socket yet.
DoWrite(std::unique_ptr<WriteWrap> & req_wrap,uv_buf_t * bufs,size_t nbufs,uv_stream_t * send_handle)2318 int Http2Stream::DoWrite(std::unique_ptr<WriteWrap>& req_wrap,
2319 uv_buf_t* bufs,
2320 size_t nbufs,
2321 uv_stream_t* send_handle) {
2322 CHECK_NULL(send_handle);
2323 Http2Scope h2scope(this);
2324 if (!IsWritable() || IsDestroyed()) {
2325 req_wrap->Done(UV_EOF);
2326 return 0;
2327 }
2328 Debug(this, "queuing %d buffers to send", id_, nbufs);
2329 for (size_t i = 0; i < nbufs; ++i) {
2330 // Store the req_wrap on the last write info in the queue, so that it is
2331 // only marked as finished once all buffers associated with it are finished.
2332 queue_.emplace(nghttp2_stream_write {
2333 i == nbufs - 1 ? req_wrap.get() : nullptr,
2334 bufs[i]
2335 });
2336 IncrementAvailableOutboundLength(bufs[i].len);
2337 }
2338 CHECK_NE(nghttp2_session_resume_data(**session_, id_), NGHTTP2_ERR_NOMEM);
2339 return 0;
2340 }
2341
2342 // Ads a header to the Http2Stream. Note that the header name and value are
2343 // provided using a buffer structure provided by nghttp2 that allows us to
2344 // avoid unnecessary memcpy's. Those buffers are ref counted. The ref count
2345 // is incremented here and are decremented when the header name and values
2346 // are garbage collected later.
AddHeader(nghttp2_rcbuf * name,nghttp2_rcbuf * value,uint8_t flags)2347 bool Http2Stream::AddHeader(nghttp2_rcbuf* name,
2348 nghttp2_rcbuf* value,
2349 uint8_t flags) {
2350 CHECK(!this->IsDestroyed());
2351 if (this->statistics_.first_header == 0)
2352 this->statistics_.first_header = uv_hrtime();
2353 size_t name_len = nghttp2_rcbuf_get_buf(name).len;
2354 if (name_len == 0 && !IsReverted(SECURITY_REVERT_CVE_2019_9516)) {
2355 return true; // Ignore headers with empty names.
2356 }
2357 size_t value_len = nghttp2_rcbuf_get_buf(value).len;
2358 size_t length = name_len + value_len + 32;
2359 // A header can only be added if we have not exceeded the maximum number
2360 // of headers and the session has memory available for it.
2361 if (!session_->IsAvailableSessionMemory(length) ||
2362 current_headers_.size() == max_header_pairs_ ||
2363 current_headers_length_ + length > max_header_length_) {
2364 return false;
2365 }
2366 nghttp2_header header;
2367 header.name = name;
2368 header.value = value;
2369 header.flags = flags;
2370 current_headers_.push_back(header);
2371 nghttp2_rcbuf_incref(name);
2372 nghttp2_rcbuf_incref(value);
2373 current_headers_length_ += length;
2374 session_->IncrementCurrentSessionMemory(length);
2375 return true;
2376 }
2377
2378 // A Provider is the thing that provides outbound DATA frame data.
Provider(Http2Stream * stream,int options)2379 Http2Stream::Provider::Provider(Http2Stream* stream, int options) {
2380 CHECK(!stream->IsDestroyed());
2381 provider_.source.ptr = stream;
2382 empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
2383 }
2384
Provider(int options)2385 Http2Stream::Provider::Provider(int options) {
2386 provider_.source.ptr = nullptr;
2387 empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
2388 }
2389
~Provider()2390 Http2Stream::Provider::~Provider() {
2391 provider_.source.ptr = nullptr;
2392 }
2393
2394 // The Stream Provider pulls data from a linked list of uv_buf_t structs
2395 // built via the StreamBase API and the Streams js API.
Stream(int options)2396 Http2Stream::Provider::Stream::Stream(int options)
2397 : Http2Stream::Provider(options) {
2398 provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
2399 }
2400
Stream(Http2Stream * stream,int options)2401 Http2Stream::Provider::Stream::Stream(Http2Stream* stream, int options)
2402 : Http2Stream::Provider(stream, options) {
2403 provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
2404 }
2405
OnRead(nghttp2_session * handle,int32_t id,uint8_t * buf,size_t length,uint32_t * flags,nghttp2_data_source * source,void * user_data)2406 ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
2407 int32_t id,
2408 uint8_t* buf,
2409 size_t length,
2410 uint32_t* flags,
2411 nghttp2_data_source* source,
2412 void* user_data) {
2413 Http2Session* session = static_cast<Http2Session*>(user_data);
2414 Debug(session, "reading outbound data for stream %d", id);
2415 Http2Stream* stream = GetStream(session, id, source);
2416 if (stream->statistics_.first_byte_sent == 0)
2417 stream->statistics_.first_byte_sent = uv_hrtime();
2418 CHECK_EQ(id, stream->id());
2419
2420 size_t amount = 0; // amount of data being sent in this data frame.
2421
2422 // Remove all empty chunks from the head of the queue.
2423 // This is done here so that .write('', cb) is still a meaningful way to
2424 // find out when the HTTP2 stream wants to consume data, and because the
2425 // StreamBase API allows empty input chunks.
2426 while (!stream->queue_.empty() && stream->queue_.front().buf.len == 0) {
2427 WriteWrap* finished = stream->queue_.front().req_wrap;
2428 stream->queue_.pop();
2429 if (finished != nullptr)
2430 finished->Done(0);
2431 }
2432
2433 if (!stream->queue_.empty()) {
2434 Debug(session, "stream %d has pending outbound data", id);
2435 amount = std::min(stream->available_outbound_length_, length);
2436 Debug(session, "sending %d bytes for data frame on stream %d", amount, id);
2437 if (amount > 0) {
2438 // Just return the length, let Http2Session::OnSendData take care of
2439 // actually taking the buffers out of the queue.
2440 *flags |= NGHTTP2_DATA_FLAG_NO_COPY;
2441 stream->DecrementAvailableOutboundLength(amount);
2442 }
2443 }
2444
2445 if (amount == 0 && stream->IsWritable()) {
2446 CHECK(stream->queue_.empty());
2447 Debug(session, "deferring stream %d", id);
2448 stream->EmitWantsWrite(length);
2449 if (stream->available_outbound_length_ > 0 || !stream->IsWritable()) {
2450 // EmitWantsWrite() did something interesting synchronously, restart:
2451 return OnRead(handle, id, buf, length, flags, source, user_data);
2452 }
2453 return NGHTTP2_ERR_DEFERRED;
2454 }
2455
2456 if (stream->available_outbound_length_ == 0 && !stream->IsWritable()) {
2457 Debug(session, "no more data for stream %d", id);
2458 *flags |= NGHTTP2_DATA_FLAG_EOF;
2459 if (stream->HasTrailers()) {
2460 *flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
2461 stream->OnTrailers();
2462 }
2463 }
2464
2465 stream->statistics_.sent_bytes += amount;
2466 return amount;
2467 }
2468
IncrementAvailableOutboundLength(size_t amount)2469 inline void Http2Stream::IncrementAvailableOutboundLength(size_t amount) {
2470 available_outbound_length_ += amount;
2471 session_->IncrementCurrentSessionMemory(amount);
2472 }
2473
DecrementAvailableOutboundLength(size_t amount)2474 inline void Http2Stream::DecrementAvailableOutboundLength(size_t amount) {
2475 available_outbound_length_ -= amount;
2476 session_->DecrementCurrentSessionMemory(amount);
2477 }
2478
2479
2480 // Implementation of the JavaScript API
2481
2482 // Fetches the string description of a nghttp2 error code and passes that
2483 // back to JS land
HttpErrorString(const FunctionCallbackInfo<Value> & args)2484 void HttpErrorString(const FunctionCallbackInfo<Value>& args) {
2485 Environment* env = Environment::GetCurrent(args);
2486 uint32_t val = args[0]->Uint32Value(env->context()).ToChecked();
2487 args.GetReturnValue().Set(
2488 String::NewFromOneByte(
2489 env->isolate(),
2490 reinterpret_cast<const uint8_t*>(nghttp2_strerror(val)),
2491 NewStringType::kInternalized).ToLocalChecked());
2492 }
2493
2494
2495 // Serializes the settings object into a Buffer instance that
2496 // would be suitable, for instance, for creating the Base64
2497 // output for an HTTP2-Settings header field.
PackSettings(const FunctionCallbackInfo<Value> & args)2498 void PackSettings(const FunctionCallbackInfo<Value>& args) {
2499 Environment* env = Environment::GetCurrent(args);
2500 // TODO(addaleax): We should not be creating a full AsyncWrap for this.
2501 Local<Object> obj;
2502 if (!env->http2settings_constructor_template()
2503 ->NewInstance(env->context())
2504 .ToLocal(&obj)) {
2505 return;
2506 }
2507 Http2Session::Http2Settings settings(env, nullptr, obj);
2508 args.GetReturnValue().Set(settings.Pack());
2509 }
2510
2511 // A TypedArray instance is shared between C++ and JS land to contain the
2512 // default SETTINGS. RefreshDefaultSettings updates that TypedArray with the
2513 // default values.
RefreshDefaultSettings(const FunctionCallbackInfo<Value> & args)2514 void RefreshDefaultSettings(const FunctionCallbackInfo<Value>& args) {
2515 Environment* env = Environment::GetCurrent(args);
2516 Http2Session::Http2Settings::RefreshDefaults(env);
2517 }
2518
2519 // Sets the next stream ID the Http2Session. If successful, returns true.
SetNextStreamID(const FunctionCallbackInfo<Value> & args)2520 void Http2Session::SetNextStreamID(const FunctionCallbackInfo<Value>& args) {
2521 Environment* env = Environment::GetCurrent(args);
2522 Http2Session* session;
2523 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2524 int32_t id = args[0]->Int32Value(env->context()).ToChecked();
2525 if (nghttp2_session_set_next_stream_id(**session, id) < 0) {
2526 Debug(session, "failed to set next stream id to %d", id);
2527 return args.GetReturnValue().Set(false);
2528 }
2529 args.GetReturnValue().Set(true);
2530 Debug(session, "set next stream id to %d", id);
2531 }
2532
2533 // A TypedArray instance is shared between C++ and JS land to contain the
2534 // SETTINGS (either remote or local). RefreshSettings updates the current
2535 // values established for each of the settings so those can be read in JS land.
2536 template <get_setting fn>
RefreshSettings(const FunctionCallbackInfo<Value> & args)2537 void Http2Session::RefreshSettings(const FunctionCallbackInfo<Value>& args) {
2538 Environment* env = Environment::GetCurrent(args);
2539 Http2Session* session;
2540 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2541 Http2Settings::Update(env, session, fn);
2542 Debug(session, "settings refreshed for session");
2543 }
2544
2545 // A TypedArray instance is shared between C++ and JS land to contain state
2546 // information of the current Http2Session. This updates the values in the
2547 // TypedArray so those can be read in JS land.
RefreshState(const FunctionCallbackInfo<Value> & args)2548 void Http2Session::RefreshState(const FunctionCallbackInfo<Value>& args) {
2549 Environment* env = Environment::GetCurrent(args);
2550 Http2Session* session;
2551 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2552 Debug(session, "refreshing state");
2553
2554 AliasedBuffer<double, Float64Array>& buffer =
2555 env->http2_state()->session_state_buffer;
2556
2557 nghttp2_session* s = **session;
2558
2559 buffer[IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE] =
2560 nghttp2_session_get_effective_local_window_size(s);
2561 buffer[IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH] =
2562 nghttp2_session_get_effective_recv_data_length(s);
2563 buffer[IDX_SESSION_STATE_NEXT_STREAM_ID] =
2564 nghttp2_session_get_next_stream_id(s);
2565 buffer[IDX_SESSION_STATE_LOCAL_WINDOW_SIZE] =
2566 nghttp2_session_get_local_window_size(s);
2567 buffer[IDX_SESSION_STATE_LAST_PROC_STREAM_ID] =
2568 nghttp2_session_get_last_proc_stream_id(s);
2569 buffer[IDX_SESSION_STATE_REMOTE_WINDOW_SIZE] =
2570 nghttp2_session_get_remote_window_size(s);
2571 buffer[IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE] =
2572 nghttp2_session_get_outbound_queue_size(s);
2573 buffer[IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE] =
2574 nghttp2_session_get_hd_deflate_dynamic_table_size(s);
2575 buffer[IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE] =
2576 nghttp2_session_get_hd_inflate_dynamic_table_size(s);
2577 }
2578
2579
2580 // Constructor for new Http2Session instances.
New(const FunctionCallbackInfo<Value> & args)2581 void Http2Session::New(const FunctionCallbackInfo<Value>& args) {
2582 Environment* env = Environment::GetCurrent(args);
2583 CHECK(args.IsConstructCall());
2584 int val = args[0]->IntegerValue(env->context()).ToChecked();
2585 nghttp2_session_type type = static_cast<nghttp2_session_type>(val);
2586 Http2Session* session = new Http2Session(env, args.This(), type);
2587 session->get_async_id(); // avoid compiler warning
2588 Debug(session, "session created");
2589 }
2590
2591
2592 // Binds the Http2Session with a StreamBase used for i/o
Consume(const FunctionCallbackInfo<Value> & args)2593 void Http2Session::Consume(const FunctionCallbackInfo<Value>& args) {
2594 Http2Session* session;
2595 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2596 CHECK(args[0]->IsExternal());
2597 session->Consume(args[0].As<External>());
2598 }
2599
2600 // Destroys the Http2Session instance and renders it unusable
Destroy(const FunctionCallbackInfo<Value> & args)2601 void Http2Session::Destroy(const FunctionCallbackInfo<Value>& args) {
2602 Http2Session* session;
2603 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2604 Debug(session, "destroying session");
2605 Environment* env = Environment::GetCurrent(args);
2606 Local<Context> context = env->context();
2607
2608 uint32_t code = args[0]->Uint32Value(context).ToChecked();
2609 bool socketDestroyed = args[1]->BooleanValue(context).ToChecked();
2610
2611 session->Close(code, socketDestroyed);
2612 }
2613
2614 // Submits a new request on the Http2Session and returns either an error code
2615 // or the Http2Stream object.
Request(const FunctionCallbackInfo<Value> & args)2616 void Http2Session::Request(const FunctionCallbackInfo<Value>& args) {
2617 Http2Session* session;
2618 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2619 Environment* env = session->env();
2620 Local<Context> context = env->context();
2621 Isolate* isolate = env->isolate();
2622
2623 Local<Array> headers = args[0].As<Array>();
2624 int options = args[1]->IntegerValue(context).ToChecked();
2625 Http2Priority priority(env, args[2], args[3], args[4]);
2626
2627 Headers list(isolate, context, headers);
2628
2629 Debug(session, "request submitted");
2630
2631 int32_t ret = 0;
2632 Http2Stream* stream =
2633 session->Http2Session::SubmitRequest(*priority, *list, list.length(),
2634 &ret, options);
2635
2636 if (ret <= 0 || stream == nullptr) {
2637 Debug(session, "could not submit request: %s", nghttp2_strerror(ret));
2638 return args.GetReturnValue().Set(ret);
2639 }
2640
2641 Debug(session, "request submitted, new stream id %d", stream->id());
2642 args.GetReturnValue().Set(stream->object());
2643 }
2644
2645 // Submits a GOAWAY frame to signal that the Http2Session is in the process
2646 // of shutting down. Note that this function does not actually alter the
2647 // state of the Http2Session, it's simply a notification.
Goaway(uint32_t code,int32_t lastStreamID,uint8_t * data,size_t len)2648 void Http2Session::Goaway(uint32_t code,
2649 int32_t lastStreamID,
2650 uint8_t* data,
2651 size_t len) {
2652 if (IsDestroyed())
2653 return;
2654
2655 Http2Scope h2scope(this);
2656 // the last proc stream id is the most recently created Http2Stream.
2657 if (lastStreamID <= 0)
2658 lastStreamID = nghttp2_session_get_last_proc_stream_id(session_);
2659 Debug(this, "submitting goaway");
2660 nghttp2_submit_goaway(session_, NGHTTP2_FLAG_NONE,
2661 lastStreamID, code, data, len);
2662 }
2663
2664 // Submits a GOAWAY frame to signal that the Http2Session is in the process
2665 // of shutting down. The opaque data argument is an optional TypedArray that
2666 // can be used to send debugging data to the connected peer.
Goaway(const FunctionCallbackInfo<Value> & args)2667 void Http2Session::Goaway(const FunctionCallbackInfo<Value>& args) {
2668 Environment* env = Environment::GetCurrent(args);
2669 Local<Context> context = env->context();
2670 Http2Session* session;
2671 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2672
2673 uint32_t code = args[0]->Uint32Value(context).ToChecked();
2674 int32_t lastStreamID = args[1]->Int32Value(context).ToChecked();
2675 Local<Value> opaqueData = args[2];
2676 uint8_t* data = nullptr;
2677 size_t length = 0;
2678
2679 if (Buffer::HasInstance(opaqueData)) {
2680 data = reinterpret_cast<uint8_t*>(Buffer::Data(opaqueData));
2681 length = Buffer::Length(opaqueData);
2682 }
2683
2684 session->Goaway(code, lastStreamID, data, length);
2685 }
2686
2687 // Update accounting of data chunks. This is used primarily to manage timeout
2688 // logic when using the FD Provider.
UpdateChunksSent(const FunctionCallbackInfo<Value> & args)2689 void Http2Session::UpdateChunksSent(const FunctionCallbackInfo<Value>& args) {
2690 Environment* env = Environment::GetCurrent(args);
2691 Isolate* isolate = env->isolate();
2692 HandleScope scope(isolate);
2693 Http2Session* session;
2694 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2695
2696 uint32_t length = session->chunks_sent_since_last_write_;
2697
2698 session->object()->Set(env->context(),
2699 env->chunks_sent_since_last_write_string(),
2700 Integer::NewFromUnsigned(isolate, length)).FromJust();
2701
2702 args.GetReturnValue().Set(length);
2703 }
2704
2705 // Submits an RST_STREAM frame effectively closing the Http2Stream. Note that
2706 // this *WILL* alter the state of the stream, causing the OnStreamClose
2707 // callback to the triggered.
RstStream(const FunctionCallbackInfo<Value> & args)2708 void Http2Stream::RstStream(const FunctionCallbackInfo<Value>& args) {
2709 Environment* env = Environment::GetCurrent(args);
2710 Local<Context> context = env->context();
2711 Http2Stream* stream;
2712 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2713 uint32_t code = args[0]->Uint32Value(context).ToChecked();
2714 Debug(stream, "sending rst_stream with code %d", code);
2715 stream->SubmitRstStream(code);
2716 }
2717
2718 // Initiates a response on the Http2Stream using the StreamBase API to provide
2719 // outbound DATA frames.
Respond(const FunctionCallbackInfo<Value> & args)2720 void Http2Stream::Respond(const FunctionCallbackInfo<Value>& args) {
2721 Environment* env = Environment::GetCurrent(args);
2722 Local<Context> context = env->context();
2723 Isolate* isolate = env->isolate();
2724 Http2Stream* stream;
2725 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2726
2727 Local<Array> headers = args[0].As<Array>();
2728 int options = args[1]->IntegerValue(context).ToChecked();
2729
2730 Headers list(isolate, context, headers);
2731
2732 args.GetReturnValue().Set(
2733 stream->SubmitResponse(*list, list.length(), options));
2734 Debug(stream, "response submitted");
2735 }
2736
2737
2738 // Submits informational headers on the Http2Stream
Info(const FunctionCallbackInfo<Value> & args)2739 void Http2Stream::Info(const FunctionCallbackInfo<Value>& args) {
2740 Environment* env = Environment::GetCurrent(args);
2741 Local<Context> context = env->context();
2742 Isolate* isolate = env->isolate();
2743 Http2Stream* stream;
2744 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2745
2746 Local<Array> headers = args[0].As<Array>();
2747
2748 Headers list(isolate, context, headers);
2749 args.GetReturnValue().Set(stream->SubmitInfo(*list, list.length()));
2750 Debug(stream, "%d informational headers sent", list.length());
2751 }
2752
2753 // Submits trailing headers on the Http2Stream
Trailers(const FunctionCallbackInfo<Value> & args)2754 void Http2Stream::Trailers(const FunctionCallbackInfo<Value>& args) {
2755 Environment* env = Environment::GetCurrent(args);
2756 Local<Context> context = env->context();
2757 Isolate* isolate = env->isolate();
2758 Http2Stream* stream;
2759 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2760
2761 Local<Array> headers = args[0].As<Array>();
2762
2763 Headers list(isolate, context, headers);
2764 args.GetReturnValue().Set(stream->SubmitTrailers(*list, list.length()));
2765 Debug(stream, "%d trailing headers sent", list.length());
2766 }
2767
2768 // Grab the numeric id of the Http2Stream
GetID(const FunctionCallbackInfo<Value> & args)2769 void Http2Stream::GetID(const FunctionCallbackInfo<Value>& args) {
2770 Http2Stream* stream;
2771 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2772 args.GetReturnValue().Set(stream->id());
2773 }
2774
2775 // Destroy the Http2Stream, rendering it no longer usable
Destroy(const FunctionCallbackInfo<Value> & args)2776 void Http2Stream::Destroy(const FunctionCallbackInfo<Value>& args) {
2777 Http2Stream* stream;
2778 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2779 Debug(stream, "destroying stream");
2780 stream->Destroy();
2781 }
2782
2783 // Prompt the Http2Stream to begin sending data to the JS land.
FlushData(const FunctionCallbackInfo<Value> & args)2784 void Http2Stream::FlushData(const FunctionCallbackInfo<Value>& args) {
2785 Http2Stream* stream;
2786 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2787 stream->ReadStart();
2788 Debug(stream, "data flushed to js");
2789 }
2790
2791 // Initiate a Push Promise and create the associated Http2Stream
PushPromise(const FunctionCallbackInfo<Value> & args)2792 void Http2Stream::PushPromise(const FunctionCallbackInfo<Value>& args) {
2793 Environment* env = Environment::GetCurrent(args);
2794 Local<Context> context = env->context();
2795 Isolate* isolate = env->isolate();
2796 Http2Stream* parent;
2797 ASSIGN_OR_RETURN_UNWRAP(&parent, args.Holder());
2798
2799 Local<Array> headers = args[0].As<Array>();
2800 int options = args[1]->IntegerValue(context).ToChecked();
2801
2802 Headers list(isolate, context, headers);
2803
2804 Debug(parent, "creating push promise");
2805
2806 int32_t ret = 0;
2807 Http2Stream* stream = parent->SubmitPushPromise(*list, list.length(),
2808 &ret, options);
2809 if (ret <= 0 || stream == nullptr) {
2810 Debug(parent, "failed to create push stream: %d", ret);
2811 return args.GetReturnValue().Set(ret);
2812 }
2813 Debug(parent, "push stream %d created", stream->id());
2814 args.GetReturnValue().Set(stream->object());
2815 }
2816
2817 // Send a PRIORITY frame
Priority(const FunctionCallbackInfo<Value> & args)2818 void Http2Stream::Priority(const FunctionCallbackInfo<Value>& args) {
2819 Environment* env = Environment::GetCurrent(args);
2820 Local<Context> context = env->context();
2821 Http2Stream* stream;
2822 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2823
2824 Http2Priority priority(env, args[0], args[1], args[2]);
2825 bool silent = args[3]->BooleanValue(context).ToChecked();
2826
2827 CHECK_EQ(stream->SubmitPriority(*priority, silent), 0);
2828 Debug(stream, "priority submitted");
2829 }
2830
2831 // A TypedArray shared by C++ and JS land is used to communicate state
2832 // information about the Http2Stream. This updates the values in that
2833 // TypedArray so that the state can be read by JS.
RefreshState(const FunctionCallbackInfo<Value> & args)2834 void Http2Stream::RefreshState(const FunctionCallbackInfo<Value>& args) {
2835 Environment* env = Environment::GetCurrent(args);
2836 Http2Stream* stream;
2837 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2838
2839 Debug(stream, "refreshing state");
2840
2841 AliasedBuffer<double, Float64Array>& buffer =
2842 env->http2_state()->stream_state_buffer;
2843
2844 nghttp2_stream* str = **stream;
2845 nghttp2_session* s = **(stream->session());
2846
2847 if (str == nullptr) {
2848 buffer[IDX_STREAM_STATE] = NGHTTP2_STREAM_STATE_IDLE;
2849 buffer[IDX_STREAM_STATE_WEIGHT] =
2850 buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2851 buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2852 buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2853 buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] = 0;
2854 } else {
2855 buffer[IDX_STREAM_STATE] =
2856 nghttp2_stream_get_state(str);
2857 buffer[IDX_STREAM_STATE_WEIGHT] =
2858 nghttp2_stream_get_weight(str);
2859 buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2860 nghttp2_stream_get_sum_dependency_weight(str);
2861 buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2862 nghttp2_session_get_stream_local_close(s, stream->id());
2863 buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2864 nghttp2_session_get_stream_remote_close(s, stream->id());
2865 buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] =
2866 nghttp2_session_get_stream_local_window_size(s, stream->id());
2867 }
2868 }
2869
AltSvc(int32_t id,uint8_t * origin,size_t origin_len,uint8_t * value,size_t value_len)2870 void Http2Session::AltSvc(int32_t id,
2871 uint8_t* origin,
2872 size_t origin_len,
2873 uint8_t* value,
2874 size_t value_len) {
2875 Http2Scope h2scope(this);
2876 CHECK_EQ(nghttp2_submit_altsvc(session_, NGHTTP2_FLAG_NONE, id,
2877 origin, origin_len, value, value_len), 0);
2878 }
2879
Origin(nghttp2_origin_entry * ov,size_t count)2880 void Http2Session::Origin(nghttp2_origin_entry* ov, size_t count) {
2881 Http2Scope h2scope(this);
2882 CHECK_EQ(nghttp2_submit_origin(session_, NGHTTP2_FLAG_NONE, ov, count), 0);
2883 }
2884
2885 // Submits an AltSvc frame to be sent to the connected peer.
AltSvc(const FunctionCallbackInfo<Value> & args)2886 void Http2Session::AltSvc(const FunctionCallbackInfo<Value>& args) {
2887 Environment* env = Environment::GetCurrent(args);
2888 Http2Session* session;
2889 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2890
2891 int32_t id = args[0]->Int32Value(env->context()).ToChecked();
2892
2893 // origin and value are both required to be ASCII, handle them as such.
2894 Local<String> origin_str = args[1]->ToString(env->context()).ToLocalChecked();
2895 Local<String> value_str = args[2]->ToString(env->context()).ToLocalChecked();
2896
2897 size_t origin_len = origin_str->Length();
2898 size_t value_len = value_str->Length();
2899
2900 CHECK_LE(origin_len + value_len, 16382); // Max permitted for ALTSVC
2901 // Verify that origin len != 0 if stream id == 0, or
2902 // that origin len == 0 if stream id != 0
2903 CHECK((origin_len != 0 && id == 0) || (origin_len == 0 && id != 0));
2904
2905 MaybeStackBuffer<uint8_t> origin(origin_len);
2906 MaybeStackBuffer<uint8_t> value(value_len);
2907 origin_str->WriteOneByte(env->isolate(), *origin);
2908 value_str->WriteOneByte(env->isolate(), *value);
2909
2910 session->AltSvc(id, *origin, origin_len, *value, value_len);
2911 }
2912
Origin(const FunctionCallbackInfo<Value> & args)2913 void Http2Session::Origin(const FunctionCallbackInfo<Value>& args) {
2914 Environment* env = Environment::GetCurrent(args);
2915 Local<Context> context = env->context();
2916 Http2Session* session;
2917 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2918
2919 Local<String> origin_string = args[0].As<String>();
2920 int count = args[1]->IntegerValue(context).ToChecked();
2921
2922
2923 Origins origins(env->isolate(),
2924 env->context(),
2925 origin_string,
2926 count);
2927
2928 session->Origin(*origins, origins.length());
2929 }
2930
2931 // Submits a PING frame to be sent to the connected peer.
Ping(const FunctionCallbackInfo<Value> & args)2932 void Http2Session::Ping(const FunctionCallbackInfo<Value>& args) {
2933 Environment* env = Environment::GetCurrent(args);
2934 Http2Session* session;
2935 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2936
2937 // A PING frame may have exactly 8 bytes of payload data. If not provided,
2938 // then the current hrtime will be used as the payload.
2939 uint8_t* payload = nullptr;
2940 if (Buffer::HasInstance(args[0])) {
2941 payload = reinterpret_cast<uint8_t*>(Buffer::Data(args[0]));
2942 CHECK_EQ(Buffer::Length(args[0]), 8);
2943 }
2944
2945 Local<Object> obj;
2946 if (!env->http2ping_constructor_template()
2947 ->NewInstance(env->context())
2948 .ToLocal(&obj)) {
2949 return;
2950 }
2951 if (obj->Set(env->context(), env->ondone_string(), args[1]).IsNothing())
2952 return;
2953 Http2Session::Http2Ping* ping = new Http2Ping(session, obj);
2954
2955 // To prevent abuse, we strictly limit the number of unacknowledged PING
2956 // frames that may be sent at any given time. This is configurable in the
2957 // Options when creating a Http2Session.
2958 if (!session->AddPing(ping)) {
2959 ping->Done(false);
2960 return args.GetReturnValue().Set(false);
2961 }
2962
2963 // The Ping itself is an Async resource. When the acknowledgement is received,
2964 // the callback will be invoked and a notification sent out to JS land. The
2965 // notification will include the duration of the ping, allowing the round
2966 // trip to be measured.
2967 ping->Send(payload);
2968 args.GetReturnValue().Set(true);
2969 }
2970
2971 // Submits a SETTINGS frame for the Http2Session
Settings(const FunctionCallbackInfo<Value> & args)2972 void Http2Session::Settings(const FunctionCallbackInfo<Value>& args) {
2973 Environment* env = Environment::GetCurrent(args);
2974 Http2Session* session;
2975 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2976
2977 Local<Object> obj;
2978 if (!env->http2settings_constructor_template()
2979 ->NewInstance(env->context())
2980 .ToLocal(&obj)) {
2981 return;
2982 }
2983 if (obj->Set(env->context(), env->ondone_string(), args[0]).IsNothing())
2984 return;
2985
2986 Http2Session::Http2Settings* settings =
2987 new Http2Settings(session->env(), session, obj, 0);
2988 if (!session->AddSettings(settings)) {
2989 settings->Done(false);
2990 return args.GetReturnValue().Set(false);
2991 }
2992
2993 settings->Send();
2994 args.GetReturnValue().Set(true);
2995 }
2996
2997
PopPing()2998 Http2Session::Http2Ping* Http2Session::PopPing() {
2999 Http2Ping* ping = nullptr;
3000 if (!outstanding_pings_.empty()) {
3001 ping = outstanding_pings_.front();
3002 outstanding_pings_.pop();
3003 DecrementCurrentSessionMemory(sizeof(*ping));
3004 }
3005 return ping;
3006 }
3007
AddPing(Http2Session::Http2Ping * ping)3008 bool Http2Session::AddPing(Http2Session::Http2Ping* ping) {
3009 if (outstanding_pings_.size() == max_outstanding_pings_)
3010 return false;
3011 outstanding_pings_.push(ping);
3012 IncrementCurrentSessionMemory(sizeof(*ping));
3013 return true;
3014 }
3015
PopSettings()3016 Http2Session::Http2Settings* Http2Session::PopSettings() {
3017 Http2Settings* settings = nullptr;
3018 if (!outstanding_settings_.empty()) {
3019 settings = outstanding_settings_.front();
3020 outstanding_settings_.pop();
3021 DecrementCurrentSessionMemory(sizeof(*settings));
3022 }
3023 return settings;
3024 }
3025
AddSettings(Http2Session::Http2Settings * settings)3026 bool Http2Session::AddSettings(Http2Session::Http2Settings* settings) {
3027 if (outstanding_settings_.size() == max_outstanding_settings_)
3028 return false;
3029 outstanding_settings_.push(settings);
3030 IncrementCurrentSessionMemory(sizeof(*settings));
3031 return true;
3032 }
3033
Http2Ping(Http2Session * session,Local<Object> obj)3034 Http2Session::Http2Ping::Http2Ping(Http2Session* session, Local<Object> obj)
3035 : AsyncWrap(session->env(), obj, AsyncWrap::PROVIDER_HTTP2PING),
3036 session_(session),
3037 startTime_(uv_hrtime()) {}
3038
Send(uint8_t * payload)3039 void Http2Session::Http2Ping::Send(uint8_t* payload) {
3040 uint8_t data[8];
3041 if (payload == nullptr) {
3042 memcpy(&data, &startTime_, arraysize(data));
3043 payload = data;
3044 }
3045 Http2Scope h2scope(session_);
3046 CHECK_EQ(nghttp2_submit_ping(**session_, NGHTTP2_FLAG_NONE, payload), 0);
3047 }
3048
Done(bool ack,const uint8_t * payload)3049 void Http2Session::Http2Ping::Done(bool ack, const uint8_t* payload) {
3050 session_->statistics_.ping_rtt = uv_hrtime() - startTime_;
3051 double duration = session_->statistics_.ping_rtt / 1e6;
3052
3053 Local<Value> buf = Undefined(env()->isolate());
3054 if (payload != nullptr) {
3055 buf = Buffer::Copy(env()->isolate(),
3056 reinterpret_cast<const char*>(payload),
3057 8).ToLocalChecked();
3058 }
3059
3060 Local<Value> argv[3] = {
3061 Boolean::New(env()->isolate(), ack),
3062 Number::New(env()->isolate(), duration),
3063 buf
3064 };
3065 MakeCallback(env()->ondone_string(), arraysize(argv), argv);
3066 delete this;
3067 }
3068
3069
MemoryInfo(MemoryTracker * tracker) const3070 void nghttp2_stream_write::MemoryInfo(MemoryTracker* tracker) const {
3071 if (req_wrap != nullptr)
3072 tracker->TrackField("req_wrap", req_wrap->GetAsyncWrap());
3073 tracker->TrackField("buf", buf);
3074 }
3075
3076
MemoryInfo(MemoryTracker * tracker) const3077 void nghttp2_header::MemoryInfo(MemoryTracker* tracker) const {
3078 tracker->TrackFieldWithSize("name", nghttp2_rcbuf_get_buf(name).len);
3079 tracker->TrackFieldWithSize("value", nghttp2_rcbuf_get_buf(value).len);
3080 }
3081
3082
3083 // Set up the process.binding('http2') binding.
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)3084 void Initialize(Local<Object> target,
3085 Local<Value> unused,
3086 Local<Context> context,
3087 void* priv) {
3088 Environment* env = Environment::GetCurrent(context);
3089 Isolate* isolate = env->isolate();
3090 HandleScope scope(isolate);
3091
3092 std::unique_ptr<Http2State> state(new Http2State(isolate));
3093
3094 #define SET_STATE_TYPEDARRAY(name, field) \
3095 target->Set(context, \
3096 FIXED_ONE_BYTE_STRING(isolate, (name)), \
3097 (field)).FromJust()
3098
3099 // Initialize the buffer used for padding callbacks
3100 SET_STATE_TYPEDARRAY(
3101 "paddingBuffer", state->padding_buffer.GetJSArray());
3102 // Initialize the buffer used to store the session state
3103 SET_STATE_TYPEDARRAY(
3104 "sessionState", state->session_state_buffer.GetJSArray());
3105 // Initialize the buffer used to store the stream state
3106 SET_STATE_TYPEDARRAY(
3107 "streamState", state->stream_state_buffer.GetJSArray());
3108 SET_STATE_TYPEDARRAY(
3109 "settingsBuffer", state->settings_buffer.GetJSArray());
3110 SET_STATE_TYPEDARRAY(
3111 "optionsBuffer", state->options_buffer.GetJSArray());
3112 SET_STATE_TYPEDARRAY(
3113 "streamStats", state->stream_stats_buffer.GetJSArray());
3114 SET_STATE_TYPEDARRAY(
3115 "sessionStats", state->session_stats_buffer.GetJSArray());
3116 #undef SET_STATE_TYPEDARRAY
3117
3118 env->set_http2_state(std::move(state));
3119
3120 NODE_DEFINE_CONSTANT(target, PADDING_BUF_FRAME_LENGTH);
3121 NODE_DEFINE_CONSTANT(target, PADDING_BUF_MAX_PAYLOAD_LENGTH);
3122 NODE_DEFINE_CONSTANT(target, PADDING_BUF_RETURN_VALUE);
3123
3124 NODE_DEFINE_CONSTANT(target, kBitfield);
3125 NODE_DEFINE_CONSTANT(target, kSessionPriorityListenerCount);
3126 NODE_DEFINE_CONSTANT(target, kSessionFrameErrorListenerCount);
3127 NODE_DEFINE_CONSTANT(target, kSessionUint8FieldCount);
3128
3129 NODE_DEFINE_CONSTANT(target, kSessionHasRemoteSettingsListeners);
3130 NODE_DEFINE_CONSTANT(target, kSessionRemoteSettingsIsUpToDate);
3131 NODE_DEFINE_CONSTANT(target, kSessionHasPingListeners);
3132 NODE_DEFINE_CONSTANT(target, kSessionHasAltsvcListeners);
3133
3134 // Method to fetch the nghttp2 string description of an nghttp2 error code
3135 env->SetMethod(target, "nghttp2ErrorString", HttpErrorString);
3136
3137 Local<String> http2SessionClassName =
3138 FIXED_ONE_BYTE_STRING(isolate, "Http2Session");
3139
3140 Local<FunctionTemplate> ping = FunctionTemplate::New(env->isolate());
3141 ping->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Ping"));
3142 ping->Inherit(AsyncWrap::GetConstructorTemplate(env));
3143 Local<ObjectTemplate> pingt = ping->InstanceTemplate();
3144 pingt->SetInternalFieldCount(1);
3145 env->set_http2ping_constructor_template(pingt);
3146
3147 Local<FunctionTemplate> setting = FunctionTemplate::New(env->isolate());
3148 setting->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Setting"));
3149 setting->Inherit(AsyncWrap::GetConstructorTemplate(env));
3150 Local<ObjectTemplate> settingt = setting->InstanceTemplate();
3151 settingt->SetInternalFieldCount(1);
3152 env->set_http2settings_constructor_template(settingt);
3153
3154 Local<FunctionTemplate> stream = FunctionTemplate::New(env->isolate());
3155 stream->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Stream"));
3156 env->SetProtoMethod(stream, "id", Http2Stream::GetID);
3157 env->SetProtoMethod(stream, "destroy", Http2Stream::Destroy);
3158 env->SetProtoMethod(stream, "flushData", Http2Stream::FlushData);
3159 env->SetProtoMethod(stream, "priority", Http2Stream::Priority);
3160 env->SetProtoMethod(stream, "pushPromise", Http2Stream::PushPromise);
3161 env->SetProtoMethod(stream, "info", Http2Stream::Info);
3162 env->SetProtoMethod(stream, "trailers", Http2Stream::Trailers);
3163 env->SetProtoMethod(stream, "respond", Http2Stream::Respond);
3164 env->SetProtoMethod(stream, "rstStream", Http2Stream::RstStream);
3165 env->SetProtoMethod(stream, "refreshState", Http2Stream::RefreshState);
3166 stream->Inherit(AsyncWrap::GetConstructorTemplate(env));
3167 StreamBase::AddMethods<Http2Stream>(env, stream);
3168 Local<ObjectTemplate> streamt = stream->InstanceTemplate();
3169 streamt->SetInternalFieldCount(1);
3170 env->set_http2stream_constructor_template(streamt);
3171 target->Set(context,
3172 FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Stream"),
3173 stream->GetFunction(env->context()).ToLocalChecked()).FromJust();
3174
3175 Local<FunctionTemplate> session =
3176 env->NewFunctionTemplate(Http2Session::New);
3177 session->SetClassName(http2SessionClassName);
3178 session->InstanceTemplate()->SetInternalFieldCount(1);
3179 session->Inherit(AsyncWrap::GetConstructorTemplate(env));
3180 env->SetProtoMethod(session, "origin", Http2Session::Origin);
3181 env->SetProtoMethod(session, "altsvc", Http2Session::AltSvc);
3182 env->SetProtoMethod(session, "ping", Http2Session::Ping);
3183 env->SetProtoMethod(session, "consume", Http2Session::Consume);
3184 env->SetProtoMethod(session, "destroy", Http2Session::Destroy);
3185 env->SetProtoMethod(session, "goaway", Http2Session::Goaway);
3186 env->SetProtoMethod(session, "settings", Http2Session::Settings);
3187 env->SetProtoMethod(session, "request", Http2Session::Request);
3188 env->SetProtoMethod(session, "setNextStreamID",
3189 Http2Session::SetNextStreamID);
3190 env->SetProtoMethod(session, "updateChunksSent",
3191 Http2Session::UpdateChunksSent);
3192 env->SetProtoMethod(session, "refreshState", Http2Session::RefreshState);
3193 env->SetProtoMethod(
3194 session, "localSettings",
3195 Http2Session::RefreshSettings<nghttp2_session_get_local_settings>);
3196 env->SetProtoMethod(
3197 session, "remoteSettings",
3198 Http2Session::RefreshSettings<nghttp2_session_get_remote_settings>);
3199 target->Set(context,
3200 http2SessionClassName,
3201 session->GetFunction(env->context()).ToLocalChecked()).FromJust();
3202
3203 Local<Object> constants = Object::New(isolate);
3204 Local<Array> name_for_error_code = Array::New(isolate);
3205
3206 #define NODE_NGHTTP2_ERROR_CODES(V) \
3207 V(NGHTTP2_SESSION_SERVER); \
3208 V(NGHTTP2_SESSION_CLIENT); \
3209 V(NGHTTP2_STREAM_STATE_IDLE); \
3210 V(NGHTTP2_STREAM_STATE_OPEN); \
3211 V(NGHTTP2_STREAM_STATE_RESERVED_LOCAL); \
3212 V(NGHTTP2_STREAM_STATE_RESERVED_REMOTE); \
3213 V(NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL); \
3214 V(NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE); \
3215 V(NGHTTP2_STREAM_STATE_CLOSED); \
3216 V(NGHTTP2_NO_ERROR); \
3217 V(NGHTTP2_PROTOCOL_ERROR); \
3218 V(NGHTTP2_INTERNAL_ERROR); \
3219 V(NGHTTP2_FLOW_CONTROL_ERROR); \
3220 V(NGHTTP2_SETTINGS_TIMEOUT); \
3221 V(NGHTTP2_STREAM_CLOSED); \
3222 V(NGHTTP2_FRAME_SIZE_ERROR); \
3223 V(NGHTTP2_REFUSED_STREAM); \
3224 V(NGHTTP2_CANCEL); \
3225 V(NGHTTP2_COMPRESSION_ERROR); \
3226 V(NGHTTP2_CONNECT_ERROR); \
3227 V(NGHTTP2_ENHANCE_YOUR_CALM); \
3228 V(NGHTTP2_INADEQUATE_SECURITY); \
3229 V(NGHTTP2_HTTP_1_1_REQUIRED); \
3230
3231 #define V(name) \
3232 NODE_DEFINE_CONSTANT(constants, name); \
3233 name_for_error_code->Set(static_cast<int>(name), \
3234 FIXED_ONE_BYTE_STRING(isolate, #name));
3235 NODE_NGHTTP2_ERROR_CODES(V)
3236 #undef V
3237
3238 NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_REQUEST);
3239 NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_RESPONSE);
3240 NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_PUSH_RESPONSE);
3241 NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_HEADERS);
3242 NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_NV_FLAG_NONE);
3243 NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_NV_FLAG_NO_INDEX);
3244 NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_DEFERRED);
3245 NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE);
3246 NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_INVALID_ARGUMENT);
3247 NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_STREAM_CLOSED);
3248 NODE_DEFINE_CONSTANT(constants, NGHTTP2_ERR_FRAME_SIZE_ERROR);
3249
3250 NODE_DEFINE_HIDDEN_CONSTANT(constants, STREAM_OPTION_EMPTY_PAYLOAD);
3251 NODE_DEFINE_HIDDEN_CONSTANT(constants, STREAM_OPTION_GET_TRAILERS);
3252
3253 NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_NONE);
3254 NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_END_STREAM);
3255 NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_END_HEADERS);
3256 NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_ACK);
3257 NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_PADDED);
3258 NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_PRIORITY);
3259
3260 NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_HEADER_TABLE_SIZE);
3261 NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_ENABLE_PUSH);
3262 NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE);
3263 NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_MAX_FRAME_SIZE);
3264 NODE_DEFINE_CONSTANT(constants, MAX_MAX_FRAME_SIZE);
3265 NODE_DEFINE_CONSTANT(constants, MIN_MAX_FRAME_SIZE);
3266 NODE_DEFINE_CONSTANT(constants, MAX_INITIAL_WINDOW_SIZE);
3267 NODE_DEFINE_CONSTANT(constants, NGHTTP2_DEFAULT_WEIGHT);
3268
3269 NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE);
3270 NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_ENABLE_PUSH);
3271 NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
3272 NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
3273 NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_MAX_FRAME_SIZE);
3274 NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE);
3275 NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL);
3276
3277 NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_NONE);
3278 NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_ALIGNED);
3279 NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_MAX);
3280 NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_CALLBACK);
3281
3282 #define STRING_CONSTANT(NAME, VALUE) \
3283 NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_HEADER_" # NAME, VALUE);
3284 HTTP_KNOWN_HEADERS(STRING_CONSTANT)
3285 #undef STRING_CONSTANT
3286
3287 #define STRING_CONSTANT(NAME, VALUE) \
3288 NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_METHOD_" # NAME, VALUE);
3289 HTTP_KNOWN_METHODS(STRING_CONSTANT)
3290 #undef STRING_CONSTANT
3291
3292 #define V(name, _) NODE_DEFINE_CONSTANT(constants, HTTP_STATUS_##name);
3293 HTTP_STATUS_CODES(V)
3294 #undef V
3295
3296 env->SetMethod(target, "refreshDefaultSettings", RefreshDefaultSettings);
3297 env->SetMethod(target, "packSettings", PackSettings);
3298
3299 target->Set(context,
3300 env->constants_string(),
3301 constants).FromJust();
3302 target->Set(context,
3303 FIXED_ONE_BYTE_STRING(isolate, "nameForErrorCode"),
3304 name_for_error_code).FromJust();
3305 }
3306 } // namespace http2
3307 } // namespace node
3308
3309 NODE_BUILTIN_MODULE_CONTEXT_AWARE(http2, node::http2::Initialize)
3310