summaryrefslogtreecommitdiff
path: root/chromium/net/third_party/quiche/src/quiche/http2/adapter/nghttp2_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/net/third_party/quiche/src/quiche/http2/adapter/nghttp2_test.cc')
-rw-r--r--chromium/net/third_party/quiche/src/quiche/http2/adapter/nghttp2_test.cc205
1 files changed, 205 insertions, 0 deletions
diff --git a/chromium/net/third_party/quiche/src/quiche/http2/adapter/nghttp2_test.cc b/chromium/net/third_party/quiche/src/quiche/http2/adapter/nghttp2_test.cc
new file mode 100644
index 00000000000..e4f5db5e84e
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quiche/http2/adapter/nghttp2_test.cc
@@ -0,0 +1,205 @@
+#include "quiche/http2/adapter/nghttp2.h"
+
+#include "absl/strings/str_cat.h"
+#include "quiche/http2/adapter/mock_nghttp2_callbacks.h"
+#include "quiche/http2/adapter/nghttp2_test_utils.h"
+#include "quiche/http2/adapter/nghttp2_util.h"
+#include "quiche/http2/adapter/test_frame_sequence.h"
+#include "quiche/http2/adapter/test_utils.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+namespace {
+
+using testing::_;
+
+enum FrameType {
+ DATA,
+ HEADERS,
+ PRIORITY,
+ RST_STREAM,
+ SETTINGS,
+ PUSH_PROMISE,
+ PING,
+ GOAWAY,
+ WINDOW_UPDATE,
+};
+
+nghttp2_option* GetOptions() {
+ nghttp2_option* options;
+ nghttp2_option_new(&options);
+ // Set some common options for compatibility.
+ nghttp2_option_set_no_closed_streams(options, 1);
+ nghttp2_option_set_no_auto_window_update(options, 1);
+ nghttp2_option_set_max_send_header_block_length(options, 0x2000000);
+ nghttp2_option_set_max_outbound_ack(options, 10000);
+ return options;
+}
+
+class Nghttp2Test : public testing::Test {
+ public:
+ Nghttp2Test() : session_(MakeSessionPtr(nullptr)) {}
+
+ void SetUp() override { InitializeSession(); }
+
+ virtual Perspective GetPerspective() = 0;
+
+ void InitializeSession() {
+ auto nghttp2_callbacks = MockNghttp2Callbacks::GetCallbacks();
+ nghttp2_option* options = GetOptions();
+ nghttp2_session* ptr;
+ if (GetPerspective() == Perspective::kClient) {
+ nghttp2_session_client_new2(&ptr, nghttp2_callbacks.get(),
+ &mock_callbacks_, options);
+ } else {
+ nghttp2_session_server_new2(&ptr, nghttp2_callbacks.get(),
+ &mock_callbacks_, options);
+ }
+ nghttp2_option_del(options);
+
+ // Sets up the Send() callback to append to |serialized_|.
+ EXPECT_CALL(mock_callbacks_, Send(_, _, _))
+ .WillRepeatedly(
+ [this](const uint8_t* data, size_t length, int /*flags*/) {
+ absl::StrAppend(&serialized_, ToStringView(data, length));
+ return length;
+ });
+ // Sets up the SendData() callback to fetch and append data from a
+ // TestDataSource.
+ EXPECT_CALL(mock_callbacks_, SendData(_, _, _, _))
+ .WillRepeatedly([this](nghttp2_frame* /*frame*/, const uint8_t* framehd,
+ size_t length, nghttp2_data_source* source) {
+ QUICHE_LOG(INFO) << "Appending frame header and " << length
+ << " bytes of data";
+ auto* s = static_cast<TestDataSource*>(source->ptr);
+ absl::StrAppend(&serialized_, ToStringView(framehd, 9),
+ s->ReadNext(length));
+ return 0;
+ });
+ session_ = MakeSessionPtr(ptr);
+ }
+
+ testing::StrictMock<MockNghttp2Callbacks> mock_callbacks_;
+ nghttp2_session_unique_ptr session_;
+ std::string serialized_;
+};
+
+class Nghttp2ClientTest : public Nghttp2Test {
+ public:
+ Perspective GetPerspective() override { return Perspective::kClient; }
+};
+
+// Verifies nghttp2 behavior when acting as a client.
+TEST_F(Nghttp2ClientTest, ClientReceivesUnexpectedHeaders) {
+ const std::string initial_frames = TestFrameSequence()
+ .ServerPreface()
+ .Ping(42)
+ .WindowUpdate(0, 1000)
+ .Serialize();
+
+ testing::InSequence seq;
+ EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, SETTINGS, 0)));
+ EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsSettings(testing::IsEmpty())));
+ EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, PING, 0)));
+ EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsPing(42)));
+ EXPECT_CALL(mock_callbacks_,
+ OnBeginFrame(HasFrameHeader(0, WINDOW_UPDATE, 0)));
+ EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsWindowUpdate(1000)));
+
+ ssize_t result = nghttp2_session_mem_recv(
+ session_.get(), ToUint8Ptr(initial_frames.data()), initial_frames.size());
+ ASSERT_EQ(result, initial_frames.size());
+
+ const std::string unexpected_stream_frames =
+ TestFrameSequence()
+ .Headers(1,
+ {{":status", "200"},
+ {"server", "my-fake-server"},
+ {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}},
+ /*fin=*/false)
+ .Data(1, "This is the response body.")
+ .RstStream(3, Http2ErrorCode::INTERNAL_ERROR)
+ .GoAway(5, Http2ErrorCode::ENHANCE_YOUR_CALM, "calm down!!")
+ .Serialize();
+
+ EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(1, HEADERS, _)));
+ EXPECT_CALL(mock_callbacks_, OnInvalidFrameRecv(IsHeaders(1, _, _), _));
+ // No events from the DATA, RST_STREAM or GOAWAY.
+
+ nghttp2_session_mem_recv(session_.get(),
+ ToUint8Ptr(unexpected_stream_frames.data()),
+ unexpected_stream_frames.size());
+}
+
+// Tests the request-sending behavior of nghttp2 when acting as a client.
+TEST_F(Nghttp2ClientTest, ClientSendsRequest) {
+ int result = nghttp2_session_send(session_.get());
+ ASSERT_EQ(result, 0);
+
+ EXPECT_THAT(serialized_, testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized_.clear();
+
+ const std::string initial_frames =
+ TestFrameSequence().ServerPreface().Serialize();
+ testing::InSequence s;
+
+ // Server preface (empty SETTINGS)
+ EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, SETTINGS, 0)));
+ EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsSettings(testing::IsEmpty())));
+
+ ssize_t recv_result = nghttp2_session_mem_recv(
+ session_.get(), ToUint8Ptr(initial_frames.data()), initial_frames.size());
+ EXPECT_EQ(initial_frames.size(), recv_result);
+
+ // Client wants to send a SETTINGS ack.
+ EXPECT_CALL(mock_callbacks_, BeforeFrameSend(IsSettings(testing::IsEmpty())));
+ EXPECT_CALL(mock_callbacks_, OnFrameSend(IsSettings(testing::IsEmpty())));
+ EXPECT_TRUE(nghttp2_session_want_write(session_.get()));
+ result = nghttp2_session_send(session_.get());
+ EXPECT_THAT(serialized_, EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
+ serialized_.clear();
+
+ EXPECT_FALSE(nghttp2_session_want_write(session_.get()));
+
+ // The following sets up the client request.
+ std::vector<std::pair<absl::string_view, absl::string_view>> headers = {
+ {":method", "POST"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}};
+ std::vector<nghttp2_nv> nvs;
+ for (const auto& h : headers) {
+ nvs.push_back({.name = ToUint8Ptr(h.first.data()),
+ .value = ToUint8Ptr(h.second.data()),
+ .namelen = h.first.size(),
+ .valuelen = h.second.size()});
+ }
+ const absl::string_view kBody = "This is an example request body.";
+ TestDataSource source{kBody};
+ nghttp2_data_provider provider = source.MakeDataProvider();
+ // After submitting the request, the client will want to write.
+ int stream_id =
+ nghttp2_submit_request(session_.get(), nullptr /* pri_spec */, nvs.data(),
+ nvs.size(), &provider, nullptr /* stream_data */);
+ EXPECT_GT(stream_id, 0);
+ EXPECT_TRUE(nghttp2_session_want_write(session_.get()));
+
+ // We expect that the client will want to write HEADERS, then DATA.
+ EXPECT_CALL(mock_callbacks_, BeforeFrameSend(IsHeaders(stream_id, _, _)));
+ EXPECT_CALL(mock_callbacks_, OnFrameSend(IsHeaders(stream_id, _, _)));
+ EXPECT_CALL(mock_callbacks_, OnFrameSend(IsData(stream_id, kBody.size(), _)));
+ nghttp2_session_send(session_.get());
+ EXPECT_THAT(serialized_, EqualsFrames({spdy::SpdyFrameType::HEADERS,
+ spdy::SpdyFrameType::DATA}));
+ EXPECT_THAT(serialized_, testing::HasSubstr(kBody));
+
+ // Once the request is flushed, the client no longer wants to write.
+ EXPECT_FALSE(nghttp2_session_want_write(session_.get()));
+}
+
+} // namespace
+} // namespace test
+} // namespace adapter
+} // namespace http2