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