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