http2: add specific error code for custom frames · nodejs/node@062541a
@@ -32,6 +32,7 @@ using v8::Integer;
3232using v8::Isolate;
3333using v8::Local;
3434using v8::MaybeLocal;
35+using v8::NewStringType;
3536using v8::Number;
3637using v8::Object;
3738using 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() {
736737CHECK_NOT_NULL(stream_buf_.base);
737738CHECK_LE(stream_buf_offset_, stream_buf_.len);
738739size_t read_len = stream_buf_.len - stream_buf_offset_;
@@ -742,12 +743,14 @@ ssize_t Http2Session::ConsumeHTTP2Data() {
742743 read_len,
743744nghttp2_session_want_read(session_.get()));
744745set_receive_paused(false);
746+ custom_recv_error_code_ = nullptr;
745747ssize_t ret =
746748nghttp2_session_mem_recv(session_.get(),
747749reinterpret_cast<uint8_t*>(stream_buf_.base) +
748750 stream_buf_offset_,
749751 read_len);
750752CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
753+CHECK_IMPLIES(custom_recv_error_code_ != nullptr, ret < 0);
751754752755if (is_receive_paused()) {
753756CHECK(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()) {
777777SendPendingData();
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,
900923int lib_error_code,
901924void* user_data) {
902925 Http2Session* session = static_cast<Http2Session*>(user_data);
926+const uint32_t max_invalid_frames = session->js_fields_->max_invalid_frames;
903927904928Debug(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";
910935return 1;
936+ }
911937912938// If the error is fatal or if error code is ERR_STREAM_CLOSED... emit error
913939if (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) {
12881314if (invalid_frame_count_++ > js_fields_->max_invalid_frames) {
1315+ custom_recv_error_code_ = "ERR_HTTP2_TOO_MANY_INVALID_FRAMES";
12891316Debug(this, "rejecting empty-frame-without-END_STREAM flood\n");
12901317// Consider a flood of 0-length frames without END_STREAM an error.
12911318return 1;
@@ -1470,7 +1497,7 @@ void Http2Session::OnStreamAfterWrite(WriteWrap* w, int status) {
14701497ConsumeHTTP2Data();
14711498 }
147214991473-if (!is_write_scheduled()) {
1500+if (!is_write_scheduled() && !is_destroyed()) {
14741501// Schedule a new write if nghttp2 wants to send data.
14751502MaybeScheduleWrite();
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();
1816183418171835MaybeStopReading();
18181836}