diff options
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.cc | 205 |
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 |