Convey more information in Http2VisitorInterface::OnConnectionError().
This CL introduces a ConnectionError enum class to Http2VisitorInterface and
adds a ConnectionError argument to Http2VisitorInterface::OnConnectionError().
The visitor then has more detail about the scenario that triggers the
connection error. For now, values include sending and parsing errors, an
invalid connection preface, and a header error.
PiperOrigin-RevId: 402636055
diff --git a/http2/adapter/callback_visitor.cc b/http2/adapter/callback_visitor.cc
index becf5bf..99c1223 100644
--- a/http2/adapter/callback_visitor.cc
+++ b/http2/adapter/callback_visitor.cc
@@ -68,7 +68,7 @@
}
}
-void CallbackVisitor::OnConnectionError() {
+void CallbackVisitor::OnConnectionError(ConnectionError /*error*/) {
QUICHE_LOG(ERROR) << "OnConnectionError not implemented";
}
diff --git a/http2/adapter/callback_visitor.h b/http2/adapter/callback_visitor.h
index f252989..4abfe94 100644
--- a/http2/adapter/callback_visitor.h
+++ b/http2/adapter/callback_visitor.h
@@ -23,7 +23,7 @@
void* user_data);
int64_t OnReadyToSend(absl::string_view serialized) override;
- void OnConnectionError() override;
+ void OnConnectionError(ConnectionError error) override;
bool OnFrameHeader(Http2StreamId stream_id, size_t length, uint8_t type,
uint8_t flags) override;
void OnSettingsStart() override;
diff --git a/http2/adapter/http2_util.cc b/http2/adapter/http2_util.cc
index e054a82..17c3ce8 100644
--- a/http2/adapter/http2_util.cc
+++ b/http2/adapter/http2_util.cc
@@ -2,6 +2,11 @@
namespace http2 {
namespace adapter {
+namespace {
+
+using ConnectionError = Http2VisitorInterface::ConnectionError;
+
+} // anonymous namespace
spdy::SpdyErrorCode TranslateErrorCode(Http2ErrorCode code) {
switch (code) {
@@ -69,5 +74,18 @@
}
}
+absl::string_view ConnectionErrorToString(ConnectionError error) {
+ switch (error) {
+ case ConnectionError::kInvalidConnectionPreface:
+ return "InvalidConnectionPreface";
+ case ConnectionError::kSendError:
+ return "SendError";
+ case ConnectionError::kParseError:
+ return "ParseError";
+ case ConnectionError::kHeaderError:
+ return "HeaderError";
+ }
+}
+
} // namespace adapter
} // namespace http2
diff --git a/http2/adapter/http2_util.h b/http2/adapter/http2_util.h
index 88e9a49..be6b877 100644
--- a/http2/adapter/http2_util.h
+++ b/http2/adapter/http2_util.h
@@ -2,6 +2,7 @@
#define QUICHE_HTTP2_ADAPTER_HTTP2_UTIL_H_
#include "http2/adapter/http2_protocol.h"
+#include "http2/adapter/http2_visitor_interface.h"
#include "common/platform/api/quiche_export.h"
#include "spdy/core/spdy_protocol.h"
@@ -13,6 +14,9 @@
QUICHE_EXPORT_PRIVATE Http2ErrorCode
TranslateErrorCode(spdy::SpdyErrorCode code);
+QUICHE_EXPORT_PRIVATE absl::string_view ConnectionErrorToString(
+ Http2VisitorInterface::ConnectionError error);
+
} // namespace adapter
} // namespace http2
diff --git a/http2/adapter/http2_visitor_interface.h b/http2/adapter/http2_visitor_interface.h
index 4d12e42..f955bfb 100644
--- a/http2/adapter/http2_visitor_interface.h
+++ b/http2/adapter/http2_visitor_interface.h
@@ -60,8 +60,18 @@
// bytes were actually sent. May return kSendBlocked or kSendError.
virtual int64_t OnReadyToSend(absl::string_view serialized) = 0;
- // Called when a connection-level processing error has been encountered.
- virtual void OnConnectionError() = 0;
+ // Called when a connection-level error has occurred.
+ enum class ConnectionError {
+ // The peer sent an invalid connection preface.
+ kInvalidConnectionPreface,
+ // The visitor encountered an error sending bytes to the peer.
+ kSendError,
+ // There was an error reading and framing bytes from the peer.
+ kParseError,
+ // The visitor considered a received header to be a connection error.
+ kHeaderError,
+ };
+ virtual void OnConnectionError(ConnectionError error) = 0;
// Called when the header for a frame is received.
virtual bool OnFrameHeader(Http2StreamId /*stream_id*/, size_t /*length*/,
diff --git a/http2/adapter/mock_http2_visitor.h b/http2/adapter/mock_http2_visitor.h
index 1faf443..a66fe79 100644
--- a/http2/adapter/mock_http2_visitor.h
+++ b/http2/adapter/mock_http2_visitor.h
@@ -30,7 +30,7 @@
MOCK_METHOD(int64_t, OnReadyToSend, (absl::string_view serialized),
(override));
- MOCK_METHOD(void, OnConnectionError, (), (override));
+ MOCK_METHOD(void, OnConnectionError, (ConnectionError error), (override));
MOCK_METHOD(bool, OnFrameHeader,
(Http2StreamId stream_id, size_t length, uint8_t type,
uint8_t flags),
diff --git a/http2/adapter/nghttp2_adapter.cc b/http2/adapter/nghttp2_adapter.cc
index 5da518f..e780562 100644
--- a/http2/adapter/nghttp2_adapter.cc
+++ b/http2/adapter/nghttp2_adapter.cc
@@ -3,6 +3,7 @@
#include "absl/algorithm/container.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
+#include "http2/adapter/http2_visitor_interface.h"
#include "http2/adapter/nghttp2_callbacks.h"
#include "http2/adapter/nghttp2_data_provider.h"
#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
@@ -14,6 +15,8 @@
namespace {
+using ConnectionError = Http2VisitorInterface::ConnectionError;
+
// A metadata source that deletes itself upon completion.
class SelfDeletingMetadataSource : public MetadataSource {
public:
@@ -63,7 +66,7 @@
int64_t NgHttp2Adapter::ProcessBytes(absl::string_view bytes) {
const int64_t processed_bytes = session_->ProcessBytes(bytes);
if (processed_bytes < 0) {
- visitor_.OnConnectionError();
+ visitor_.OnConnectionError(ConnectionError::kParseError);
}
return processed_bytes;
}
@@ -142,7 +145,7 @@
const int result = nghttp2_session_send(session_->raw_ptr());
if (result != 0) {
QUICHE_VLOG(1) << "nghttp2_session_send returned " << result;
- visitor_.OnConnectionError();
+ visitor_.OnConnectionError(ConnectionError::kSendError);
}
return result;
}
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc
index b78827a..8fc772d 100644
--- a/http2/adapter/nghttp2_adapter_test.cc
+++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -13,6 +13,8 @@
namespace test {
namespace {
+using ConnectionError = Http2VisitorInterface::ConnectionError;
+
using testing::_;
enum FrameType {
@@ -474,7 +476,7 @@
EXPECT_CALL(visitor, OnMetadataForStream(1, _))
.WillOnce(testing::Return(false));
// Remaining frames are not processed due to the error.
- EXPECT_CALL(visitor, OnConnectionError());
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
const int64_t stream_result = adapter->ProcessBytes(stream_frames);
// The false return from OnMetadataForStream() results in a connection error.
@@ -702,7 +704,8 @@
OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT"))
.WillOnce(
testing::Return(Http2VisitorInterface::HEADER_CONNECTION_ERROR));
- EXPECT_CALL(visitor, OnConnectionError());
+ // Translation to nghttp2 treats this error as a general parsing error.
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
const int64_t stream_result = adapter->ProcessBytes(stream_frames);
EXPECT_EQ(-902 /* NGHTTP2_ERR_CALLBACK_FAILURE */, stream_result);
@@ -765,7 +768,7 @@
EXPECT_CALL(visitor, OnBeginHeadersForStream(1))
.WillOnce(testing::Return(false));
// Rejecting headers leads to a connection error.
- EXPECT_CALL(visitor, OnConnectionError());
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
const int64_t stream_result = adapter->ProcessBytes(stream_frames);
EXPECT_EQ(NGHTTP2_ERR_CALLBACK_FAILURE, stream_result);
@@ -836,7 +839,7 @@
EXPECT_CALL(visitor,
OnGoAway(1, Http2ErrorCode::INTERNAL_ERROR, "indigestion"))
.WillOnce(testing::Return(false));
- EXPECT_CALL(visitor, OnConnectionError());
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
const int64_t stream_result = adapter->ProcessBytes(stream_frames);
EXPECT_EQ(NGHTTP2_ERR_CALLBACK_FAILURE, stream_result);
@@ -1391,7 +1394,7 @@
auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
visitor.set_has_write_error();
- EXPECT_CALL(visitor, OnConnectionError);
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kSendError));
int result = adapter->Send();
EXPECT_EQ(result, NGHTTP2_ERR_CALLBACK_FAILURE);
@@ -1721,7 +1724,7 @@
EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one"));
EXPECT_CALL(visitor, OnEndHeadersForStream(1))
.WillOnce(testing::Return(false));
- EXPECT_CALL(visitor, OnConnectionError());
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
const int64_t result = adapter->ProcessBytes(frames);
EXPECT_EQ(-902, result);
@@ -1766,7 +1769,7 @@
EXPECT_CALL(visitor, OnFrameHeader(0, 8, PING, 0))
.WillOnce(testing::Return(false));
- EXPECT_CALL(visitor, OnConnectionError());
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
const int64_t result = adapter->ProcessBytes(frames);
EXPECT_EQ(-902, result);
@@ -1822,7 +1825,7 @@
EXPECT_CALL(visitor, OnFrameHeader(1, 25, DATA, 0));
EXPECT_CALL(visitor, OnBeginDataForStream(1, 25))
.WillOnce(testing::Return(false));
- EXPECT_CALL(visitor, OnConnectionError());
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
const int64_t result = adapter->ProcessBytes(frames);
EXPECT_EQ(NGHTTP2_ERR_CALLBACK_FAILURE, result);
@@ -1878,7 +1881,7 @@
EXPECT_CALL(visitor, OnFrameHeader(1, 25, DATA, 0));
EXPECT_CALL(visitor, OnBeginDataForStream(1, 25));
EXPECT_CALL(visitor, OnDataForStream(1, _)).WillOnce(testing::Return(false));
- EXPECT_CALL(visitor, OnConnectionError());
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
const int64_t result = adapter->ProcessBytes(frames);
EXPECT_EQ(NGHTTP2_ERR_CALLBACK_FAILURE, result);
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index 38ab9fb..544ae23 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -17,6 +17,8 @@
namespace test {
namespace {
+using ConnectionError = Http2VisitorInterface::ConnectionError;
+
using testing::_;
enum FrameType {
@@ -286,7 +288,7 @@
EXPECT_CALL(visitor, OnMetadataForStream(1, _))
.WillOnce(testing::Return(false));
// Remaining frames are not processed due to the error.
- EXPECT_CALL(visitor, OnConnectionError());
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
const int64_t stream_result = adapter->ProcessBytes(stream_frames);
// Negative integer returned to indicate an error.
@@ -441,7 +443,7 @@
OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT"))
.WillOnce(
testing::Return(Http2VisitorInterface::HEADER_CONNECTION_ERROR));
- EXPECT_CALL(visitor, OnConnectionError());
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kHeaderError));
// Note: OgHttp2Adapter continues processing bytes until the input is
// complete.
EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0));
@@ -513,7 +515,7 @@
EXPECT_CALL(visitor, OnBeginHeadersForStream(1))
.WillOnce(testing::Return(false));
// Rejecting headers leads to a connection error.
- EXPECT_CALL(visitor, OnConnectionError());
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kHeaderError));
// Note: OgHttp2Adapter continues processing bytes until the input is
// complete.
EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0));
@@ -677,7 +679,7 @@
// TODO(birenroy): Pass the GOAWAY opaque data through the oghttp2 stack.
EXPECT_CALL(visitor, OnGoAway(1, Http2ErrorCode::INTERNAL_ERROR, ""))
.WillOnce(testing::Return(false));
- EXPECT_CALL(visitor, OnConnectionError());
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
const int64_t stream_result = adapter->ProcessBytes(stream_frames);
EXPECT_LT(stream_result, 0);
@@ -827,7 +829,7 @@
auto adapter = OgHttp2Adapter::Create(visitor, options);
visitor.set_has_write_error();
- EXPECT_CALL(visitor, OnConnectionError);
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kSendError));
int result = adapter->Send();
EXPECT_EQ(result, Http2VisitorInterface::kSendError);
@@ -1435,7 +1437,7 @@
EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one"));
EXPECT_CALL(visitor, OnEndHeadersForStream(1))
.WillOnce(testing::Return(false));
- EXPECT_CALL(visitor, OnConnectionError());
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
const int64_t result = adapter->ProcessBytes(frames);
EXPECT_LT(result, 0);
@@ -1484,7 +1486,7 @@
EXPECT_CALL(visitor, OnFrameHeader(0, 8, PING, 0))
.WillOnce(testing::Return(false));
- EXPECT_CALL(visitor, OnConnectionError());
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
const int64_t result = adapter->ProcessBytes(frames);
EXPECT_LT(result, 0);
@@ -1544,7 +1546,7 @@
EXPECT_CALL(visitor, OnFrameHeader(1, 25, DATA, 0));
EXPECT_CALL(visitor, OnBeginDataForStream(1, 25))
.WillOnce(testing::Return(false));
- EXPECT_CALL(visitor, OnConnectionError());
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
const int64_t result = adapter->ProcessBytes(frames);
EXPECT_LT(result, 0);
@@ -1604,7 +1606,7 @@
EXPECT_CALL(visitor, OnFrameHeader(1, 25, DATA, 0));
EXPECT_CALL(visitor, OnBeginDataForStream(1, 25));
EXPECT_CALL(visitor, OnDataForStream(1, _)).WillOnce(testing::Return(false));
- EXPECT_CALL(visitor, OnConnectionError());
+ EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError));
const int64_t result = adapter->ProcessBytes(frames);
EXPECT_LT(result, 0);
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index 09a3ce0..adcc7b5 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -6,6 +6,7 @@
#include "absl/memory/memory.h"
#include "absl/strings/escaping.h"
#include "http2/adapter/http2_protocol.h"
+#include "http2/adapter/http2_visitor_interface.h"
#include "http2/adapter/oghttp2_util.h"
#include "spdy/core/spdy_protocol.h"
@@ -14,6 +15,8 @@
namespace {
+using ConnectionError = Http2VisitorInterface::ConnectionError;
+
// #define OGHTTP2_DEBUG_TRACE 1
#ifdef OGHTTP2_DEBUG_TRACE
@@ -274,7 +277,7 @@
QUICHE_DLOG(INFO) << "Preface doesn't match! Expected: ["
<< absl::CEscape(remaining_preface_) << "], actual: ["
<< absl::CEscape(bytes) << "]";
- LatchErrorAndNotify();
+ LatchErrorAndNotify(ConnectionError::kInvalidConnectionPreface);
return -1;
}
remaining_preface_.remove_prefix(min_size);
@@ -345,7 +348,7 @@
}
}
if (result < 0) {
- LatchErrorAndNotify();
+ LatchErrorAndNotify(ConnectionError::kSendError);
return result;
} else if (!serialized_prefix_.empty()) {
return 0;
@@ -381,7 +384,7 @@
spdy::SpdySerializedFrame frame = framer_.SerializeFrame(*frame_ptr);
const int64_t result = visitor_.OnReadyToSend(absl::string_view(frame));
if (result < 0) {
- LatchErrorAndNotify();
+ LatchErrorAndNotify(ConnectionError::kSendError);
return false;
} else if (result == 0) {
// Write blocked.
@@ -623,7 +626,7 @@
QUICHE_VLOG(1) << "Error: "
<< http2::Http2DecoderAdapter::SpdyFramerErrorToString(error)
<< " details: " << detailed_error;
- LatchErrorAndNotify();
+ LatchErrorAndNotify(ConnectionError::kParseError);
}
void OgHttp2Session::OnCommonHeader(spdy::SpdyStreamId stream_id,
@@ -832,7 +835,7 @@
stream_id, spdy::ERROR_CODE_INTERNAL_ERROR));
}
} else if (result == Http2VisitorInterface::HEADER_CONNECTION_ERROR) {
- LatchErrorAndNotify();
+ LatchErrorAndNotify(ConnectionError::kHeaderError);
}
}
@@ -1004,9 +1007,9 @@
}
}
-void OgHttp2Session::LatchErrorAndNotify() {
+void OgHttp2Session::LatchErrorAndNotify(ConnectionError error) {
latched_error_ = true;
- visitor_.OnConnectionError();
+ visitor_.OnConnectionError(error);
}
} // namespace adapter
diff --git a/http2/adapter/oghttp2_session.h b/http2/adapter/oghttp2_session.h
index 814b843..7f60302 100644
--- a/http2/adapter/oghttp2_session.h
+++ b/http2/adapter/oghttp2_session.h
@@ -279,7 +279,7 @@
// Returns true if the session can create a new stream.
bool CanCreateStream() const;
- void LatchErrorAndNotify();
+ void LatchErrorAndNotify(Http2VisitorInterface::ConnectionError error);
// Receives events when inbound frames are parsed.
Http2VisitorInterface& visitor_;
diff --git a/http2/adapter/recording_http2_visitor.cc b/http2/adapter/recording_http2_visitor.cc
index 4dbe4e2..7e12e3d 100644
--- a/http2/adapter/recording_http2_visitor.cc
+++ b/http2/adapter/recording_http2_visitor.cc
@@ -2,6 +2,7 @@
#include "absl/strings/str_format.h"
#include "http2/adapter/http2_protocol.h"
+#include "http2/adapter/http2_util.h"
namespace http2 {
namespace adapter {
@@ -12,8 +13,9 @@
return serialized.size();
}
-void RecordingHttp2Visitor::OnConnectionError() {
- events_.push_back("OnConnectionError");
+void RecordingHttp2Visitor::OnConnectionError(ConnectionError error) {
+ events_.push_back(
+ absl::StrFormat("OnConnectionError %s", ConnectionErrorToString(error)));
}
bool RecordingHttp2Visitor::OnFrameHeader(Http2StreamId stream_id,
diff --git a/http2/adapter/recording_http2_visitor.h b/http2/adapter/recording_http2_visitor.h
index 8946ad3..4cbc15b 100644
--- a/http2/adapter/recording_http2_visitor.h
+++ b/http2/adapter/recording_http2_visitor.h
@@ -21,7 +21,7 @@
// From Http2VisitorInterface
int64_t OnReadyToSend(absl::string_view serialized) override;
- void OnConnectionError() override;
+ void OnConnectionError(ConnectionError error) override;
bool OnFrameHeader(Http2StreamId stream_id, size_t length, uint8_t type,
uint8_t flags) override;
void OnSettingsStart() override;
diff --git a/http2/adapter/recording_http2_visitor_test.cc b/http2/adapter/recording_http2_visitor_test.cc
index 6763244..d8d3ea7 100644
--- a/http2/adapter/recording_http2_visitor_test.cc
+++ b/http2/adapter/recording_http2_visitor_test.cc
@@ -3,6 +3,7 @@
#include <list>
#include "http2/adapter/http2_protocol.h"
+#include "http2/adapter/http2_visitor_interface.h"
#include "http2/test_tools/http2_random.h"
#include "common/platform/api/quiche_test.h"
@@ -64,7 +65,8 @@
std::list<RecordingHttp2Visitor*> visitors = {&chocolate_visitor,
&vanilla_visitor};
for (RecordingHttp2Visitor* visitor : visitors) {
- visitor->OnConnectionError();
+ visitor->OnConnectionError(
+ Http2VisitorInterface::ConnectionError::kSendError);
visitor->OnFrameHeader(stream_id, length, type, flags);
visitor->OnSettingsStart();
visitor->OnSetting(setting);
diff --git a/http2/adapter/test_utils.cc b/http2/adapter/test_utils.cc
index 4223b49..210cb66 100644
--- a/http2/adapter/test_utils.cc
+++ b/http2/adapter/test_utils.cc
@@ -3,6 +3,7 @@
#include <ostream>
#include "absl/strings/str_format.h"
+#include "http2/adapter/http2_visitor_interface.h"
#include "common/quiche_endian.h"
#include "spdy/core/hpack/hpack_encoder.h"
#include "spdy/core/spdy_frame_reader.h"
@@ -10,6 +11,11 @@
namespace http2 {
namespace adapter {
namespace test {
+namespace {
+
+using ConnectionError = Http2VisitorInterface::ConnectionError;
+
+} // anonymous namespace
TestDataFrameSource::TestDataFrameSource(Http2VisitorInterface& visitor,
bool has_fin)
@@ -49,7 +55,7 @@
const int64_t result = visitor_.OnReadyToSend(concatenated);
if (result < 0) {
// Write encountered error.
- visitor_.OnConnectionError();
+ visitor_.OnConnectionError(ConnectionError::kSendError);
current_fragment_ = {};
payload_fragments_.clear();
return false;
@@ -60,7 +66,7 @@
// Probably need to handle this better within this test class.
QUICHE_LOG(DFATAL)
<< "DATA frame not fully flushed. Connection will be corrupt!";
- visitor_.OnConnectionError();
+ visitor_.OnConnectionError(ConnectionError::kSendError);
current_fragment_ = {};
payload_fragments_.clear();
return false;