http2: add specific error code for custom frames · nodejs/node@062541a

@@ -32,6 +32,7 @@ using v8::Integer;

3232

using v8::Isolate;

3333

using v8::Local;

3434

using v8::MaybeLocal;

35+

using v8::NewStringType;

3536

using v8::Number;

3637

using v8::Object;

3738

using v8::ObjectTemplate;

@@ -732,7 +733,7 @@ ssize_t Http2Session::OnMaxFrameSizePadding(size_t frameLen,

732733

// various callback functions. Each of these will typically result in a call

733734

// out to JavaScript so this particular function is rather hot and can be

734735

// quite expensive. This is a potential performance optimization target later.

735-

ssize_t Http2Session::ConsumeHTTP2Data() {

736+

void Http2Session::ConsumeHTTP2Data() {

736737

CHECK_NOT_NULL(stream_buf_.base);

737738

CHECK_LE(stream_buf_offset_, stream_buf_.len);

738739

size_t read_len = stream_buf_.len - stream_buf_offset_;

@@ -742,12 +743,14 @@ ssize_t Http2Session::ConsumeHTTP2Data() {

742743

read_len,

743744

nghttp2_session_want_read(session_.get()));

744745

set_receive_paused(false);

746+

custom_recv_error_code_ = nullptr;

745747

ssize_t ret =

746748

nghttp2_session_mem_recv(session_.get(),

747749

reinterpret_cast<uint8_t*>(stream_buf_.base) +

748750

stream_buf_offset_,

749751

read_len);

750752

CHECK_NE(ret, NGHTTP2_ERR_NOMEM);

753+

CHECK_IMPLIES(custom_recv_error_code_ != nullptr, ret < 0);

751754752755

if (is_receive_paused()) {

753756

CHECK(is_reading_stopped());

@@ -759,7 +762,7 @@ ssize_t Http2Session::ConsumeHTTP2Data() {

759762

// Even if all bytes were received, a paused stream may delay the

760763

// nghttp2_on_frame_recv_callback which may have an END_STREAM flag.

761764

stream_buf_offset_ += ret;

762-

return ret;

765+

goto done;

763766

}

764767765768

// We are done processing the current input chunk.

@@ -769,14 +772,34 @@ ssize_t Http2Session::ConsumeHTTP2Data() {

769772

stream_buf_allocation_.clear();

770773

stream_buf_ = uv_buf_init(nullptr, 0);

771774772-

if (ret < 0)

773-

return ret;

774-775775

// Send any data that was queued up while processing the received data.

776-

if (!is_destroyed()) {

776+

if (ret >= 0 && !is_destroyed()) {

777777

SendPendingData();

778778

}

779-

return ret;

779+780+

done:

781+

if (UNLIKELY(ret < 0)) {

782+

Isolate* isolate = env()->isolate();

783+

Debug(this,

784+

"fatal error receiving data: %d (%s)",

785+

ret,

786+

custom_recv_error_code_ != nullptr ?

787+

custom_recv_error_code_ : "(no custom error code)");

788+

Local<Value> args[] = {

789+

Integer::New(isolate, static_cast<int32_t>(ret)),

790+

Null(isolate)

791+

};

792+

if (custom_recv_error_code_ != nullptr) {

793+

args[1] = String::NewFromUtf8(

794+

isolate,

795+

custom_recv_error_code_,

796+

NewStringType::kInternalized).ToLocalChecked();

797+

}

798+

MakeCallback(

799+

env()->http2session_on_error_function(),

800+

arraysize(args),

801+

args);

802+

}

780803

}

781804782805

@@ -900,14 +923,17 @@ int Http2Session::OnInvalidFrame(nghttp2_session* handle,

900923

int lib_error_code,

901924

void* user_data) {

902925

Http2Session* session = static_cast<Http2Session*>(user_data);

926+

const uint32_t max_invalid_frames = session->js_fields_->max_invalid_frames;

903927904928

Debug(session,

905929

"invalid frame received (%u/%u), code: %d",

906930

session->invalid_frame_count_,

907-

session->js_fields_->max_invalid_frames,

931+

max_invalid_frames,

908932

lib_error_code);

909-

if (session->invalid_frame_count_++ > session->js_fields_->max_invalid_frames)

933+

if (session->invalid_frame_count_++ > max_invalid_frames) {

934+

session->custom_recv_error_code_ = "ERR_HTTP2_TOO_MANY_INVALID_FRAMES";

910935

return 1;

936+

}

911937912938

// If the error is fatal or if error code is ERR_STREAM_CLOSED... emit error

913939

if (nghttp2_is_fatal(lib_error_code) ||

@@ -1286,6 +1312,7 @@ int Http2Session::HandleDataFrame(const nghttp2_frame* frame) {

12861312

stream->EmitRead(UV_EOF);

12871313

} else if (frame->hd.length == 0) {

12881314

if (invalid_frame_count_++ > js_fields_->max_invalid_frames) {

1315+

custom_recv_error_code_ = "ERR_HTTP2_TOO_MANY_INVALID_FRAMES";

12891316

Debug(this, "rejecting empty-frame-without-END_STREAM flood\n");

12901317

// Consider a flood of 0-length frames without END_STREAM an error.

12911318

return 1;

@@ -1470,7 +1497,7 @@ void Http2Session::OnStreamAfterWrite(WriteWrap* w, int status) {

14701497

ConsumeHTTP2Data();

14711498

}

147214991473-

if (!is_write_scheduled()) {

1500+

if (!is_write_scheduled() && !is_destroyed()) {

14741501

// Schedule a new write if nghttp2 wants to send data.

14751502

MaybeScheduleWrite();

14761503

}

@@ -1798,21 +1825,12 @@ void Http2Session::OnStreamRead(ssize_t nread, const uv_buf_t& buf_) {

17981825

// offset of a DATA frame's data into the socket read buffer.

17991826

stream_buf_ = uv_buf_init(buf.data(), static_cast<unsigned int>(nread));

180018271801-

Isolate* isolate = env()->isolate();

1802-18031828

// Store this so we can create an ArrayBuffer for read data from it.

18041829

// DATA frames will be emitted as slices of that ArrayBuffer to avoid having

18051830

// to copy memory.

18061831

stream_buf_allocation_ = std::move(buf);

180718321808-

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-

}

1833+

ConsumeHTTP2Data();

1816183418171835

MaybeStopReading();

18181836

}