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 // If nghttp2 is unable to send a queued up frame, it will call this callback
927 // to let us know. If the failure occurred because we are in the process of
928 // closing down the session or stream, we go ahead and ignore it. We don't
929 // really care about those and there's nothing we can reasonably do about it
930 // anyway. Other types of failures are reported up to JavaScript. This should
931 // be exceedingly rare.
OnFrameNotSent(nghttp2_session * handle,const nghttp2_frame * frame,int error_code,void * user_data)932 int Http2Session::OnFrameNotSent(nghttp2_session* handle,
933 const nghttp2_frame* frame,
934 int error_code,
935 void* user_data) {
936 Http2Session* session = static_cast<Http2Session*>(user_data);
937 Environment* env = session->env();
938 Debug(session, "frame type %d was not sent, code: %d",
939 frame->hd.type, error_code);
940
941 // Do not report if the frame was not sent due to the session closing
942 if (error_code == NGHTTP2_ERR_SESSION_CLOSING ||
943 error_code == NGHTTP2_ERR_STREAM_CLOSED ||
944 error_code == NGHTTP2_ERR_STREAM_CLOSING ||
945 session->js_fields_->frame_error_listener_count == 0) {
946 return 0;
947 }
948
949 Isolate* isolate = env->isolate();
950 HandleScope scope(isolate);
951 Local<Context> context = env->context();
952 Context::Scope context_scope(context);
953
954 Local<Value> argv[3] = {
955 Integer::New(isolate, frame->hd.stream_id),
956 Integer::New(isolate, frame->hd.type),
957 Integer::New(isolate, error_code)
958 };
959 session->MakeCallback(
960 env->http2session_on_frame_error_function(),
961 arraysize(argv), argv);
962 return 0;
963 }
964
OnFrameSent(nghttp2_session * handle,const nghttp2_frame * frame,void * user_data)965 int Http2Session::OnFrameSent(nghttp2_session* handle,
966 const nghttp2_frame* frame,
967 void* user_data) {
968 Http2Session* session = static_cast<Http2Session*>(user_data);
969 session->statistics_.frame_sent += 1;
970 return 0;
971 }
972
973 // Called by nghttp2 when a stream closes.
OnStreamClose(nghttp2_session * handle,int32_t id,uint32_t code,void * user_data)974 int Http2Session::OnStreamClose(nghttp2_session* handle,
975 int32_t id,
976 uint32_t code,
977 void* user_data) {
978 Http2Session* session = static_cast<Http2Session*>(user_data);
979 Environment* env = session->env();
980 Isolate* isolate = env->isolate();
981 HandleScope scope(isolate);
982 Local<Context> context = env->context();
983 Context::Scope context_scope(context);
984 Debug(session, "stream %d closed with code: %d", id, code);
985 BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
986 // Intentionally ignore the callback if the stream does not exist or has
987 // already been destroyed
988 if (!stream || stream->is_destroyed())
989 return 0;
990
991 stream->Close(code);
992
993 // It is possible for the stream close to occur before the stream is
994 // ever passed on to the javascript side. If that happens, the callback
995 // will return false.
996 Local<Value> arg = Integer::NewFromUnsigned(isolate, code);
997 MaybeLocal<Value> answer =
998 stream->MakeCallback(env->http2session_on_stream_close_function(),
999 1, &arg);
1000 if (answer.IsEmpty() || answer.ToLocalChecked()->IsFalse()) {
1001 // Skip to destroy
1002 stream->Destroy();
1003 }
1004 return 0;
1005 }
1006
1007 // Called by nghttp2 when an invalid header has been received. For now, we
1008 // ignore these. If this callback was not provided, nghttp2 would handle
1009 // invalid headers strictly and would shut down the stream. We are intentionally
1010 // 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)1011 int Http2Session::OnInvalidHeader(nghttp2_session* session,
1012 const nghttp2_frame* frame,
1013 nghttp2_rcbuf* name,
1014 nghttp2_rcbuf* value,
1015 uint8_t flags,
1016 void* user_data) {
1017 // Ignore invalid header fields by default.
1018 return 0;
1019 }
1020
1021 // When nghttp2 receives a DATA frame, it will deliver the data payload to
1022 // us in discrete chunks. We push these into a linked list stored in the
1023 // Http2Sttream which is flushed out to JavaScript as quickly as possible.
1024 // 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)1025 int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
1026 uint8_t flags,
1027 int32_t id,
1028 const uint8_t* data,
1029 size_t len,
1030 void* user_data) {
1031 Http2Session* session = static_cast<Http2Session*>(user_data);
1032 Debug(session, "buffering data chunk for stream %d, size: "
1033 "%d, flags: %d", id, len, flags);
1034 Environment* env = session->env();
1035 HandleScope scope(env->isolate());
1036
1037 // We should never actually get a 0-length chunk so this check is
1038 // only a precaution at this point.
1039 if (len == 0)
1040 return 0;
1041
1042 // Notify nghttp2 that we've consumed a chunk of data on the connection
1043 // so that it can send a WINDOW_UPDATE frame. This is a critical part of
1044 // the flow control process in http2
1045 CHECK_EQ(nghttp2_session_consume_connection(handle, len), 0);
1046 BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
1047
1048 // If the stream has been destroyed, ignore this chunk
1049 if (!stream || stream->is_destroyed())
1050 return 0;
1051
1052 stream->statistics_.received_bytes += len;
1053
1054 // Repeatedly ask the stream's owner for memory, and copy the read data
1055 // into those buffers.
1056 // The typical case is actually the exception here; Http2StreamListeners
1057 // know about the HTTP2 session associated with this stream, so they know
1058 // about the larger from-socket read buffer, so they do not require copying.
1059 do {
1060 uv_buf_t buf = stream->EmitAlloc(len);
1061 ssize_t avail = len;
1062 if (static_cast<ssize_t>(buf.len) < avail)
1063 avail = buf.len;
1064
1065 // `buf.base == nullptr` is the default Http2StreamListener's way
1066 // of saying that it wants a pointer to the raw original.
1067 // Since it has access to the original socket buffer from which the data
1068 // was read in the first place, it can use that to minimize ArrayBuffer
1069 // allocations.
1070 if (LIKELY(buf.base == nullptr))
1071 buf.base = reinterpret_cast<char*>(const_cast<uint8_t*>(data));
1072 else
1073 memcpy(buf.base, data, avail);
1074 data += avail;
1075 len -= avail;
1076 stream->EmitRead(avail, buf);
1077
1078 // If the stream owner (e.g. the JS Http2Stream) wants more data, just
1079 // tell nghttp2 that all data has been consumed. Otherwise, defer until
1080 // more data is being requested.
1081 if (stream->is_reading())
1082 nghttp2_session_consume_stream(handle, id, avail);
1083 else
1084 stream->inbound_consumed_data_while_paused_ += avail;
1085
1086 // If we have a gathered a lot of data for output, try sending it now.
1087 if (session->outgoing_length_ > 4096 ||
1088 stream->available_outbound_length_ > 4096) {
1089 session->SendPendingData();
1090 }
1091 } while (len != 0);
1092
1093 // If we are currently waiting for a write operation to finish, we should
1094 // tell nghttp2 that we want to wait before we process more input data.
1095 if (session->is_write_in_progress()) {
1096 CHECK(session->is_reading_stopped());
1097 session->set_receive_paused();
1098 Debug(session, "receive paused");
1099 return NGHTTP2_ERR_PAUSE;
1100 }
1101
1102 return 0;
1103 }
1104
1105 // Called by nghttp2 when it needs to determine how much padding to use in
1106 // a DATA or HEADERS frame.
OnSelectPadding(nghttp2_session * handle,const nghttp2_frame * frame,size_t maxPayloadLen,void * user_data)1107 ssize_t Http2Session::OnSelectPadding(nghttp2_session* handle,
1108 const nghttp2_frame* frame,
1109 size_t maxPayloadLen,
1110 void* user_data) {
1111 Http2Session* session = static_cast<Http2Session*>(user_data);
1112 ssize_t padding = frame->hd.length;
1113
1114 switch (session->padding_strategy_) {
1115 case PADDING_STRATEGY_NONE:
1116 // Fall-through
1117 break;
1118 case PADDING_STRATEGY_MAX:
1119 padding = session->OnMaxFrameSizePadding(padding, maxPayloadLen);
1120 break;
1121 case PADDING_STRATEGY_ALIGNED:
1122 padding = session->OnDWordAlignedPadding(padding, maxPayloadLen);
1123 break;
1124 }
1125 return padding;
1126 }
1127
1128 #define BAD_PEER_MESSAGE "Remote peer returned unexpected data while we " \
1129 "expected SETTINGS frame. Perhaps, peer does not " \
1130 "support HTTP/2 properly."
1131
1132 // We use this currently to determine when an attempt is made to use the http2
1133 // protocol with a non-http2 peer.
OnNghttpError(nghttp2_session * handle,const char * message,size_t len,void * user_data)1134 int Http2Session::OnNghttpError(nghttp2_session* handle,
1135 const char* message,
1136 size_t len,
1137 void* user_data) {
1138 // Unfortunately, this is currently the only way for us to know if
1139 // the session errored because the peer is not an http2 peer.
1140 Http2Session* session = static_cast<Http2Session*>(user_data);
1141 Debug(session, "Error '%s'", message);
1142 if (strncmp(message, BAD_PEER_MESSAGE, len) == 0) {
1143 Environment* env = session->env();
1144 Isolate* isolate = env->isolate();
1145 HandleScope scope(isolate);
1146 Local<Context> context = env->context();
1147 Context::Scope context_scope(context);
1148 Local<Value> arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1149 session->MakeCallback(env->http2session_on_error_function(), 1, &arg);
1150 }
1151 return 0;
1152 }
1153
OnStreamAlloc(size_t size)1154 uv_buf_t Http2StreamListener::OnStreamAlloc(size_t size) {
1155 // See the comments in Http2Session::OnDataChunkReceived
1156 // (which is the only possible call site for this method).
1157 return uv_buf_init(nullptr, size);
1158 }
1159
OnStreamRead(ssize_t nread,const uv_buf_t & buf)1160 void Http2StreamListener::OnStreamRead(ssize_t nread, const uv_buf_t& buf) {
1161 Http2Stream* stream = static_cast<Http2Stream*>(stream_);
1162 Http2Session* session = stream->session();
1163 Environment* env = stream->env();
1164 HandleScope handle_scope(env->isolate());
1165 Context::Scope context_scope(env->context());
1166
1167 if (nread < 0) {
1168 PassReadErrorToPreviousListener(nread);
1169 return;
1170 }
1171
1172 Local<ArrayBuffer> ab;
1173 if (session->stream_buf_ab_.IsEmpty()) {
1174 ab = session->stream_buf_allocation_.ToArrayBuffer();
1175 session->stream_buf_ab_.Reset(env->isolate(), ab);
1176 } else {
1177 ab = PersistentToLocal::Strong(session->stream_buf_ab_);
1178 }
1179
1180 // There is a single large array buffer for the entire data read from the
1181 // network; create a slice of that array buffer and emit it as the
1182 // received data buffer.
1183 size_t offset = buf.base - session->stream_buf_.base;
1184
1185 // Verify that the data offset is inside the current read buffer.
1186 CHECK_GE(offset, session->stream_buf_offset_);
1187 CHECK_LE(offset, session->stream_buf_.len);
1188 CHECK_LE(offset + buf.len, session->stream_buf_.len);
1189
1190 stream->CallJSOnreadMethod(nread, ab, offset);
1191 }
1192
1193
1194 // Called by OnFrameReceived to notify JavaScript land that a complete
1195 // HEADERS frame has been received and processed. This method converts the
1196 // received headers into a JavaScript array and pushes those out to JS.
HandleHeadersFrame(const nghttp2_frame * frame)1197 void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
1198 Isolate* isolate = env()->isolate();
1199 HandleScope scope(isolate);
1200 Local<Context> context = env()->context();
1201 Context::Scope context_scope(context);
1202
1203 int32_t id = GetFrameID(frame);
1204 Debug(this, "handle headers frame for stream %d", id);
1205 BaseObjectPtr<Http2Stream> stream = FindStream(id);
1206
1207 // If the stream has already been destroyed, ignore.
1208 if (!stream || stream->is_destroyed())
1209 return;
1210
1211 // The headers are stored as a vector of Http2Header instances.
1212 // The following converts that into a JS array with the structure:
1213 // [name1, value1, name2, value2, name3, value3, name3, value4] and so on.
1214 // That array is passed up to the JS layer and converted into an Object form
1215 // like {name1: value1, name2: value2, name3: [value3, value4]}. We do it
1216 // this way for performance reasons (it's faster to generate and pass an
1217 // array than it is to generate and pass the object).
1218
1219 MaybeStackBuffer<Local<Value>, 64> headers_v(stream->headers_count() * 2);
1220 MaybeStackBuffer<Local<Value>, 32> sensitive_v(stream->headers_count());
1221 size_t sensitive_count = 0;
1222
1223 stream->TransferHeaders([&](const Http2Header& header, size_t i) {
1224 headers_v[i * 2] = header.GetName(this).ToLocalChecked();
1225 headers_v[i * 2 + 1] = header.GetValue(this).ToLocalChecked();
1226 if (header.flags() & NGHTTP2_NV_FLAG_NO_INDEX)
1227 sensitive_v[sensitive_count++] = headers_v[i * 2];
1228 });
1229 CHECK_EQ(stream->headers_count(), 0);
1230
1231 DecrementCurrentSessionMemory(stream->current_headers_length_);
1232 stream->current_headers_length_ = 0;
1233
1234 Local<Value> args[] = {
1235 stream->object(),
1236 Integer::New(isolate, id),
1237 Integer::New(isolate, stream->headers_category()),
1238 Integer::New(isolate, frame->hd.flags),
1239 Array::New(isolate, headers_v.out(), headers_v.length()),
1240 Array::New(isolate, sensitive_v.out(), sensitive_count),
1241 };
1242 MakeCallback(env()->http2session_on_headers_function(),
1243 arraysize(args), args);
1244 }
1245
1246
1247 // Called by OnFrameReceived when a complete PRIORITY frame has been
1248 // received. Notifies JS land about the priority change. Note that priorities
1249 // are considered advisory only, so this has no real effect other than to
1250 // simply let user code know that the priority has changed.
HandlePriorityFrame(const nghttp2_frame * frame)1251 void Http2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
1252 if (js_fields_->priority_listener_count == 0) return;
1253 Isolate* isolate = env()->isolate();
1254 HandleScope scope(isolate);
1255 Local<Context> context = env()->context();
1256 Context::Scope context_scope(context);
1257
1258 nghttp2_priority priority_frame = frame->priority;
1259 int32_t id = GetFrameID(frame);
1260 Debug(this, "handle priority frame for stream %d", id);
1261 // Priority frame stream ID should never be <= 0. nghttp2 handles this for us
1262 nghttp2_priority_spec spec = priority_frame.pri_spec;
1263
1264 Local<Value> argv[4] = {
1265 Integer::New(isolate, id),
1266 Integer::New(isolate, spec.stream_id),
1267 Integer::New(isolate, spec.weight),
1268 Boolean::New(isolate, spec.exclusive)
1269 };
1270 MakeCallback(env()->http2session_on_priority_function(),
1271 arraysize(argv), argv);
1272 }
1273
1274
1275 // Called by OnFrameReceived when a complete DATA frame has been received.
1276 // If we know that this was the last DATA frame (because the END_STREAM flag
1277 // is set), then we'll terminate the readable side of the StreamBase.
HandleDataFrame(const nghttp2_frame * frame)1278 int Http2Session::HandleDataFrame(const nghttp2_frame* frame) {
1279 int32_t id = GetFrameID(frame);
1280 Debug(this, "handling data frame for stream %d", id);
1281 BaseObjectPtr<Http2Stream> stream = FindStream(id);
1282
1283 if (stream &&
1284 !stream->is_destroyed() &&
1285 frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
1286 stream->EmitRead(UV_EOF);
1287 } else if (frame->hd.length == 0) {
1288 if (invalid_frame_count_++ > js_fields_->max_invalid_frames) {
1289 Debug(this, "rejecting empty-frame-without-END_STREAM flood\n");
1290 // Consider a flood of 0-length frames without END_STREAM an error.
1291 return 1;
1292 }
1293 }
1294 return 0;
1295 }
1296
1297
1298 // Called by OnFrameReceived when a complete GOAWAY frame has been received.
HandleGoawayFrame(const nghttp2_frame * frame)1299 void Http2Session::HandleGoawayFrame(const nghttp2_frame* frame) {
1300 Isolate* isolate = env()->isolate();
1301 HandleScope scope(isolate);
1302 Local<Context> context = env()->context();
1303 Context::Scope context_scope(context);
1304
1305 nghttp2_goaway goaway_frame = frame->goaway;
1306 Debug(this, "handling goaway frame");
1307
1308 Local<Value> argv[3] = {
1309 Integer::NewFromUnsigned(isolate, goaway_frame.error_code),
1310 Integer::New(isolate, goaway_frame.last_stream_id),
1311 Undefined(isolate)
1312 };
1313
1314 size_t length = goaway_frame.opaque_data_len;
1315 if (length > 0) {
1316 // If the copy fails for any reason here, we just ignore it.
1317 // The additional goaway data is completely optional and we
1318 // shouldn't fail if we're not able to process it.
1319 argv[2] = Buffer::Copy(isolate,
1320 reinterpret_cast<char*>(goaway_frame.opaque_data),
1321 length).ToLocalChecked();
1322 }
1323
1324 MakeCallback(env()->http2session_on_goaway_data_function(),
1325 arraysize(argv), argv);
1326 }
1327
1328 // Called by OnFrameReceived when a complete ALTSVC frame has been received.
HandleAltSvcFrame(const nghttp2_frame * frame)1329 void Http2Session::HandleAltSvcFrame(const nghttp2_frame* frame) {
1330 if (!(js_fields_->bitfield & (1 << kSessionHasAltsvcListeners))) return;
1331 Isolate* isolate = env()->isolate();
1332 HandleScope scope(isolate);
1333 Local<Context> context = env()->context();
1334 Context::Scope context_scope(context);
1335
1336 int32_t id = GetFrameID(frame);
1337
1338 nghttp2_extension ext = frame->ext;
1339 nghttp2_ext_altsvc* altsvc = static_cast<nghttp2_ext_altsvc*>(ext.payload);
1340 Debug(this, "handling altsvc frame");
1341
1342 Local<Value> argv[3] = {
1343 Integer::New(isolate, id),
1344 OneByteString(isolate, altsvc->origin, altsvc->origin_len),
1345 OneByteString(isolate, altsvc->field_value, altsvc->field_value_len)
1346 };
1347
1348 MakeCallback(env()->http2session_on_altsvc_function(),
1349 arraysize(argv), argv);
1350 }
1351
HandleOriginFrame(const nghttp2_frame * frame)1352 void Http2Session::HandleOriginFrame(const nghttp2_frame* frame) {
1353 Isolate* isolate = env()->isolate();
1354 HandleScope scope(isolate);
1355 Local<Context> context = env()->context();
1356 Context::Scope context_scope(context);
1357
1358 Debug(this, "handling origin frame");
1359
1360 nghttp2_extension ext = frame->ext;
1361 nghttp2_ext_origin* origin = static_cast<nghttp2_ext_origin*>(ext.payload);
1362
1363 size_t nov = origin->nov;
1364 std::vector<Local<Value>> origin_v(nov);
1365
1366 for (size_t i = 0; i < nov; ++i) {
1367 const nghttp2_origin_entry& entry = origin->ov[i];
1368 origin_v[i] = OneByteString(isolate, entry.origin, entry.origin_len);
1369 }
1370 Local<Value> holder = Array::New(isolate, origin_v.data(), origin_v.size());
1371 MakeCallback(env()->http2session_on_origin_function(), 1, &holder);
1372 }
1373
1374 // Called by OnFrameReceived when a complete PING frame has been received.
HandlePingFrame(const nghttp2_frame * frame)1375 void Http2Session::HandlePingFrame(const nghttp2_frame* frame) {
1376 Isolate* isolate = env()->isolate();
1377 HandleScope scope(isolate);
1378 Local<Context> context = env()->context();
1379 Context::Scope context_scope(context);
1380 Local<Value> arg;
1381 bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
1382 if (ack) {
1383 BaseObjectPtr<Http2Ping> ping = PopPing();
1384
1385 if (!ping) {
1386 // PING Ack is unsolicited. Treat as a connection error. The HTTP/2
1387 // spec does not require this, but there is no legitimate reason to
1388 // receive an unsolicited PING ack on a connection. Either the peer
1389 // is buggy or malicious, and we're not going to tolerate such
1390 // nonsense.
1391 arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1392 MakeCallback(env()->http2session_on_error_function(), 1, &arg);
1393 return;
1394 }
1395
1396 ping->Done(true, frame->ping.opaque_data);
1397 return;
1398 }
1399
1400 if (!(js_fields_->bitfield & (1 << kSessionHasPingListeners))) return;
1401 // Notify the session that a ping occurred
1402 arg = Buffer::Copy(
1403 env(),
1404 reinterpret_cast<const char*>(frame->ping.opaque_data),
1405 8).ToLocalChecked();
1406 MakeCallback(env()->http2session_on_ping_function(), 1, &arg);
1407 }
1408
1409 // Called by OnFrameReceived when a complete SETTINGS frame has been received.
HandleSettingsFrame(const nghttp2_frame * frame)1410 void Http2Session::HandleSettingsFrame(const nghttp2_frame* frame) {
1411 bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
1412 if (!ack) {
1413 js_fields_->bitfield &= ~(1 << kSessionRemoteSettingsIsUpToDate);
1414 if (!(js_fields_->bitfield & (1 << kSessionHasRemoteSettingsListeners)))
1415 return;
1416 // This is not a SETTINGS acknowledgement, notify and return
1417 MakeCallback(env()->http2session_on_settings_function(), 0, nullptr);
1418 return;
1419 }
1420
1421 // If this is an acknowledgement, we should have an Http2Settings
1422 // object for it.
1423 BaseObjectPtr<Http2Settings> settings = PopSettings();
1424 if (settings) {
1425 settings->Done(true);
1426 return;
1427 }
1428 // SETTINGS Ack is unsolicited. Treat as a connection error. The HTTP/2
1429 // spec does not require this, but there is no legitimate reason to
1430 // receive an unsolicited SETTINGS ack on a connection. Either the peer
1431 // is buggy or malicious, and we're not going to tolerate such
1432 // nonsense.
1433 // Note that nghttp2 currently prevents this from happening for SETTINGS
1434 // frames, so this block is purely defensive just in case that behavior
1435 // changes. Specifically, unlike unsolicited PING acks, unsolicited
1436 // SETTINGS acks should *never* make it this far.
1437 Isolate* isolate = env()->isolate();
1438 HandleScope scope(isolate);
1439 Local<Context> context = env()->context();
1440 Context::Scope context_scope(context);
1441 Local<Value> arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1442 MakeCallback(env()->http2session_on_error_function(), 1, &arg);
1443 }
1444
1445 // Callback used when data has been written to the stream.
OnStreamAfterWrite(WriteWrap * w,int status)1446 void Http2Session::OnStreamAfterWrite(WriteWrap* w, int status) {
1447 Debug(this, "write finished with status %d", status);
1448
1449 CHECK(is_write_in_progress());
1450 set_write_in_progress(false);
1451
1452 // Inform all pending writes about their completion.
1453 ClearOutgoing(status);
1454
1455 if (is_reading_stopped() &&
1456 !is_write_in_progress() &&
1457 nghttp2_session_want_read(session_.get())) {
1458 set_reading_stopped(false);
1459 stream_->ReadStart();
1460 }
1461
1462 if (is_destroyed()) {
1463 HandleScope scope(env()->isolate());
1464 MakeCallback(env()->ondone_string(), 0, nullptr);
1465 return;
1466 }
1467
1468 // If there is more incoming data queued up, consume it.
1469 if (stream_buf_offset_ > 0) {
1470 ConsumeHTTP2Data();
1471 }
1472
1473 if (!is_write_scheduled()) {
1474 // Schedule a new write if nghttp2 wants to send data.
1475 MaybeScheduleWrite();
1476 }
1477 }
1478
1479 // If the underlying nghttp2_session struct has data pending in its outbound
1480 // queue, MaybeScheduleWrite will schedule a SendPendingData() call to occur
1481 // on the next iteration of the Node.js event loop (using the SetImmediate
1482 // queue), but only if a write has not already been scheduled.
MaybeScheduleWrite()1483 void Http2Session::MaybeScheduleWrite() {
1484 CHECK(!is_write_scheduled());
1485 if (UNLIKELY(!session_))
1486 return;
1487
1488 if (nghttp2_session_want_write(session_.get())) {
1489 HandleScope handle_scope(env()->isolate());
1490 Debug(this, "scheduling write");
1491 set_write_scheduled();
1492 BaseObjectPtr<Http2Session> strong_ref{this};
1493 env()->SetImmediate([this, strong_ref](Environment* env) {
1494 if (!session_ || !is_write_scheduled()) {
1495 // This can happen e.g. when a stream was reset before this turn
1496 // of the event loop, in which case SendPendingData() is called early,
1497 // or the session was destroyed in the meantime.
1498 return;
1499 }
1500
1501 // Sending data may call arbitrary JS code, so keep track of
1502 // async context.
1503 HandleScope handle_scope(env->isolate());
1504 InternalCallbackScope callback_scope(this);
1505 SendPendingData();
1506 });
1507 }
1508 }
1509
MaybeStopReading()1510 void Http2Session::MaybeStopReading() {
1511 if (is_reading_stopped()) return;
1512 int want_read = nghttp2_session_want_read(session_.get());
1513 Debug(this, "wants read? %d", want_read);
1514 if (want_read == 0 || is_write_in_progress()) {
1515 set_reading_stopped();
1516 stream_->ReadStop();
1517 }
1518 }
1519
1520 // Unset the sending state, finish up all current writes, and reset
1521 // storage for data and metadata that was associated with these writes.
ClearOutgoing(int status)1522 void Http2Session::ClearOutgoing(int status) {
1523 CHECK(is_sending());
1524
1525 set_sending(false);
1526
1527 if (!outgoing_buffers_.empty()) {
1528 outgoing_storage_.clear();
1529 outgoing_length_ = 0;
1530
1531 std::vector<NgHttp2StreamWrite> current_outgoing_buffers_;
1532 current_outgoing_buffers_.swap(outgoing_buffers_);
1533 for (const NgHttp2StreamWrite& wr : current_outgoing_buffers_) {
1534 BaseObjectPtr<AsyncWrap> wrap = std::move(wr.req_wrap);
1535 if (wrap) {
1536 // TODO(addaleax): Pass `status` instead of 0, so that we actually error
1537 // out with the error from the write to the underlying protocol,
1538 // if one occurred.
1539 WriteWrap::FromObject(wrap)->Done(0);
1540 }
1541 }
1542 }
1543
1544 // Now that we've finished sending queued data, if there are any pending
1545 // RstStreams we should try sending again and then flush them one by one.
1546 if (!pending_rst_streams_.empty()) {
1547 std::vector<int32_t> current_pending_rst_streams;
1548 pending_rst_streams_.swap(current_pending_rst_streams);
1549
1550 SendPendingData();
1551
1552 for (int32_t stream_id : current_pending_rst_streams) {
1553 BaseObjectPtr<Http2Stream> stream = FindStream(stream_id);
1554 if (LIKELY(stream))
1555 stream->FlushRstStream();
1556 }
1557 }
1558 }
1559
PushOutgoingBuffer(NgHttp2StreamWrite && write)1560 void Http2Session::PushOutgoingBuffer(NgHttp2StreamWrite&& write) {
1561 outgoing_length_ += write.buf.len;
1562 outgoing_buffers_.emplace_back(std::move(write));
1563 }
1564
1565 // Queue a given block of data for sending. This always creates a copy,
1566 // so it is used for the cases in which nghttp2 requests sending of a
1567 // small chunk of data.
CopyDataIntoOutgoing(const uint8_t * src,size_t src_length)1568 void Http2Session::CopyDataIntoOutgoing(const uint8_t* src, size_t src_length) {
1569 size_t offset = outgoing_storage_.size();
1570 outgoing_storage_.resize(offset + src_length);
1571 memcpy(&outgoing_storage_[offset], src, src_length);
1572
1573 // Store with a base of `nullptr` initially, since future resizes
1574 // of the outgoing_buffers_ vector may invalidate the pointer.
1575 // The correct base pointers will be set later, before writing to the
1576 // underlying socket.
1577 PushOutgoingBuffer(NgHttp2StreamWrite {
1578 uv_buf_init(nullptr, src_length)
1579 });
1580 }
1581
1582 // Prompts nghttp2 to begin serializing it's pending data and pushes each
1583 // chunk out to the i/o socket to be sent. This is a particularly hot method
1584 // that will generally be called at least twice be event loop iteration.
1585 // This is a potential performance optimization target later.
1586 // Returns non-zero value if a write is already in progress.
SendPendingData()1587 uint8_t Http2Session::SendPendingData() {
1588 Debug(this, "sending pending data");
1589 // Do not attempt to send data on the socket if the destroying flag has
1590 // been set. That means everything is shutting down and the socket
1591 // will not be usable.
1592 if (is_destroyed())
1593 return 0;
1594 set_write_scheduled(false);
1595
1596 // SendPendingData should not be called recursively.
1597 if (is_sending())
1598 return 1;
1599 // This is cleared by ClearOutgoing().
1600 set_sending();
1601
1602 ssize_t src_length;
1603 const uint8_t* src;
1604
1605 CHECK(outgoing_buffers_.empty());
1606 CHECK(outgoing_storage_.empty());
1607
1608 // Part One: Gather data from nghttp2
1609
1610 while ((src_length = nghttp2_session_mem_send(session_.get(), &src)) > 0) {
1611 Debug(this, "nghttp2 has %d bytes to send", src_length);
1612 CopyDataIntoOutgoing(src, src_length);
1613 }
1614
1615 CHECK_NE(src_length, NGHTTP2_ERR_NOMEM);
1616
1617 if (stream_ == nullptr) {
1618 // It would seem nice to bail out earlier, but `nghttp2_session_mem_send()`
1619 // does take care of things like closing the individual streams after
1620 // a socket has been torn down, so we still need to call it.
1621 ClearOutgoing(UV_ECANCELED);
1622 return 0;
1623 }
1624
1625 // Part Two: Pass Data to the underlying stream
1626
1627 size_t count = outgoing_buffers_.size();
1628 if (count == 0) {
1629 ClearOutgoing(0);
1630 return 0;
1631 }
1632 MaybeStackBuffer<uv_buf_t, 32> bufs;
1633 bufs.AllocateSufficientStorage(count);
1634
1635 // Set the buffer base pointers for copied data that ended up in the
1636 // sessions's own storage since it might have shifted around during gathering.
1637 // (Those are marked by having .base == nullptr.)
1638 size_t offset = 0;
1639 size_t i = 0;
1640 for (const NgHttp2StreamWrite& write : outgoing_buffers_) {
1641 statistics_.data_sent += write.buf.len;
1642 if (write.buf.base == nullptr) {
1643 bufs[i++] = uv_buf_init(
1644 reinterpret_cast<char*>(outgoing_storage_.data() + offset),
1645 write.buf.len);
1646 offset += write.buf.len;
1647 } else {
1648 bufs[i++] = write.buf;
1649 }
1650 }
1651
1652 chunks_sent_since_last_write_++;
1653
1654 CHECK(!is_write_in_progress());
1655 set_write_in_progress();
1656 StreamWriteResult res = underlying_stream()->Write(*bufs, count);
1657 if (!res.async) {
1658 set_write_in_progress(false);
1659 ClearOutgoing(res.err);
1660 }
1661
1662 MaybeStopReading();
1663
1664 return 0;
1665 }
1666
1667
1668 // This callback is called from nghttp2 when it wants to send DATA frames for a
1669 // given Http2Stream, when we set the `NGHTTP2_DATA_FLAG_NO_COPY` flag earlier
1670 // in the Http2Stream::Provider::Stream::OnRead callback.
1671 // 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)1672 int Http2Session::OnSendData(
1673 nghttp2_session* session_,
1674 nghttp2_frame* frame,
1675 const uint8_t* framehd,
1676 size_t length,
1677 nghttp2_data_source* source,
1678 void* user_data) {
1679 Http2Session* session = static_cast<Http2Session*>(user_data);
1680 BaseObjectPtr<Http2Stream> stream = session->FindStream(frame->hd.stream_id);
1681 if (!stream) return 0;
1682
1683 // Send the frame header + a byte that indicates padding length.
1684 session->CopyDataIntoOutgoing(framehd, 9);
1685 if (frame->data.padlen > 0) {
1686 uint8_t padding_byte = frame->data.padlen - 1;
1687 CHECK_EQ(padding_byte, frame->data.padlen - 1);
1688 session->CopyDataIntoOutgoing(&padding_byte, 1);
1689 }
1690
1691 Debug(session, "nghttp2 has %d bytes to send directly", length);
1692 while (length > 0) {
1693 // nghttp2 thinks that there is data available (length > 0), which means
1694 // we told it so, which means that we *should* have data available.
1695 CHECK(!stream->queue_.empty());
1696
1697 NgHttp2StreamWrite& write = stream->queue_.front();
1698 if (write.buf.len <= length) {
1699 // This write does not suffice by itself, so we can consume it completely.
1700 length -= write.buf.len;
1701 session->PushOutgoingBuffer(std::move(write));
1702 stream->queue_.pop();
1703 continue;
1704 }
1705
1706 // Slice off `length` bytes of the first write in the queue.
1707 session->PushOutgoingBuffer(NgHttp2StreamWrite {
1708 uv_buf_init(write.buf.base, length)
1709 });
1710 write.buf.base += length;
1711 write.buf.len -= length;
1712 break;
1713 }
1714
1715 if (frame->data.padlen > 0) {
1716 // Send padding if that was requested.
1717 session->PushOutgoingBuffer(NgHttp2StreamWrite {
1718 uv_buf_init(const_cast<char*>(zero_bytes_256), frame->data.padlen - 1)
1719 });
1720 }
1721
1722 return 0;
1723 }
1724
1725 // Creates a new Http2Stream and submits a new http2 request.
SubmitRequest(const Http2Priority & priority,const Http2Headers & headers,int32_t * ret,int options)1726 Http2Stream* Http2Session::SubmitRequest(
1727 const Http2Priority& priority,
1728 const Http2Headers& headers,
1729 int32_t* ret,
1730 int options) {
1731 Debug(this, "submitting request");
1732 Http2Scope h2scope(this);
1733 Http2Stream* stream = nullptr;
1734 Http2Stream::Provider::Stream prov(options);
1735 *ret = nghttp2_submit_request(
1736 session_.get(),
1737 &priority,
1738 headers.data(),
1739 headers.length(),
1740 *prov,
1741 nullptr);
1742 CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
1743 if (LIKELY(*ret > 0))
1744 stream = Http2Stream::New(this, *ret, NGHTTP2_HCAT_HEADERS, options);
1745 return stream;
1746 }
1747
OnStreamAlloc(size_t suggested_size)1748 uv_buf_t Http2Session::OnStreamAlloc(size_t suggested_size) {
1749 return AllocatedBuffer::AllocateManaged(env(), suggested_size).release();
1750 }
1751
1752 // Callback used to receive inbound data from the i/o stream
OnStreamRead(ssize_t nread,const uv_buf_t & buf_)1753 void Http2Session::OnStreamRead(ssize_t nread, const uv_buf_t& buf_) {
1754 HandleScope handle_scope(env()->isolate());
1755 Context::Scope context_scope(env()->context());
1756 Http2Scope h2scope(this);
1757 CHECK_NOT_NULL(stream_);
1758 Debug(this, "receiving %d bytes, offset %d", nread, stream_buf_offset_);
1759 AllocatedBuffer buf(env(), buf_);
1760
1761 // Only pass data on if nread > 0
1762 if (nread <= 0) {
1763 if (nread < 0) {
1764 PassReadErrorToPreviousListener(nread);
1765 }
1766 return;
1767 }
1768
1769 statistics_.data_received += nread;
1770
1771 if (LIKELY(stream_buf_offset_ == 0)) {
1772 // Shrink to the actual amount of used data.
1773 buf.Resize(nread);
1774 } else {
1775 // This is a very unlikely case, and should only happen if the ReadStart()
1776 // call in OnStreamAfterWrite() immediately provides data. If that does
1777 // happen, we concatenate the data we received with the already-stored
1778 // pending input data, slicing off the already processed part.
1779 size_t pending_len = stream_buf_.len - stream_buf_offset_;
1780 AllocatedBuffer new_buf =
1781 AllocatedBuffer::AllocateManaged(env(), pending_len + nread);
1782 memcpy(new_buf.data(), stream_buf_.base + stream_buf_offset_, pending_len);
1783 memcpy(new_buf.data() + pending_len, buf.data(), nread);
1784
1785 buf = std::move(new_buf);
1786 nread = buf.size();
1787 stream_buf_offset_ = 0;
1788 stream_buf_ab_.Reset();
1789
1790 // We have now fully processed the stream_buf_ input chunk (by moving the
1791 // remaining part into buf, which will be accounted for below).
1792 DecrementCurrentSessionMemory(stream_buf_.len);
1793 }
1794
1795 IncrementCurrentSessionMemory(nread);
1796
1797 // Remember the current buffer, so that OnDataChunkReceived knows the
1798 // offset of a DATA frame's data into the socket read buffer.
1799 stream_buf_ = uv_buf_init(buf.data(), static_cast<unsigned int>(nread));
1800
1801 Isolate* isolate = env()->isolate();
1802
1803 // Store this so we can create an ArrayBuffer for read data from it.
1804 // DATA frames will be emitted as slices of that ArrayBuffer to avoid having
1805 // to copy memory.
1806 stream_buf_allocation_ = std::move(buf);
1807
1808 ssize_t ret = ConsumeHTTP2Data();
1809
1810 if (UNLIKELY(ret < 0)) {
1811 Debug(this, "fatal error receiving data: %d", ret);
1812 Local<Value> arg = Integer::New(isolate, static_cast<int32_t>(ret));
1813 MakeCallback(env()->http2session_on_error_function(), 1, &arg);
1814 return;
1815 }
1816
1817 MaybeStopReading();
1818 }
1819
HasWritesOnSocketForStream(Http2Stream * stream)1820 bool Http2Session::HasWritesOnSocketForStream(Http2Stream* stream) {
1821 for (const NgHttp2StreamWrite& wr : outgoing_buffers_) {
1822 if (wr.req_wrap && WriteWrap::FromObject(wr.req_wrap)->stream() == stream)
1823 return true;
1824 }
1825 return false;
1826 }
1827
1828 // Every Http2Session session is tightly bound to a single i/o StreamBase
1829 // (typically a net.Socket or tls.TLSSocket). The lifecycle of the two is
1830 // tightly coupled with all data transfer between the two happening at the
1831 // C++ layer via the StreamBase API.
Consume(Local<Object> stream_obj)1832 void Http2Session::Consume(Local<Object> stream_obj) {
1833 StreamBase* stream = StreamBase::FromObject(stream_obj);
1834 stream->PushStreamListener(this);
1835 Debug(this, "i/o stream consumed");
1836 }
1837
1838 // Allow injecting of data from JS
1839 // This is used when the socket has already some data received
1840 // before our listener was attached
1841 // https://github.com/nodejs/node/issues/35475
Receive(const FunctionCallbackInfo<Value> & args)1842 void Http2Session::Receive(const FunctionCallbackInfo<Value>& args) {
1843 Http2Session* session;
1844 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
1845 CHECK(args[0]->IsObject());
1846
1847 ArrayBufferViewContents<char> buffer(args[0]);
1848 const char* data = buffer.data();
1849 size_t len = buffer.length();
1850 Debug(session, "Receiving %zu bytes injected from JS", len);
1851
1852 // Copy given buffer
1853 while (len > 0) {
1854 uv_buf_t buf = session->OnStreamAlloc(len);
1855 size_t copy = buf.len > len ? len : buf.len;
1856 memcpy(buf.base, data, copy);
1857 buf.len = copy;
1858 session->OnStreamRead(copy, buf);
1859
1860 data += copy;
1861 len -= copy;
1862 }
1863 }
1864
New(Http2Session * session,int32_t id,nghttp2_headers_category category,int options)1865 Http2Stream* Http2Stream::New(Http2Session* session,
1866 int32_t id,
1867 nghttp2_headers_category category,
1868 int options) {
1869 Local<Object> obj;
1870 if (!session->env()
1871 ->http2stream_constructor_template()
1872 ->NewInstance(session->env()->context())
1873 .ToLocal(&obj)) {
1874 return nullptr;
1875 }
1876 return new Http2Stream(session, obj, id, category, options);
1877 }
1878
Http2Stream(Http2Session * session,Local<Object> obj,int32_t id,nghttp2_headers_category category,int options)1879 Http2Stream::Http2Stream(Http2Session* session,
1880 Local<Object> obj,
1881 int32_t id,
1882 nghttp2_headers_category category,
1883 int options)
1884 : AsyncWrap(session->env(), obj, AsyncWrap::PROVIDER_HTTP2STREAM),
1885 StreamBase(session->env()),
1886 session_(session),
1887 id_(id),
1888 current_headers_category_(category) {
1889 MakeWeak();
1890 StreamBase::AttachToObject(GetObject());
1891 statistics_.start_time = uv_hrtime();
1892
1893 // Limit the number of header pairs
1894 max_header_pairs_ = session->max_header_pairs();
1895 if (max_header_pairs_ == 0) {
1896 max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
1897 }
1898 current_headers_.reserve(std::min(max_header_pairs_, 12u));
1899
1900 // Limit the number of header octets
1901 max_header_length_ =
1902 std::min(
1903 nghttp2_session_get_local_settings(
1904 session->session(),
1905 NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE),
1906 MAX_MAX_HEADER_LIST_SIZE);
1907
1908 if (options & STREAM_OPTION_GET_TRAILERS)
1909 set_has_trailers();
1910
1911 PushStreamListener(&stream_listener_);
1912
1913 if (options & STREAM_OPTION_EMPTY_PAYLOAD)
1914 Shutdown();
1915 session->AddStream(this);
1916 }
1917
~Http2Stream()1918 Http2Stream::~Http2Stream() {
1919 Debug(this, "tearing down stream");
1920 }
1921
MemoryInfo(MemoryTracker * tracker) const1922 void Http2Stream::MemoryInfo(MemoryTracker* tracker) const {
1923 tracker->TrackField("current_headers", current_headers_);
1924 tracker->TrackField("queue", queue_);
1925 }
1926
diagnostic_name() const1927 std::string Http2Stream::diagnostic_name() const {
1928 return "HttpStream " + std::to_string(id()) + " (" +
1929 std::to_string(static_cast<int64_t>(get_async_id())) + ") [" +
1930 session()->diagnostic_name() + "]";
1931 }
1932
1933 // Notify the Http2Stream that a new block of HEADERS is being processed.
StartHeaders(nghttp2_headers_category category)1934 void Http2Stream::StartHeaders(nghttp2_headers_category category) {
1935 Debug(this, "starting headers, category: %d", category);
1936 CHECK(!this->is_destroyed());
1937 session_->DecrementCurrentSessionMemory(current_headers_length_);
1938 current_headers_length_ = 0;
1939 current_headers_.clear();
1940 current_headers_category_ = category;
1941 }
1942
1943
operator *() const1944 nghttp2_stream* Http2Stream::operator*() const { return stream(); }
1945
stream() const1946 nghttp2_stream* Http2Stream::stream() const {
1947 return nghttp2_session_find_stream(session_->session(), id_);
1948 }
1949
Close(int32_t code)1950 void Http2Stream::Close(int32_t code) {
1951 CHECK(!this->is_destroyed());
1952 set_closed();
1953 code_ = code;
1954 Debug(this, "closed with code %d", code);
1955 }
1956
CreateShutdownWrap(v8::Local<v8::Object> object)1957 ShutdownWrap* Http2Stream::CreateShutdownWrap(v8::Local<v8::Object> object) {
1958 // DoShutdown() always finishes synchronously, so there's no need to create
1959 // a structure to store asynchronous context.
1960 return nullptr;
1961 }
1962
DoShutdown(ShutdownWrap * req_wrap)1963 int Http2Stream::DoShutdown(ShutdownWrap* req_wrap) {
1964 if (is_destroyed())
1965 return UV_EPIPE;
1966
1967 {
1968 Http2Scope h2scope(this);
1969 set_not_writable();
1970 CHECK_NE(nghttp2_session_resume_data(
1971 session_->session(), id_),
1972 NGHTTP2_ERR_NOMEM);
1973 Debug(this, "writable side shutdown");
1974 }
1975 return 1;
1976 }
1977
1978 // Destroy the Http2Stream and render it unusable. Actual resources for the
1979 // Stream will not be freed until the next tick of the Node.js event loop
1980 // using the SetImmediate queue.
Destroy()1981 void Http2Stream::Destroy() {
1982 // Do nothing if this stream instance is already destroyed
1983 if (is_destroyed())
1984 return;
1985 if (session_->has_pending_rststream(id_))
1986 FlushRstStream();
1987 set_destroyed();
1988
1989 Debug(this, "destroying stream");
1990
1991 // Wait until the start of the next loop to delete because there
1992 // may still be some pending operations queued for this stream.
1993 BaseObjectPtr<Http2Stream> strong_ref = session_->RemoveStream(id_);
1994 if (strong_ref) {
1995 env()->SetImmediate([this, strong_ref = std::move(strong_ref)](
1996 Environment* env) {
1997 // Free any remaining outgoing data chunks here. This should be done
1998 // here because it's possible for destroy to have been called while
1999 // we still have queued outbound writes.
2000 while (!queue_.empty()) {
2001 NgHttp2StreamWrite& head = queue_.front();
2002 if (head.req_wrap)
2003 WriteWrap::FromObject(head.req_wrap)->Done(UV_ECANCELED);
2004 queue_.pop();
2005 }
2006
2007 // We can destroy the stream now if there are no writes for it
2008 // already on the socket. Otherwise, we'll wait for the garbage collector
2009 // to take care of cleaning up.
2010 if (session() == nullptr ||
2011 !session()->HasWritesOnSocketForStream(this)) {
2012 // Delete once strong_ref goes out of scope.
2013 Detach();
2014 }
2015 });
2016 }
2017
2018 statistics_.end_time = uv_hrtime();
2019 session_->statistics_.stream_average_duration =
2020 ((statistics_.end_time - statistics_.start_time) /
2021 session_->statistics_.stream_count) / 1e6;
2022 EmitStatistics();
2023 }
2024
2025
2026 // Initiates a response on the Http2Stream using data provided via the
2027 // StreamBase Streams API.
SubmitResponse(const Http2Headers & headers,int options)2028 int Http2Stream::SubmitResponse(const Http2Headers& headers, int options) {
2029 CHECK(!this->is_destroyed());
2030 Http2Scope h2scope(this);
2031 Debug(this, "submitting response");
2032 if (options & STREAM_OPTION_GET_TRAILERS)
2033 set_has_trailers();
2034
2035 if (!is_writable())
2036 options |= STREAM_OPTION_EMPTY_PAYLOAD;
2037
2038 Http2Stream::Provider::Stream prov(this, options);
2039 int ret = nghttp2_submit_response(
2040 session_->session(),
2041 id_,
2042 headers.data(),
2043 headers.length(),
2044 *prov);
2045 CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2046 return ret;
2047 }
2048
2049
2050 // Submit informational headers for a stream.
SubmitInfo(const Http2Headers & headers)2051 int Http2Stream::SubmitInfo(const Http2Headers& headers) {
2052 CHECK(!this->is_destroyed());
2053 Http2Scope h2scope(this);
2054 Debug(this, "sending %d informational headers", headers.length());
2055 int ret = nghttp2_submit_headers(
2056 session_->session(),
2057 NGHTTP2_FLAG_NONE,
2058 id_,
2059 nullptr,
2060 headers.data(),
2061 headers.length(),
2062 nullptr);
2063 CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2064 return ret;
2065 }
2066
OnTrailers()2067 void Http2Stream::OnTrailers() {
2068 Debug(this, "let javascript know we are ready for trailers");
2069 CHECK(!this->is_destroyed());
2070 Isolate* isolate = env()->isolate();
2071 HandleScope scope(isolate);
2072 Local<Context> context = env()->context();
2073 Context::Scope context_scope(context);
2074 set_has_trailers(false);
2075 MakeCallback(env()->http2session_on_stream_trailers_function(), 0, nullptr);
2076 }
2077
2078 // Submit informational headers for a stream.
SubmitTrailers(const Http2Headers & headers)2079 int Http2Stream::SubmitTrailers(const Http2Headers& headers) {
2080 CHECK(!this->is_destroyed());
2081 Http2Scope h2scope(this);
2082 Debug(this, "sending %d trailers", headers.length());
2083 int ret;
2084 // Sending an empty trailers frame poses problems in Safari, Edge & IE.
2085 // Instead we can just send an empty data frame with NGHTTP2_FLAG_END_STREAM
2086 // to indicate that the stream is ready to be closed.
2087 if (headers.length() == 0) {
2088 Http2Stream::Provider::Stream prov(this, 0);
2089 ret = nghttp2_submit_data(
2090 session_->session(),
2091 NGHTTP2_FLAG_END_STREAM,
2092 id_,
2093 *prov);
2094 } else {
2095 ret = nghttp2_submit_trailer(
2096 session_->session(),
2097 id_,
2098 headers.data(),
2099 headers.length());
2100 }
2101 CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2102 return ret;
2103 }
2104
2105 // Submit a PRIORITY frame to the connected peer.
SubmitPriority(const Http2Priority & priority,bool silent)2106 int Http2Stream::SubmitPriority(const Http2Priority& priority,
2107 bool silent) {
2108 CHECK(!this->is_destroyed());
2109 Http2Scope h2scope(this);
2110 Debug(this, "sending priority spec");
2111 int ret = silent ?
2112 nghttp2_session_change_stream_priority(
2113 session_->session(),
2114 id_,
2115 &priority) :
2116 nghttp2_submit_priority(
2117 session_->session(),
2118 NGHTTP2_FLAG_NONE,
2119 id_, &priority);
2120 CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2121 return ret;
2122 }
2123
2124 // Closes the Http2Stream by submitting an RST_STREAM frame to the connected
2125 // peer.
SubmitRstStream(const uint32_t code)2126 void Http2Stream::SubmitRstStream(const uint32_t code) {
2127 CHECK(!this->is_destroyed());
2128 code_ = code;
2129
2130 auto is_stream_cancel = [](const uint32_t code) {
2131 return code == NGHTTP2_CANCEL;
2132 };
2133
2134 // If RST_STREAM frame is received with error code NGHTTP2_CANCEL,
2135 // add it to the pending list and don't force purge the data. It is
2136 // to avoids the double free error due to unwanted behavior of nghttp2.
2137
2138 // Add stream to the pending list only if it is received with scope
2139 // below in the stack. The pending list may not get processed
2140 // if RST_STREAM received is not in scope and added to the list
2141 // causing endpoint to hang.
2142 if (session_->is_in_scope() && is_stream_cancel(code)) {
2143 session_->AddPendingRstStream(id_);
2144 return;
2145 }
2146
2147
2148 // If possible, force a purge of any currently pending data here to make sure
2149 // it is sent before closing the stream. If it returns non-zero then we need
2150 // to wait until the current write finishes and try again to avoid nghttp2
2151 // behaviour where it prioritizes RstStream over everything else.
2152 if (session_->SendPendingData() != 0) {
2153 session_->AddPendingRstStream(id_);
2154 return;
2155 }
2156
2157 FlushRstStream();
2158 }
2159
FlushRstStream()2160 void Http2Stream::FlushRstStream() {
2161 if (is_destroyed())
2162 return;
2163 Http2Scope h2scope(this);
2164 CHECK_EQ(nghttp2_submit_rst_stream(
2165 session_->session(),
2166 NGHTTP2_FLAG_NONE,
2167 id_,
2168 code_), 0);
2169 }
2170
2171
2172 // Submit a push promise and create the associated Http2Stream if successful.
SubmitPushPromise(const Http2Headers & headers,int32_t * ret,int options)2173 Http2Stream* Http2Stream::SubmitPushPromise(const Http2Headers& headers,
2174 int32_t* ret,
2175 int options) {
2176 CHECK(!this->is_destroyed());
2177 Http2Scope h2scope(this);
2178 Debug(this, "sending push promise");
2179 *ret = nghttp2_submit_push_promise(
2180 session_->session(),
2181 NGHTTP2_FLAG_NONE,
2182 id_,
2183 headers.data(),
2184 headers.length(),
2185 nullptr);
2186 CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
2187 Http2Stream* stream = nullptr;
2188 if (*ret > 0) {
2189 stream = Http2Stream::New(
2190 session_.get(), *ret, NGHTTP2_HCAT_HEADERS, options);
2191 }
2192
2193 return stream;
2194 }
2195
2196 // Switch the StreamBase into flowing mode to begin pushing chunks of data
2197 // out to JS land.
ReadStart()2198 int Http2Stream::ReadStart() {
2199 Http2Scope h2scope(this);
2200 CHECK(!this->is_destroyed());
2201 set_reading();
2202
2203 Debug(this, "reading starting");
2204
2205 // Tell nghttp2 about our consumption of the data that was handed
2206 // off to JS land.
2207 nghttp2_session_consume_stream(
2208 session_->session(),
2209 id_,
2210 inbound_consumed_data_while_paused_);
2211 inbound_consumed_data_while_paused_ = 0;
2212
2213 return 0;
2214 }
2215
2216 // Switch the StreamBase into paused mode.
ReadStop()2217 int Http2Stream::ReadStop() {
2218 CHECK(!this->is_destroyed());
2219 if (!is_reading())
2220 return 0;
2221 set_paused();
2222 Debug(this, "reading stopped");
2223 return 0;
2224 }
2225
2226 // The Http2Stream class is a subclass of StreamBase. The DoWrite method
2227 // receives outbound chunks of data to send as outbound DATA frames. These
2228 // are queued in an internal linked list of uv_buf_t structs that are sent
2229 // when nghttp2 is ready to serialize the data frame.
2230 //
2231 // Queue the given set of uv_but_t handles for writing to an
2232 // nghttp2_stream. The WriteWrap's Done callback will be invoked once the
2233 // chunks of data have been flushed to the underlying nghttp2_session.
2234 // Note that this does *not* mean that the data has been flushed
2235 // to the socket yet.
DoWrite(WriteWrap * req_wrap,uv_buf_t * bufs,size_t nbufs,uv_stream_t * send_handle)2236 int Http2Stream::DoWrite(WriteWrap* req_wrap,
2237 uv_buf_t* bufs,
2238 size_t nbufs,
2239 uv_stream_t* send_handle) {
2240 CHECK_NULL(send_handle);
2241 Http2Scope h2scope(this);
2242 if (!is_writable() || is_destroyed()) {
2243 req_wrap->Done(UV_EOF);
2244 return 0;
2245 }
2246 Debug(this, "queuing %d buffers to send", nbufs);
2247 for (size_t i = 0; i < nbufs; ++i) {
2248 // Store the req_wrap on the last write info in the queue, so that it is
2249 // only marked as finished once all buffers associated with it are finished.
2250 queue_.emplace(NgHttp2StreamWrite {
2251 BaseObjectPtr<AsyncWrap>(
2252 i == nbufs - 1 ? req_wrap->GetAsyncWrap() : nullptr),
2253 bufs[i]
2254 });
2255 IncrementAvailableOutboundLength(bufs[i].len);
2256 }
2257 CHECK_NE(nghttp2_session_resume_data(
2258 session_->session(),
2259 id_), NGHTTP2_ERR_NOMEM);
2260 return 0;
2261 }
2262
2263 // Ads a header to the Http2Stream. Note that the header name and value are
2264 // provided using a buffer structure provided by nghttp2 that allows us to
2265 // avoid unnecessary memcpy's. Those buffers are ref counted. The ref count
2266 // is incremented here and are decremented when the header name and values
2267 // are garbage collected later.
AddHeader(nghttp2_rcbuf * name,nghttp2_rcbuf * value,uint8_t flags)2268 bool Http2Stream::AddHeader(nghttp2_rcbuf* name,
2269 nghttp2_rcbuf* value,
2270 uint8_t flags) {
2271 CHECK(!this->is_destroyed());
2272
2273 if (Http2RcBufferPointer::IsZeroLength(name))
2274 return true; // Ignore empty headers.
2275
2276 Http2Header header(env(), name, value, flags);
2277 size_t length = header.length() + 32;
2278 // A header can only be added if we have not exceeded the maximum number
2279 // of headers and the session has memory available for it.
2280 if (!session_->has_available_session_memory(length) ||
2281 current_headers_.size() == max_header_pairs_ ||
2282 current_headers_length_ + length > max_header_length_) {
2283 return false;
2284 }
2285
2286 if (statistics_.first_header == 0)
2287 statistics_.first_header = uv_hrtime();
2288
2289 current_headers_.push_back(std::move(header));
2290
2291 current_headers_length_ += length;
2292 session_->IncrementCurrentSessionMemory(length);
2293 return true;
2294 }
2295
2296 // A Provider is the thing that provides outbound DATA frame data.
Provider(Http2Stream * stream,int options)2297 Http2Stream::Provider::Provider(Http2Stream* stream, int options) {
2298 CHECK(!stream->is_destroyed());
2299 provider_.source.ptr = stream;
2300 empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
2301 }
2302
Provider(int options)2303 Http2Stream::Provider::Provider(int options) {
2304 provider_.source.ptr = nullptr;
2305 empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
2306 }
2307
~Provider()2308 Http2Stream::Provider::~Provider() {
2309 provider_.source.ptr = nullptr;
2310 }
2311
2312 // The Stream Provider pulls data from a linked list of uv_buf_t structs
2313 // built via the StreamBase API and the Streams js API.
Stream(int options)2314 Http2Stream::Provider::Stream::Stream(int options)
2315 : Http2Stream::Provider(options) {
2316 provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
2317 }
2318
Stream(Http2Stream * stream,int options)2319 Http2Stream::Provider::Stream::Stream(Http2Stream* stream, int options)
2320 : Http2Stream::Provider(stream, options) {
2321 provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
2322 }
2323
OnRead(nghttp2_session * handle,int32_t id,uint8_t * buf,size_t length,uint32_t * flags,nghttp2_data_source * source,void * user_data)2324 ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
2325 int32_t id,
2326 uint8_t* buf,
2327 size_t length,
2328 uint32_t* flags,
2329 nghttp2_data_source* source,
2330 void* user_data) {
2331 Http2Session* session = static_cast<Http2Session*>(user_data);
2332 Debug(session, "reading outbound data for stream %d", id);
2333 BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
2334 if (!stream) return 0;
2335 if (stream->statistics_.first_byte_sent == 0)
2336 stream->statistics_.first_byte_sent = uv_hrtime();
2337 CHECK_EQ(id, stream->id());
2338
2339 size_t amount = 0; // amount of data being sent in this data frame.
2340
2341 // Remove all empty chunks from the head of the queue.
2342 // This is done here so that .write('', cb) is still a meaningful way to
2343 // find out when the HTTP2 stream wants to consume data, and because the
2344 // StreamBase API allows empty input chunks.
2345 while (!stream->queue_.empty() && stream->queue_.front().buf.len == 0) {
2346 BaseObjectPtr<AsyncWrap> finished =
2347 std::move(stream->queue_.front().req_wrap);
2348 stream->queue_.pop();
2349 if (finished)
2350 WriteWrap::FromObject(finished)->Done(0);
2351 }
2352
2353 if (!stream->queue_.empty()) {
2354 Debug(session, "stream %d has pending outbound data", id);
2355 amount = std::min(stream->available_outbound_length_, length);
2356 Debug(session, "sending %d bytes for data frame on stream %d", amount, id);
2357 if (amount > 0) {
2358 // Just return the length, let Http2Session::OnSendData take care of
2359 // actually taking the buffers out of the queue.
2360 *flags |= NGHTTP2_DATA_FLAG_NO_COPY;
2361 stream->DecrementAvailableOutboundLength(amount);
2362 }
2363 }
2364
2365 if (amount == 0 && stream->is_writable()) {
2366 CHECK(stream->queue_.empty());
2367 Debug(session, "deferring stream %d", id);
2368 stream->EmitWantsWrite(length);
2369 if (stream->available_outbound_length_ > 0 || !stream->is_writable()) {
2370 // EmitWantsWrite() did something interesting synchronously, restart:
2371 return OnRead(handle, id, buf, length, flags, source, user_data);
2372 }
2373 return NGHTTP2_ERR_DEFERRED;
2374 }
2375
2376 if (stream->available_outbound_length_ == 0 && !stream->is_writable()) {
2377 Debug(session, "no more data for stream %d", id);
2378 *flags |= NGHTTP2_DATA_FLAG_EOF;
2379 if (stream->has_trailers()) {
2380 *flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
2381 stream->OnTrailers();
2382 }
2383 }
2384
2385 stream->statistics_.sent_bytes += amount;
2386 return amount;
2387 }
2388
IncrementAvailableOutboundLength(size_t amount)2389 void Http2Stream::IncrementAvailableOutboundLength(size_t amount) {
2390 available_outbound_length_ += amount;
2391 session_->IncrementCurrentSessionMemory(amount);
2392 }
2393
DecrementAvailableOutboundLength(size_t amount)2394 void Http2Stream::DecrementAvailableOutboundLength(size_t amount) {
2395 available_outbound_length_ -= amount;
2396 session_->DecrementCurrentSessionMemory(amount);
2397 }
2398
2399
2400 // Implementation of the JavaScript API
2401
2402 // Fetches the string description of a nghttp2 error code and passes that
2403 // back to JS land
HttpErrorString(const FunctionCallbackInfo<Value> & args)2404 void HttpErrorString(const FunctionCallbackInfo<Value>& args) {
2405 Environment* env = Environment::GetCurrent(args);
2406 uint32_t val = args[0]->Uint32Value(env->context()).ToChecked();
2407 args.GetReturnValue().Set(
2408 OneByteString(
2409 env->isolate(),
2410 reinterpret_cast<const uint8_t*>(nghttp2_strerror(val))));
2411 }
2412
2413
2414 // Serializes the settings object into a Buffer instance that
2415 // would be suitable, for instance, for creating the Base64
2416 // output for an HTTP2-Settings header field.
PackSettings(const FunctionCallbackInfo<Value> & args)2417 void PackSettings(const FunctionCallbackInfo<Value>& args) {
2418 Http2State* state = Environment::GetBindingData<Http2State>(args);
2419 args.GetReturnValue().Set(Http2Settings::Pack(state));
2420 }
2421
2422 // A TypedArray instance is shared between C++ and JS land to contain the
2423 // default SETTINGS. RefreshDefaultSettings updates that TypedArray with the
2424 // default values.
RefreshDefaultSettings(const FunctionCallbackInfo<Value> & args)2425 void RefreshDefaultSettings(const FunctionCallbackInfo<Value>& args) {
2426 Http2State* state = Environment::GetBindingData<Http2State>(args);
2427 Http2Settings::RefreshDefaults(state);
2428 }
2429
2430 // Sets the next stream ID the Http2Session. If successful, returns true.
SetNextStreamID(const FunctionCallbackInfo<Value> & args)2431 void Http2Session::SetNextStreamID(const FunctionCallbackInfo<Value>& args) {
2432 Environment* env = Environment::GetCurrent(args);
2433 Http2Session* session;
2434 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2435 int32_t id = args[0]->Int32Value(env->context()).ToChecked();
2436 if (nghttp2_session_set_next_stream_id(session->session(), id) < 0) {
2437 Debug(session, "failed to set next stream id to %d", id);
2438 return args.GetReturnValue().Set(false);
2439 }
2440 args.GetReturnValue().Set(true);
2441 Debug(session, "set next stream id to %d", id);
2442 }
2443
2444 // Set local window size (local endpoints's window size) to the given
2445 // window_size for the stream denoted by 0.
2446 // This function returns 0 if it succeeds, or one of a negative codes
SetLocalWindowSize(const FunctionCallbackInfo<Value> & args)2447 void Http2Session::SetLocalWindowSize(
2448 const FunctionCallbackInfo<Value>& args) {
2449 Environment* env = Environment::GetCurrent(args);
2450 Http2Session* session;
2451 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2452
2453 int32_t window_size = args[0]->Int32Value(env->context()).ToChecked();
2454
2455 int result = nghttp2_session_set_local_window_size(
2456 session->session(), NGHTTP2_FLAG_NONE, 0, window_size);
2457
2458 args.GetReturnValue().Set(result);
2459
2460 Debug(session, "set local window size to %d", window_size);
2461 }
2462
2463 // A TypedArray instance is shared between C++ and JS land to contain the
2464 // SETTINGS (either remote or local). RefreshSettings updates the current
2465 // values established for each of the settings so those can be read in JS land.
2466 template <get_setting fn>
RefreshSettings(const FunctionCallbackInfo<Value> & args)2467 void Http2Session::RefreshSettings(const FunctionCallbackInfo<Value>& args) {
2468 Http2Session* session;
2469 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2470 Http2Settings::Update(session, fn);
2471 Debug(session, "settings refreshed for session");
2472 }
2473
2474 // A TypedArray instance is shared between C++ and JS land to contain state
2475 // information of the current Http2Session. This updates the values in the
2476 // TypedArray so those can be read in JS land.
RefreshState(const FunctionCallbackInfo<Value> & args)2477 void Http2Session::RefreshState(const FunctionCallbackInfo<Value>& args) {
2478 Http2Session* session;
2479 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2480 Debug(session, "refreshing state");
2481
2482 AliasedFloat64Array& buffer = session->http2_state()->session_state_buffer;
2483
2484 nghttp2_session* s = session->session();
2485
2486 buffer[IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE] =
2487 nghttp2_session_get_effective_local_window_size(s);
2488 buffer[IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH] =
2489 nghttp2_session_get_effective_recv_data_length(s);
2490 buffer[IDX_SESSION_STATE_NEXT_STREAM_ID] =
2491 nghttp2_session_get_next_stream_id(s);
2492 buffer[IDX_SESSION_STATE_LOCAL_WINDOW_SIZE] =
2493 nghttp2_session_get_local_window_size(s);
2494 buffer[IDX_SESSION_STATE_LAST_PROC_STREAM_ID] =
2495 nghttp2_session_get_last_proc_stream_id(s);
2496 buffer[IDX_SESSION_STATE_REMOTE_WINDOW_SIZE] =
2497 nghttp2_session_get_remote_window_size(s);
2498 buffer[IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE] =
2499 static_cast<double>(nghttp2_session_get_outbound_queue_size(s));
2500 buffer[IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE] =
2501 static_cast<double>(nghttp2_session_get_hd_deflate_dynamic_table_size(s));
2502 buffer[IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE] =
2503 static_cast<double>(nghttp2_session_get_hd_inflate_dynamic_table_size(s));
2504 }
2505
2506
2507 // Constructor for new Http2Session instances.
New(const FunctionCallbackInfo<Value> & args)2508 void Http2Session::New(const FunctionCallbackInfo<Value>& args) {
2509 Http2State* state = Environment::GetBindingData<Http2State>(args);
2510 Environment* env = state->env();
2511 CHECK(args.IsConstructCall());
2512 SessionType type =
2513 static_cast<SessionType>(
2514 args[0]->Int32Value(env->context()).ToChecked());
2515 Http2Session* session = new Http2Session(state, args.This(), type);
2516 session->get_async_id(); // avoid compiler warning
2517 Debug(session, "session created");
2518 }
2519
2520
2521 // Binds the Http2Session with a StreamBase used for i/o
Consume(const FunctionCallbackInfo<Value> & args)2522 void Http2Session::Consume(const FunctionCallbackInfo<Value>& args) {
2523 Http2Session* session;
2524 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2525 CHECK(args[0]->IsObject());
2526 session->Consume(args[0].As<Object>());
2527 }
2528
2529 // Destroys the Http2Session instance and renders it unusable
Destroy(const FunctionCallbackInfo<Value> & args)2530 void Http2Session::Destroy(const FunctionCallbackInfo<Value>& args) {
2531 Http2Session* session;
2532 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2533 Debug(session, "destroying session");
2534 Environment* env = Environment::GetCurrent(args);
2535 Local<Context> context = env->context();
2536
2537 uint32_t code = args[0]->Uint32Value(context).ToChecked();
2538 session->Close(code, args[1]->IsTrue());
2539 }
2540
2541 // Submits a new request on the Http2Session and returns either an error code
2542 // or the Http2Stream object.
Request(const FunctionCallbackInfo<Value> & args)2543 void Http2Session::Request(const FunctionCallbackInfo<Value>& args) {
2544 Http2Session* session;
2545 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2546 Environment* env = session->env();
2547
2548 Local<Array> headers = args[0].As<Array>();
2549 int32_t options = args[1]->Int32Value(env->context()).ToChecked();
2550
2551 Debug(session, "request submitted");
2552
2553 int32_t ret = 0;
2554 Http2Stream* stream =
2555 session->Http2Session::SubmitRequest(
2556 Http2Priority(env, args[2], args[3], args[4]),
2557 Http2Headers(env, headers),
2558 &ret,
2559 static_cast<int>(options));
2560
2561 if (ret <= 0 || stream == nullptr) {
2562 Debug(session, "could not submit request: %s", nghttp2_strerror(ret));
2563 return args.GetReturnValue().Set(ret);
2564 }
2565
2566 Debug(session, "request submitted, new stream id %d", stream->id());
2567 args.GetReturnValue().Set(stream->object());
2568 }
2569
2570 // Submits a GOAWAY frame to signal that the Http2Session is in the process
2571 // of shutting down. Note that this function does not actually alter the
2572 // state of the Http2Session, it's simply a notification.
Goaway(uint32_t code,int32_t lastStreamID,const uint8_t * data,size_t len)2573 void Http2Session::Goaway(uint32_t code,
2574 int32_t lastStreamID,
2575 const uint8_t* data,
2576 size_t len) {
2577 if (is_destroyed())
2578 return;
2579
2580 Http2Scope h2scope(this);
2581 // the last proc stream id is the most recently created Http2Stream.
2582 if (lastStreamID <= 0)
2583 lastStreamID = nghttp2_session_get_last_proc_stream_id(session_.get());
2584 Debug(this, "submitting goaway");
2585 nghttp2_submit_goaway(session_.get(), NGHTTP2_FLAG_NONE,
2586 lastStreamID, code, data, len);
2587 }
2588
2589 // Submits a GOAWAY frame to signal that the Http2Session is in the process
2590 // of shutting down. The opaque data argument is an optional TypedArray that
2591 // can be used to send debugging data to the connected peer.
Goaway(const FunctionCallbackInfo<Value> & args)2592 void Http2Session::Goaway(const FunctionCallbackInfo<Value>& args) {
2593 Environment* env = Environment::GetCurrent(args);
2594 Local<Context> context = env->context();
2595 Http2Session* session;
2596 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2597
2598 uint32_t code = args[0]->Uint32Value(context).ToChecked();
2599 int32_t lastStreamID = args[1]->Int32Value(context).ToChecked();
2600 ArrayBufferViewContents<uint8_t> opaque_data;
2601
2602 if (args[2]->IsArrayBufferView()) {
2603 opaque_data.Read(args[2].As<ArrayBufferView>());
2604 }
2605
2606 session->Goaway(code, lastStreamID, opaque_data.data(), opaque_data.length());
2607 }
2608
2609 // Update accounting of data chunks. This is used primarily to manage timeout
2610 // logic when using the FD Provider.
UpdateChunksSent(const FunctionCallbackInfo<Value> & args)2611 void Http2Session::UpdateChunksSent(const FunctionCallbackInfo<Value>& args) {
2612 Environment* env = Environment::GetCurrent(args);
2613 Isolate* isolate = env->isolate();
2614 HandleScope scope(isolate);
2615 Http2Session* session;
2616 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2617
2618 uint32_t length = session->chunks_sent_since_last_write_;
2619
2620 session->object()->Set(env->context(),
2621 env->chunks_sent_since_last_write_string(),
2622 Integer::NewFromUnsigned(isolate, length)).Check();
2623
2624 args.GetReturnValue().Set(length);
2625 }
2626
2627 // Submits an RST_STREAM frame effectively closing the Http2Stream. Note that
2628 // this *WILL* alter the state of the stream, causing the OnStreamClose
2629 // callback to the triggered.
RstStream(const FunctionCallbackInfo<Value> & args)2630 void Http2Stream::RstStream(const FunctionCallbackInfo<Value>& args) {
2631 Environment* env = Environment::GetCurrent(args);
2632 Local<Context> context = env->context();
2633 Http2Stream* stream;
2634 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2635 uint32_t code = args[0]->Uint32Value(context).ToChecked();
2636 Debug(stream, "sending rst_stream with code %d", code);
2637 stream->SubmitRstStream(code);
2638 }
2639
2640 // Initiates a response on the Http2Stream using the StreamBase API to provide
2641 // outbound DATA frames.
Respond(const FunctionCallbackInfo<Value> & args)2642 void Http2Stream::Respond(const FunctionCallbackInfo<Value>& args) {
2643 Environment* env = Environment::GetCurrent(args);
2644 Http2Stream* stream;
2645 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2646
2647 Local<Array> headers = args[0].As<Array>();
2648 int32_t options = args[1]->Int32Value(env->context()).ToChecked();
2649
2650 args.GetReturnValue().Set(
2651 stream->SubmitResponse(
2652 Http2Headers(env, headers),
2653 static_cast<int>(options)));
2654 Debug(stream, "response submitted");
2655 }
2656
2657
2658 // Submits informational headers on the Http2Stream
Info(const FunctionCallbackInfo<Value> & args)2659 void Http2Stream::Info(const FunctionCallbackInfo<Value>& args) {
2660 Environment* env = Environment::GetCurrent(args);
2661 Http2Stream* stream;
2662 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2663
2664 Local<Array> headers = args[0].As<Array>();
2665
2666 args.GetReturnValue().Set(stream->SubmitInfo(Http2Headers(env, headers)));
2667 }
2668
2669 // Submits trailing headers on the Http2Stream
Trailers(const FunctionCallbackInfo<Value> & args)2670 void Http2Stream::Trailers(const FunctionCallbackInfo<Value>& args) {
2671 Environment* env = Environment::GetCurrent(args);
2672 Http2Stream* stream;
2673 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2674
2675 Local<Array> headers = args[0].As<Array>();
2676
2677 args.GetReturnValue().Set(
2678 stream->SubmitTrailers(Http2Headers(env, headers)));
2679 }
2680
2681 // Grab the numeric id of the Http2Stream
GetID(const FunctionCallbackInfo<Value> & args)2682 void Http2Stream::GetID(const FunctionCallbackInfo<Value>& args) {
2683 Http2Stream* stream;
2684 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2685 args.GetReturnValue().Set(stream->id());
2686 }
2687
2688 // Destroy the Http2Stream, rendering it no longer usable
Destroy(const FunctionCallbackInfo<Value> & args)2689 void Http2Stream::Destroy(const FunctionCallbackInfo<Value>& args) {
2690 Http2Stream* stream;
2691 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2692 Debug(stream, "destroying stream");
2693 stream->Destroy();
2694 }
2695
2696 // Initiate a Push Promise and create the associated Http2Stream
PushPromise(const FunctionCallbackInfo<Value> & args)2697 void Http2Stream::PushPromise(const FunctionCallbackInfo<Value>& args) {
2698 Environment* env = Environment::GetCurrent(args);
2699 Http2Stream* parent;
2700 ASSIGN_OR_RETURN_UNWRAP(&parent, args.Holder());
2701
2702 Local<Array> headers = args[0].As<Array>();
2703 int32_t options = args[1]->Int32Value(env->context()).ToChecked();
2704
2705 Debug(parent, "creating push promise");
2706
2707 int32_t ret = 0;
2708 Http2Stream* stream =
2709 parent->SubmitPushPromise(
2710 Http2Headers(env, headers),
2711 &ret,
2712 static_cast<int>(options));
2713
2714 if (ret <= 0 || stream == nullptr) {
2715 Debug(parent, "failed to create push stream: %d", ret);
2716 return args.GetReturnValue().Set(ret);
2717 }
2718 Debug(parent, "push stream %d created", stream->id());
2719 args.GetReturnValue().Set(stream->object());
2720 }
2721
2722 // Send a PRIORITY frame
Priority(const FunctionCallbackInfo<Value> & args)2723 void Http2Stream::Priority(const FunctionCallbackInfo<Value>& args) {
2724 Environment* env = Environment::GetCurrent(args);
2725 Http2Stream* stream;
2726 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2727
2728 CHECK_EQ(stream->SubmitPriority(
2729 Http2Priority(env, args[0], args[1], args[2]),
2730 args[3]->IsTrue()), 0);
2731 Debug(stream, "priority submitted");
2732 }
2733
2734 // A TypedArray shared by C++ and JS land is used to communicate state
2735 // information about the Http2Stream. This updates the values in that
2736 // TypedArray so that the state can be read by JS.
RefreshState(const FunctionCallbackInfo<Value> & args)2737 void Http2Stream::RefreshState(const FunctionCallbackInfo<Value>& args) {
2738 Http2Stream* stream;
2739 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2740
2741 Debug(stream, "refreshing state");
2742
2743 CHECK_NOT_NULL(stream->session());
2744 AliasedFloat64Array& buffer =
2745 stream->session()->http2_state()->stream_state_buffer;
2746
2747 nghttp2_stream* str = stream->stream();
2748 nghttp2_session* s = stream->session()->session();
2749
2750 if (str == nullptr) {
2751 buffer[IDX_STREAM_STATE] = NGHTTP2_STREAM_STATE_IDLE;
2752 buffer[IDX_STREAM_STATE_WEIGHT] =
2753 buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2754 buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2755 buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2756 buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] = 0;
2757 } else {
2758 buffer[IDX_STREAM_STATE] =
2759 nghttp2_stream_get_state(str);
2760 buffer[IDX_STREAM_STATE_WEIGHT] =
2761 nghttp2_stream_get_weight(str);
2762 buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2763 nghttp2_stream_get_sum_dependency_weight(str);
2764 buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2765 nghttp2_session_get_stream_local_close(s, stream->id());
2766 buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2767 nghttp2_session_get_stream_remote_close(s, stream->id());
2768 buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] =
2769 nghttp2_session_get_stream_local_window_size(s, stream->id());
2770 }
2771 }
2772
AltSvc(int32_t id,uint8_t * origin,size_t origin_len,uint8_t * value,size_t value_len)2773 void Http2Session::AltSvc(int32_t id,
2774 uint8_t* origin,
2775 size_t origin_len,
2776 uint8_t* value,
2777 size_t value_len) {
2778 Http2Scope h2scope(this);
2779 CHECK_EQ(nghttp2_submit_altsvc(session_.get(), NGHTTP2_FLAG_NONE, id,
2780 origin, origin_len, value, value_len), 0);
2781 }
2782
Origin(const Origins & origins)2783 void Http2Session::Origin(const Origins& origins) {
2784 Http2Scope h2scope(this);
2785 CHECK_EQ(nghttp2_submit_origin(
2786 session_.get(),
2787 NGHTTP2_FLAG_NONE,
2788 *origins,
2789 origins.length()), 0);
2790 }
2791
2792 // Submits an AltSvc frame to be sent to the connected peer.
AltSvc(const FunctionCallbackInfo<Value> & args)2793 void Http2Session::AltSvc(const FunctionCallbackInfo<Value>& args) {
2794 Environment* env = Environment::GetCurrent(args);
2795 Http2Session* session;
2796 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2797
2798 int32_t id = args[0]->Int32Value(env->context()).ToChecked();
2799
2800 // origin and value are both required to be ASCII, handle them as such.
2801 Local<String> origin_str = args[1]->ToString(env->context()).ToLocalChecked();
2802 Local<String> value_str = args[2]->ToString(env->context()).ToLocalChecked();
2803
2804 if (origin_str.IsEmpty() || value_str.IsEmpty())
2805 return;
2806
2807 size_t origin_len = origin_str->Length();
2808 size_t value_len = value_str->Length();
2809
2810 CHECK_LE(origin_len + value_len, 16382); // Max permitted for ALTSVC
2811 // Verify that origin len != 0 if stream id == 0, or
2812 // that origin len == 0 if stream id != 0
2813 CHECK((origin_len != 0 && id == 0) || (origin_len == 0 && id != 0));
2814
2815 MaybeStackBuffer<uint8_t> origin(origin_len);
2816 MaybeStackBuffer<uint8_t> value(value_len);
2817 origin_str->WriteOneByte(env->isolate(), *origin);
2818 value_str->WriteOneByte(env->isolate(), *value);
2819
2820 session->AltSvc(id, *origin, origin_len, *value, value_len);
2821 }
2822
Origin(const FunctionCallbackInfo<Value> & args)2823 void Http2Session::Origin(const FunctionCallbackInfo<Value>& args) {
2824 Environment* env = Environment::GetCurrent(args);
2825 Local<Context> context = env->context();
2826 Http2Session* session;
2827 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2828
2829 Local<String> origin_string = args[0].As<String>();
2830 size_t count = args[1]->Int32Value(context).ToChecked();
2831
2832 session->Origin(Origins(env, origin_string, count));
2833 }
2834
2835 // Submits a PING frame to be sent to the connected peer.
Ping(const FunctionCallbackInfo<Value> & args)2836 void Http2Session::Ping(const FunctionCallbackInfo<Value>& args) {
2837 Http2Session* session;
2838 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2839
2840 // A PING frame may have exactly 8 bytes of payload data. If not provided,
2841 // then the current hrtime will be used as the payload.
2842 ArrayBufferViewContents<uint8_t, 8> payload;
2843 if (args[0]->IsArrayBufferView()) {
2844 payload.Read(args[0].As<ArrayBufferView>());
2845 CHECK_EQ(payload.length(), 8);
2846 }
2847
2848 CHECK(args[1]->IsFunction());
2849 args.GetReturnValue().Set(
2850 session->AddPing(payload.data(), args[1].As<Function>()));
2851 }
2852
2853 // Submits a SETTINGS frame for the Http2Session
Settings(const FunctionCallbackInfo<Value> & args)2854 void Http2Session::Settings(const FunctionCallbackInfo<Value>& args) {
2855 Http2Session* session;
2856 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2857 CHECK(args[0]->IsFunction());
2858 args.GetReturnValue().Set(session->AddSettings(args[0].As<Function>()));
2859 }
2860
PopPing()2861 BaseObjectPtr<Http2Ping> Http2Session::PopPing() {
2862 BaseObjectPtr<Http2Ping> ping;
2863 if (!outstanding_pings_.empty()) {
2864 ping = std::move(outstanding_pings_.front());
2865 outstanding_pings_.pop();
2866 DecrementCurrentSessionMemory(sizeof(*ping));
2867 }
2868 return ping;
2869 }
2870
AddPing(const uint8_t * payload,Local<Function> callback)2871 bool Http2Session::AddPing(const uint8_t* payload, Local<Function> callback) {
2872 Local<Object> obj;
2873 if (!env()->http2ping_constructor_template()
2874 ->NewInstance(env()->context())
2875 .ToLocal(&obj)) {
2876 return false;
2877 }
2878
2879 BaseObjectPtr<Http2Ping> ping =
2880 MakeDetachedBaseObject<Http2Ping>(this, obj, callback);
2881 if (!ping)
2882 return false;
2883
2884 if (outstanding_pings_.size() == max_outstanding_pings_) {
2885 ping->Done(false);
2886 return false;
2887 }
2888
2889 IncrementCurrentSessionMemory(sizeof(*ping));
2890 // The Ping itself is an Async resource. When the acknowledgement is received,
2891 // the callback will be invoked and a notification sent out to JS land. The
2892 // notification will include the duration of the ping, allowing the round
2893 // trip to be measured.
2894 ping->Send(payload);
2895
2896 outstanding_pings_.emplace(std::move(ping));
2897 return true;
2898 }
2899
PopSettings()2900 BaseObjectPtr<Http2Settings> Http2Session::PopSettings() {
2901 BaseObjectPtr<Http2Settings> settings;
2902 if (!outstanding_settings_.empty()) {
2903 settings = std::move(outstanding_settings_.front());
2904 outstanding_settings_.pop();
2905 DecrementCurrentSessionMemory(sizeof(*settings));
2906 }
2907 return settings;
2908 }
2909
AddSettings(Local<Function> callback)2910 bool Http2Session::AddSettings(Local<Function> callback) {
2911 Local<Object> obj;
2912 if (!env()->http2settings_constructor_template()
2913 ->NewInstance(env()->context())
2914 .ToLocal(&obj)) {
2915 return false;
2916 }
2917
2918 BaseObjectPtr<Http2Settings> settings =
2919 MakeDetachedBaseObject<Http2Settings>(this, obj, callback, 0);
2920 if (!settings)
2921 return false;
2922
2923 if (outstanding_settings_.size() == max_outstanding_settings_) {
2924 settings->Done(false);
2925 return false;
2926 }
2927
2928 IncrementCurrentSessionMemory(sizeof(*settings));
2929 settings->Send();
2930 outstanding_settings_.emplace(std::move(settings));
2931 return true;
2932 }
2933
Http2Ping(Http2Session * session,Local<Object> obj,Local<Function> callback)2934 Http2Ping::Http2Ping(
2935 Http2Session* session,
2936 Local<Object> obj,
2937 Local<Function> callback)
2938 : AsyncWrap(session->env(), obj, AsyncWrap::PROVIDER_HTTP2PING),
2939 session_(session),
2940 startTime_(uv_hrtime()) {
2941 callback_.Reset(env()->isolate(), callback);
2942 }
2943
MemoryInfo(MemoryTracker * tracker) const2944 void Http2Ping::MemoryInfo(MemoryTracker* tracker) const {
2945 tracker->TrackField("callback", callback_);
2946 }
2947
callback() const2948 Local<Function> Http2Ping::callback() const {
2949 return callback_.Get(env()->isolate());
2950 }
2951
Send(const uint8_t * payload)2952 void Http2Ping::Send(const uint8_t* payload) {
2953 CHECK(session_);
2954 uint8_t data[8];
2955 if (payload == nullptr) {
2956 memcpy(&data, &startTime_, arraysize(data));
2957 payload = data;
2958 }
2959 Http2Scope h2scope(session_.get());
2960 CHECK_EQ(nghttp2_submit_ping(
2961 session_->session(),
2962 NGHTTP2_FLAG_NONE,
2963 payload), 0);
2964 }
2965
Done(bool ack,const uint8_t * payload)2966 void Http2Ping::Done(bool ack, const uint8_t* payload) {
2967 uint64_t duration_ns = uv_hrtime() - startTime_;
2968 double duration_ms = duration_ns / 1e6;
2969 if (session_) session_->statistics_.ping_rtt = duration_ns;
2970
2971 Isolate* isolate = env()->isolate();
2972 HandleScope handle_scope(isolate);
2973 Context::Scope context_scope(env()->context());
2974
2975 Local<Value> buf = Undefined(isolate);
2976 if (payload != nullptr) {
2977 buf = Buffer::Copy(isolate,
2978 reinterpret_cast<const char*>(payload),
2979 8).ToLocalChecked();
2980 }
2981
2982 Local<Value> argv[] = {
2983 ack ? v8::True(isolate) : v8::False(isolate),
2984 Number::New(isolate, duration_ms),
2985 buf
2986 };
2987 MakeCallback(callback(), arraysize(argv), argv);
2988 }
2989
DetachFromSession()2990 void Http2Ping::DetachFromSession() {
2991 session_.reset();
2992 }
2993
MemoryInfo(MemoryTracker * tracker) const2994 void NgHttp2StreamWrite::MemoryInfo(MemoryTracker* tracker) const {
2995 if (req_wrap)
2996 tracker->TrackField("req_wrap", req_wrap);
2997 tracker->TrackField("buf", buf);
2998 }
2999
SetCallbackFunctions(const FunctionCallbackInfo<Value> & args)3000 void SetCallbackFunctions(const FunctionCallbackInfo<Value>& args) {
3001 Environment* env = Environment::GetCurrent(args);
3002 CHECK_EQ(args.Length(), 11);
3003
3004 #define SET_FUNCTION(arg, name) \
3005 CHECK(args[arg]->IsFunction()); \
3006 env->set_http2session_on_ ## name ## _function(args[arg].As<Function>());
3007
3008 SET_FUNCTION(0, error)
3009 SET_FUNCTION(1, priority)
3010 SET_FUNCTION(2, settings)
3011 SET_FUNCTION(3, ping)
3012 SET_FUNCTION(4, headers)
3013 SET_FUNCTION(5, frame_error)
3014 SET_FUNCTION(6, goaway_data)
3015 SET_FUNCTION(7, altsvc)
3016 SET_FUNCTION(8, origin)
3017 SET_FUNCTION(9, stream_trailers)
3018 SET_FUNCTION(10, stream_close)
3019
3020 #undef SET_FUNCTION
3021 }
3022
MemoryInfo(MemoryTracker * tracker) const3023 void Http2State::MemoryInfo(MemoryTracker* tracker) const {
3024 tracker->TrackField("root_buffer", root_buffer);
3025 }
3026
3027 // TODO(addaleax): Remove once we're on C++17.
3028 constexpr FastStringKey Http2State::type_name;
3029
3030 // Set up the process.binding('http2') binding.
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)3031 void Initialize(Local<Object> target,
3032 Local<Value> unused,
3033 Local<Context> context,
3034 void* priv) {
3035 Environment* env = Environment::GetCurrent(context);
3036 Isolate* isolate = env->isolate();
3037 HandleScope handle_scope(isolate);
3038
3039 Http2State* const state = env->AddBindingData<Http2State>(context, target);
3040 if (state == nullptr) return;
3041
3042 #define SET_STATE_TYPEDARRAY(name, field) \
3043 target->Set(context, \
3044 FIXED_ONE_BYTE_STRING(isolate, (name)), \
3045 (field)).FromJust()
3046
3047 // Initialize the buffer used to store the session state
3048 SET_STATE_TYPEDARRAY(
3049 "sessionState", state->session_state_buffer.GetJSArray());
3050 // Initialize the buffer used to store the stream state
3051 SET_STATE_TYPEDARRAY(
3052 "streamState", state->stream_state_buffer.GetJSArray());
3053 SET_STATE_TYPEDARRAY(
3054 "settingsBuffer", state->settings_buffer.GetJSArray());
3055 SET_STATE_TYPEDARRAY(
3056 "optionsBuffer", state->options_buffer.GetJSArray());
3057 SET_STATE_TYPEDARRAY(
3058 "streamStats", state->stream_stats_buffer.GetJSArray());
3059 SET_STATE_TYPEDARRAY(
3060 "sessionStats", state->session_stats_buffer.GetJSArray());
3061 #undef SET_STATE_TYPEDARRAY
3062
3063 NODE_DEFINE_CONSTANT(target, kBitfield);
3064 NODE_DEFINE_CONSTANT(target, kSessionPriorityListenerCount);
3065 NODE_DEFINE_CONSTANT(target, kSessionFrameErrorListenerCount);
3066 NODE_DEFINE_CONSTANT(target, kSessionMaxInvalidFrames);
3067 NODE_DEFINE_CONSTANT(target, kSessionMaxRejectedStreams);
3068 NODE_DEFINE_CONSTANT(target, kSessionUint8FieldCount);
3069
3070 NODE_DEFINE_CONSTANT(target, kSessionHasRemoteSettingsListeners);
3071 NODE_DEFINE_CONSTANT(target, kSessionRemoteSettingsIsUpToDate);
3072 NODE_DEFINE_CONSTANT(target, kSessionHasPingListeners);
3073 NODE_DEFINE_CONSTANT(target, kSessionHasAltsvcListeners);
3074
3075 // Method to fetch the nghttp2 string description of an nghttp2 error code
3076 env->SetMethod(target, "nghttp2ErrorString", HttpErrorString);
3077 env->SetMethod(target, "refreshDefaultSettings", RefreshDefaultSettings);
3078 env->SetMethod(target, "packSettings", PackSettings);
3079 env->SetMethod(target, "setCallbackFunctions", SetCallbackFunctions);
3080
3081 Local<FunctionTemplate> ping = FunctionTemplate::New(env->isolate());
3082 ping->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Ping"));
3083 ping->Inherit(AsyncWrap::GetConstructorTemplate(env));
3084 Local<ObjectTemplate> pingt = ping->InstanceTemplate();
3085 pingt->SetInternalFieldCount(Http2Ping::kInternalFieldCount);
3086 env->set_http2ping_constructor_template(pingt);
3087
3088 Local<FunctionTemplate> setting = FunctionTemplate::New(env->isolate());
3089 setting->Inherit(AsyncWrap::GetConstructorTemplate(env));
3090 Local<ObjectTemplate> settingt = setting->InstanceTemplate();
3091 settingt->SetInternalFieldCount(AsyncWrap::kInternalFieldCount);
3092 env->set_http2settings_constructor_template(settingt);
3093
3094 Local<FunctionTemplate> stream = FunctionTemplate::New(env->isolate());
3095 env->SetProtoMethod(stream, "id", Http2Stream::GetID);
3096 env->SetProtoMethod(stream, "destroy", Http2Stream::Destroy);
3097 env->SetProtoMethod(stream, "priority", Http2Stream::Priority);
3098 env->SetProtoMethod(stream, "pushPromise", Http2Stream::PushPromise);
3099 env->SetProtoMethod(stream, "info", Http2Stream::Info);
3100 env->SetProtoMethod(stream, "trailers", Http2Stream::Trailers);
3101 env->SetProtoMethod(stream, "respond", Http2Stream::Respond);
3102 env->SetProtoMethod(stream, "rstStream", Http2Stream::RstStream);
3103 env->SetProtoMethod(stream, "refreshState", Http2Stream::RefreshState);
3104 stream->Inherit(AsyncWrap::GetConstructorTemplate(env));
3105 StreamBase::AddMethods(env, stream);
3106 Local<ObjectTemplate> streamt = stream->InstanceTemplate();
3107 streamt->SetInternalFieldCount(StreamBase::kInternalFieldCount);
3108 env->set_http2stream_constructor_template(streamt);
3109 env->SetConstructorFunction(target, "Http2Stream", stream);
3110
3111 Local<FunctionTemplate> session =
3112 env->NewFunctionTemplate(Http2Session::New);
3113 session->InstanceTemplate()->SetInternalFieldCount(
3114 Http2Session::kInternalFieldCount);
3115 session->Inherit(AsyncWrap::GetConstructorTemplate(env));
3116 env->SetProtoMethod(session, "origin", Http2Session::Origin);
3117 env->SetProtoMethod(session, "altsvc", Http2Session::AltSvc);
3118 env->SetProtoMethod(session, "ping", Http2Session::Ping);
3119 env->SetProtoMethod(session, "consume", Http2Session::Consume);
3120 env->SetProtoMethod(session, "receive", Http2Session::Receive);
3121 env->SetProtoMethod(session, "destroy", Http2Session::Destroy);
3122 env->SetProtoMethod(session, "goaway", Http2Session::Goaway);
3123 env->SetProtoMethod(session, "settings", Http2Session::Settings);
3124 env->SetProtoMethod(session, "request", Http2Session::Request);
3125 env->SetProtoMethod(session, "setNextStreamID",
3126 Http2Session::SetNextStreamID);
3127 env->SetProtoMethod(session, "setLocalWindowSize",
3128 Http2Session::SetLocalWindowSize);
3129 env->SetProtoMethod(session, "updateChunksSent",
3130 Http2Session::UpdateChunksSent);
3131 env->SetProtoMethod(session, "refreshState", Http2Session::RefreshState);
3132 env->SetProtoMethod(
3133 session, "localSettings",
3134 Http2Session::RefreshSettings<nghttp2_session_get_local_settings>);
3135 env->SetProtoMethod(
3136 session, "remoteSettings",
3137 Http2Session::RefreshSettings<nghttp2_session_get_remote_settings>);
3138 env->SetConstructorFunction(target, "Http2Session", session);
3139
3140 Local<Object> constants = Object::New(isolate);
3141
3142 // This does allocate one more slot than needed but it's not used.
3143 #define V(name) FIXED_ONE_BYTE_STRING(isolate, #name),
3144 Local<Value> error_code_names[] = {
3145 HTTP2_ERROR_CODES(V)
3146 };
3147 #undef V
3148
3149 Local<Array> name_for_error_code =
3150 Array::New(
3151 isolate,
3152 error_code_names,
3153 arraysize(error_code_names));
3154
3155 target->Set(context,
3156 FIXED_ONE_BYTE_STRING(isolate, "nameForErrorCode"),
3157 name_for_error_code).Check();
3158
3159 #define V(constant) NODE_DEFINE_HIDDEN_CONSTANT(constants, constant);
3160 HTTP2_HIDDEN_CONSTANTS(V)
3161 #undef V
3162
3163 #define V(constant) NODE_DEFINE_CONSTANT(constants, constant);
3164 HTTP2_CONSTANTS(V)
3165 #undef V
3166
3167 // NGHTTP2_DEFAULT_WEIGHT is a macro and not a regular define
3168 // it won't be set properly on the constants object if included
3169 // in the HTTP2_CONSTANTS macro.
3170 NODE_DEFINE_CONSTANT(constants, NGHTTP2_DEFAULT_WEIGHT);
3171
3172 #define V(NAME, VALUE) \
3173 NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_HEADER_" # NAME, VALUE);
3174 HTTP_KNOWN_HEADERS(V)
3175 #undef V
3176
3177 #define V(NAME, VALUE) \
3178 NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_METHOD_" # NAME, VALUE);
3179 HTTP_KNOWN_METHODS(V)
3180 #undef V
3181
3182 #define V(name, _) NODE_DEFINE_CONSTANT(constants, HTTP_STATUS_##name);
3183 HTTP_STATUS_CODES(V)
3184 #undef V
3185
3186 target->Set(context, env->constants_string(), constants).Check();
3187 }
3188 } // namespace http2
3189 } // namespace node
3190
3191 NODE_MODULE_CONTEXT_AWARE_INTERNAL(http2, node::http2::Initialize)
3192