| #include "http2/adapter/nghttp2_adapter.h" |
| |
| #include "http2/adapter/http2_protocol.h" |
| #include "http2/adapter/http2_visitor_interface.h" |
| #include "http2/adapter/mock_http2_visitor.h" |
| #include "http2/adapter/nghttp2.h" |
| #include "http2/adapter/nghttp2_test_utils.h" |
| #include "http2/adapter/oghttp2_util.h" |
| #include "http2/adapter/test_frame_sequence.h" |
| #include "http2/adapter/test_utils.h" |
| #include "common/platform/api/quiche_test.h" |
| |
| namespace http2 { |
| namespace adapter { |
| namespace test { |
| namespace { |
| |
| using ConnectionError = Http2VisitorInterface::ConnectionError; |
| |
| using testing::_; |
| |
| enum FrameType { |
| DATA, |
| HEADERS, |
| PRIORITY, |
| RST_STREAM, |
| SETTINGS, |
| PUSH_PROMISE, |
| PING, |
| GOAWAY, |
| WINDOW_UPDATE, |
| CONTINUATION, |
| }; |
| |
| // This send callback assumes |source|'s pointer is a TestDataSource, and |
| // |user_data| is a Http2VisitorInterface. |
| int TestSendCallback(nghttp2_session*, nghttp2_frame* /*frame*/, |
| const uint8_t* framehd, size_t length, |
| nghttp2_data_source* source, void* user_data) { |
| auto* visitor = static_cast<Http2VisitorInterface*>(user_data); |
| // Send the frame header via the visitor. |
| ssize_t result = visitor->OnReadyToSend(ToStringView(framehd, 9)); |
| if (result == 0) { |
| return NGHTTP2_ERR_WOULDBLOCK; |
| } |
| auto* test_source = static_cast<TestDataSource*>(source->ptr); |
| absl::string_view payload = test_source->ReadNext(length); |
| // Send the frame payload via the visitor. |
| visitor->OnReadyToSend(payload); |
| return 0; |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientConstruction) { |
| testing::StrictMock<MockHttp2Visitor> visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| ASSERT_NE(nullptr, adapter); |
| EXPECT_TRUE(adapter->want_read()); |
| EXPECT_FALSE(adapter->want_write()); |
| EXPECT_FALSE(adapter->IsServerSession()); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientHandlesFrames) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), |
| testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix)); |
| visitor.Clear(); |
| |
| EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); |
| |
| const std::string initial_frames = TestFrameSequence() |
| .ServerPreface() |
| .Ping(42) |
| .WindowUpdate(0, 1000) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(0, 8, PING, 0)); |
| EXPECT_CALL(visitor, OnPing(42, false)); |
| EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0)); |
| EXPECT_CALL(visitor, OnWindowUpdate(0, 1000)); |
| |
| const int64_t initial_result = adapter->ProcessBytes(initial_frames); |
| EXPECT_EQ(initial_frames.size(), initial_result); |
| |
| EXPECT_EQ(adapter->GetSendWindowSize(), kInitialFlowControlWindowSize + 1000); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x1, 0)); |
| |
| result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::PING})); |
| visitor.Clear(); |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const std::vector<Header> headers2 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/two"}}); |
| |
| const std::vector<Header> headers3 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/three"}}); |
| |
| const char* kSentinel1 = "arbitrary pointer 1"; |
| const char* kSentinel3 = "arbitrary pointer 3"; |
| const int32_t stream_id1 = |
| adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); |
| ASSERT_GT(stream_id1, 0); |
| QUICHE_LOG(INFO) << "Created stream: " << stream_id1; |
| |
| const int32_t stream_id2 = adapter->SubmitRequest(headers2, nullptr, nullptr); |
| ASSERT_GT(stream_id2, 0); |
| QUICHE_LOG(INFO) << "Created stream: " << stream_id2; |
| |
| const int32_t stream_id3 = |
| adapter->SubmitRequest(headers3, nullptr, const_cast<char*>(kSentinel3)); |
| ASSERT_GT(stream_id3, 0); |
| QUICHE_LOG(INFO) << "Created stream: " << stream_id3; |
| |
| const char* kSentinel2 = "arbitrary pointer 2"; |
| adapter->SetStreamUserData(stream_id2, const_cast<char*>(kSentinel2)); |
| adapter->SetStreamUserData(stream_id3, nullptr); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id2, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id2, _, 0x5, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id3, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id3, _, 0x5, 0)); |
| |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, |
| spdy::SpdyFrameType::HEADERS, |
| spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| // All streams are active and have not yet received any data, so the receive |
| // window should be at the initial value. |
| EXPECT_EQ(kInitialFlowControlWindowSize, |
| adapter->GetStreamReceiveWindowSize(stream_id1)); |
| EXPECT_EQ(kInitialFlowControlWindowSize, |
| adapter->GetStreamReceiveWindowSize(stream_id2)); |
| EXPECT_EQ(kInitialFlowControlWindowSize, |
| adapter->GetStreamReceiveWindowSize(stream_id3)); |
| |
| // Upper bound on the flow control receive window should be the initial value. |
| EXPECT_EQ(kInitialFlowControlWindowSize, |
| adapter->GetStreamReceiveWindowLimit(stream_id1)); |
| |
| // Connection has not yet received any data. |
| EXPECT_EQ(kInitialFlowControlWindowSize, adapter->GetReceiveWindowSize()); |
| |
| EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); |
| |
| EXPECT_EQ(kSentinel1, adapter->GetStreamUserData(stream_id1)); |
| EXPECT_EQ(kSentinel2, adapter->GetStreamUserData(stream_id2)); |
| EXPECT_EQ(nullptr, adapter->GetStreamUserData(stream_id3)); |
| |
| EXPECT_EQ(0, adapter->GetHpackDecoderDynamicTableSize()); |
| |
| const std::string 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(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); |
| EXPECT_CALL(visitor, |
| OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, 26, DATA, 0)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, 26)); |
| EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); |
| EXPECT_CALL(visitor, OnFrameHeader(3, 4, RST_STREAM, 0)); |
| EXPECT_CALL(visitor, OnRstStream(3, Http2ErrorCode::INTERNAL_ERROR)); |
| EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::INTERNAL_ERROR)); |
| EXPECT_CALL(visitor, OnFrameHeader(0, 19, GOAWAY, 0)); |
| EXPECT_CALL(visitor, |
| OnGoAway(5, Http2ErrorCode::ENHANCE_YOUR_CALM, "calm down!!")); |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), stream_result); |
| |
| // First stream has received some data. |
| EXPECT_GT(kInitialFlowControlWindowSize, |
| adapter->GetStreamReceiveWindowSize(stream_id1)); |
| // Second stream was closed. |
| EXPECT_EQ(-1, adapter->GetStreamReceiveWindowSize(stream_id2)); |
| // Third stream has not received any data. |
| EXPECT_EQ(kInitialFlowControlWindowSize, |
| adapter->GetStreamReceiveWindowSize(stream_id3)); |
| |
| // Connection window should be the same as the first stream. |
| EXPECT_EQ(adapter->GetReceiveWindowSize(), |
| adapter->GetStreamReceiveWindowSize(stream_id1)); |
| |
| // Upper bound on the flow control receive window should still be the initial |
| // value. |
| EXPECT_EQ(kInitialFlowControlWindowSize, |
| adapter->GetStreamReceiveWindowLimit(stream_id1)); |
| |
| EXPECT_GT(adapter->GetHpackDecoderDynamicTableSize(), 0); |
| |
| // Should be 3, but this method only works for server adapters. |
| EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); |
| |
| // Even though the client recieved a GOAWAY, streams 1 and 5 are still active. |
| EXPECT_TRUE(adapter->want_read()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, 0, DATA, 1)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, 0)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); |
| EXPECT_CALL(visitor, OnFrameHeader(5, 4, RST_STREAM, 0)); |
| EXPECT_CALL(visitor, OnRstStream(5, Http2ErrorCode::REFUSED_STREAM)); |
| EXPECT_CALL(visitor, OnCloseStream(5, Http2ErrorCode::REFUSED_STREAM)); |
| adapter->ProcessBytes(TestFrameSequence() |
| .Data(1, "", true) |
| .RstStream(5, Http2ErrorCode::REFUSED_STREAM) |
| .Serialize()); |
| |
| // Should be 5, but this method only works for server adapters. |
| EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); |
| |
| // After receiving END_STREAM for 1 and RST_STREAM for 5, the session no |
| // longer expects reads. |
| EXPECT_FALSE(adapter->want_read()); |
| |
| // Client will not have anything else to write. |
| EXPECT_FALSE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), testing::IsEmpty()); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientRejects100HeadersWithFin) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const int32_t stream_id1 = adapter->SubmitRequest(headers1, nullptr, nullptr); |
| ASSERT_GT(stream_id1, 0); |
| QUICHE_LOG(INFO) << "Created stream: " << stream_id1; |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .Headers(1, {{":status", "100"}}, /*fin=*/false) |
| .Headers(1, {{":status", "100"}}, /*fin=*/true) |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "100")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "100")); |
| EXPECT_CALL(visitor, |
| OnInvalidFrame( |
| 1, Http2VisitorInterface::InvalidFrameError::kHttpMessaging)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(RST_STREAM, 1, _, 0x0, 1)); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientRejects100HeadersWithContent) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const int32_t stream_id1 = adapter->SubmitRequest(headers1, nullptr, nullptr); |
| ASSERT_GT(stream_id1, 0); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .Headers(1, {{":status", "100"}}, |
| /*fin=*/false) |
| .Data(1, "We needed the final headers before data, whoops") |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "100")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, _)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 1, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientRejects100HeadersWithContentLength) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const int32_t stream_id1 = adapter->SubmitRequest(headers1, nullptr, nullptr); |
| ASSERT_GT(stream_id1, 0); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .Headers(1, {{":status", "100"}, {"content-length", "42"}}, |
| /*fin=*/false) |
| .Headers(1, |
| {{":status", "200"}, |
| {"server", "my-fake-server"}, |
| {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "100")); |
| EXPECT_CALL( |
| visitor, |
| OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " |
| "stream: 1, name: [content-length], value: [42]")); |
| EXPECT_CALL( |
| visitor, |
| OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 1, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientHandles204WithContent) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const int32_t stream_id1 = adapter->SubmitRequest(headers1, nullptr, nullptr); |
| ASSERT_GT(stream_id1, 0); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .Headers(1, {{":status", "204"}, {"content-length", "2"}}, |
| /*fin=*/false) |
| .Data(1, "hi") |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "204")); |
| EXPECT_CALL( |
| visitor, |
| OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " |
| "stream: 1, name: [content-length], value: [2]")); |
| EXPECT_CALL( |
| visitor, |
| OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 1, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientHandles304WithContent) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const int32_t stream_id1 = adapter->SubmitRequest(headers1, nullptr, nullptr); |
| ASSERT_GT(stream_id1, 0); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .Headers(1, {{":status", "304"}, {"content-length", "2"}}, |
| /*fin=*/false) |
| .Data(1, "hi") |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "304")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "content-length", "2")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, 2)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 1, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientHandles304WithContentLength) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const int32_t stream_id = adapter->SubmitRequest(headers, nullptr, nullptr); |
| ASSERT_GT(stream_id, 0); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .Headers(1, {{":status", "304"}, {"content-length", "2"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "304")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "content-length", "2")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientHandlesTrailers) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const char* kSentinel1 = "arbitrary pointer 1"; |
| const int32_t stream_id1 = |
| adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); |
| ASSERT_GT(stream_id1, 0); |
| QUICHE_LOG(INFO) << "Created stream: " << stream_id1; |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| absl::string_view data = visitor.data(); |
| EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .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.") |
| .Headers(1, {{"final-status", "A-OK"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); |
| EXPECT_CALL(visitor, |
| OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, 26, DATA, 0)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, 26)); |
| EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "final-status", "A-OK")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), stream_result); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientSendsTrailers) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const std::string kBody = "This is an example request body."; |
| auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); |
| body1->AppendPayload(kBody); |
| // nghttp2 does not require that the data source indicate the end of data |
| // before trailers are enqueued. |
| |
| const int32_t stream_id1 = |
| adapter->SubmitRequest(headers1, std::move(body1), nullptr); |
| ASSERT_GT(stream_id1, 0); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x4, 0)); |
| EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id1, _, 0x0, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| absl::string_view data = visitor.data(); |
| EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS, |
| spdy::SpdyFrameType::DATA})); |
| visitor.Clear(); |
| |
| const std::vector<Header> trailers1 = |
| ToHeaders({{"extra-info", "Trailers are weird but good?"}}); |
| adapter->SubmitTrailer(stream_id1, trailers1); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| data = visitor.data(); |
| EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientHandlesMetadata) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const char* kSentinel1 = "arbitrary pointer 1"; |
| const int32_t stream_id1 = |
| adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); |
| ASSERT_GT(stream_id1, 0); |
| QUICHE_LOG(INFO) << "Created stream: " << stream_id1; |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| absl::string_view data = visitor.data(); |
| EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .Metadata(0, "Example connection metadata") |
| .Headers(1, |
| {{":status", "200"}, |
| {"server", "my-fake-server"}, |
| {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, |
| /*fin=*/false) |
| .Metadata(1, "Example stream metadata") |
| .Data(1, "This is the response body.", true) |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(0, _, kMetadataFrameType, 4)); |
| EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _)); |
| EXPECT_CALL(visitor, OnMetadataForStream(0, _)); |
| EXPECT_CALL(visitor, OnMetadataEndForStream(0)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); |
| EXPECT_CALL(visitor, |
| OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 4)); |
| EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _)); |
| EXPECT_CALL(visitor, OnMetadataForStream(1, _)); |
| EXPECT_CALL(visitor, OnMetadataEndForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, 26, DATA, 1)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, 26)); |
| EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), stream_result); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientHandlesMetadataWithEmptyPayload) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const int32_t stream_id = adapter->SubmitRequest(headers1, nullptr, nullptr); |
| ASSERT_GT(stream_id, 0); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| absl::string_view data = visitor.data(); |
| EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .Headers(1, |
| {{":status", "200"}, |
| {"server", "my-fake-server"}, |
| {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, |
| /*fin=*/false) |
| .Metadata(1, "") |
| .Data(1, "This is the response body.", true) |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(3); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 4)); |
| EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _)); |
| EXPECT_CALL(visitor, OnMetadataEndForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 1)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, _)); |
| EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientHandlesMetadataWithError) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const char* kSentinel1 = "arbitrary pointer 1"; |
| const int32_t stream_id1 = |
| adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); |
| ASSERT_GT(stream_id1, 0); |
| QUICHE_LOG(INFO) << "Created stream: " << stream_id1; |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .Metadata(0, "Example connection metadata") |
| .Headers(1, |
| {{":status", "200"}, |
| {"server", "my-fake-server"}, |
| {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, |
| /*fin=*/false) |
| .Metadata(1, "Example stream metadata") |
| .Data(1, "This is the response body.", true) |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(0, _, kMetadataFrameType, 4)); |
| EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _)); |
| EXPECT_CALL(visitor, OnMetadataForStream(0, _)); |
| EXPECT_CALL(visitor, OnMetadataEndForStream(0)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); |
| EXPECT_CALL(visitor, |
| OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 4)); |
| EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _)); |
| EXPECT_CALL(visitor, OnMetadataForStream(1, _)) |
| .WillOnce(testing::Return(false)); |
| // Remaining frames are not processed due to the error. |
| 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. |
| EXPECT_EQ(stream_result, NGHTTP2_ERR_CALLBACK_FAILURE); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| EXPECT_TRUE(adapter->want_read()); // Even after an error. Why? |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientHandlesHpackHeaderTableSetting) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = ToHeaders({ |
| {":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}, |
| {"x-i-do-not-like", "green eggs and ham"}, |
| {"x-i-will-not-eat-them", "here or there, in a box, with a fox"}, |
| {"x-like-them-in-a-house", "no"}, |
| {"x-like-them-with-a-mouse", "no"}, |
| }); |
| |
| const int32_t stream_id1 = adapter->SubmitRequest(headers1, nullptr, nullptr); |
| ASSERT_GT(stream_id1, 0); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| visitor.Clear(); |
| |
| EXPECT_GT(adapter->GetHpackEncoderDynamicTableSize(), 100); |
| |
| const std::string stream_frames = |
| TestFrameSequence().Settings({{HEADER_TABLE_SIZE, 100u}}).Serialize(); |
| // Server preface (SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 6, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSetting(Http2Setting{HEADER_TABLE_SIZE, 100u})); |
| |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), stream_result); |
| |
| EXPECT_LE(adapter->GetHpackEncoderDynamicTableSize(), 100); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientHandlesInvalidTrailers) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const char* kSentinel1 = "arbitrary pointer 1"; |
| const int32_t stream_id1 = |
| adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); |
| ASSERT_GT(stream_id1, 0); |
| QUICHE_LOG(INFO) << "Created stream: " << stream_id1; |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| absl::string_view data = visitor.data(); |
| EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .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.") |
| .Headers(1, {{":bad-status", "9000"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); |
| EXPECT_CALL(visitor, |
| OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, 26, DATA, 0)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, 26)); |
| EXPECT_CALL(visitor, OnDataForStream(1, "This is the response body.")); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL( |
| visitor, |
| OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " |
| "stream: 1, name: [:bad-status], value: [9000]")); |
| EXPECT_CALL( |
| visitor, |
| OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); |
| |
| // Bad status trailer will cause a PROTOCOL_ERROR. The header is never |
| // delivered in an OnHeaderForStream callback. |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), stream_result); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, stream_id1, 4, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(RST_STREAM, stream_id1, 4, 0x0, 1)); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientRstStreamWhileHandlingHeaders) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const char* kSentinel1 = "arbitrary pointer 1"; |
| const int32_t stream_id1 = |
| adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); |
| ASSERT_GT(stream_id1, 0); |
| QUICHE_LOG(INFO) << "Created stream: " << stream_id1; |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| absl::string_view data = visitor.data(); |
| EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .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.") |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); |
| EXPECT_CALL(visitor, |
| OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")) |
| .WillOnce(testing::DoAll( |
| testing::InvokeWithoutArgs([&adapter]() { |
| adapter->SubmitRst(1, Http2ErrorCode::REFUSED_STREAM); |
| }), |
| testing::Return(Http2VisitorInterface::HEADER_RST_STREAM))); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), stream_result); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, stream_id1, 4, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, stream_id1, 4, 0x0, |
| static_cast<int>(Http2ErrorCode::REFUSED_STREAM))); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::REFUSED_STREAM)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientConnectionErrorWhileHandlingHeaders) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const char* kSentinel1 = "arbitrary pointer 1"; |
| const int32_t stream_id1 = |
| adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); |
| ASSERT_GT(stream_id1, 0); |
| QUICHE_LOG(INFO) << "Created stream: " << stream_id1; |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| absl::string_view data = visitor.data(); |
| EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .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.") |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); |
| EXPECT_CALL(visitor, |
| OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")) |
| .WillOnce( |
| testing::Return(Http2VisitorInterface::HEADER_CONNECTION_ERROR)); |
| // 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); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientConnectionErrorWhileHandlingHeadersOnly) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const char* kSentinel1 = "arbitrary pointer 1"; |
| const int32_t stream_id1 = |
| adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); |
| ASSERT_GT(stream_id1, 0); |
| QUICHE_LOG(INFO) << "Created stream: " << stream_id1; |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| absl::string_view data = visitor.data(); |
| EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .Headers(1, |
| {{":status", "200"}, |
| {"server", "my-fake-server"}, |
| {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); |
| EXPECT_CALL(visitor, |
| OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")) |
| .WillOnce( |
| testing::Return(Http2VisitorInterface::HEADER_CONNECTION_ERROR)); |
| // 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); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientRejectsHeaders) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const char* kSentinel1 = "arbitrary pointer 1"; |
| const int32_t stream_id1 = |
| adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); |
| ASSERT_GT(stream_id1, 0); |
| QUICHE_LOG(INFO) << "Created stream: " << stream_id1; |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| absl::string_view data = visitor.data(); |
| EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .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.") |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)) |
| .WillOnce(testing::Return(false)); |
| // Rejecting headers leads to a connection error. |
| EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(NGHTTP2_ERR_CALLBACK_FAILURE, stream_result); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientStartsShutdown) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| EXPECT_FALSE(adapter->want_write()); |
| |
| // No-op for a client implementation. |
| adapter->SubmitShutdownNotice(); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| |
| EXPECT_EQ(visitor.data(), spdy::kHttp2ConnectionHeaderPrefix); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientFailsOnGoAway) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| |
| const char* kSentinel1 = "arbitrary pointer 1"; |
| const int32_t stream_id1 = |
| adapter->SubmitRequest(headers1, nullptr, const_cast<char*>(kSentinel1)); |
| ASSERT_GT(stream_id1, 0); |
| QUICHE_LOG(INFO) << "Created stream: " << stream_id1; |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| absl::string_view data = visitor.data(); |
| EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .Headers(1, |
| {{":status", "200"}, |
| {"server", "my-fake-server"}, |
| {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, |
| /*fin=*/false) |
| .GoAway(1, Http2ErrorCode::INTERNAL_ERROR, "indigestion") |
| .Data(1, "This is the response body.") |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":status", "200")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "server", "my-fake-server")); |
| EXPECT_CALL(visitor, |
| OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(0, _, GOAWAY, 0)); |
| EXPECT_CALL(visitor, |
| OnGoAway(1, Http2ErrorCode::INTERNAL_ERROR, "indigestion")) |
| .WillOnce(testing::Return(false)); |
| EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(NGHTTP2_ERR_CALLBACK_FAILURE, stream_result); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientRejects101Response) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}, |
| {"upgrade", "new-protocol"}}); |
| |
| const int32_t stream_id1 = adapter->SubmitRequest(headers1, nullptr, nullptr); |
| ASSERT_GT(stream_id1, 0); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| absl::string_view data = visitor.data(); |
| EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .Headers(1, |
| {{":status", "101"}, |
| {"server", "my-fake-server"}, |
| {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, |
| /*fin=*/false) |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL( |
| visitor, |
| OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " |
| "stream: 1, name: [:status], value: [101]")); |
| EXPECT_CALL( |
| visitor, |
| OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(static_cast<int64_t>(stream_frames.size()), stream_result); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0)); |
| EXPECT_CALL( |
| visitor, |
| OnFrameSent(RST_STREAM, 1, 4, 0x0, |
| static_cast<uint32_t>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientSubmitRequest) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| // Client preface does not appear to include the mandatory SETTINGS frame. |
| EXPECT_THAT(visitor.data(), |
| testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix)); |
| visitor.Clear(); |
| |
| const std::string initial_frames = |
| TestFrameSequence().ServerPreface().Serialize(); |
| testing::InSequence s; |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| const int64_t initial_result = adapter->ProcessBytes(initial_frames); |
| EXPECT_EQ(initial_frames.size(), initial_result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| visitor.Clear(); |
| |
| EXPECT_EQ(0, adapter->GetHpackEncoderDynamicTableSize()); |
| EXPECT_FALSE(adapter->want_write()); |
| const char* kSentinel = ""; |
| const absl::string_view kBody = "This is an example request body."; |
| auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); |
| body1->AppendPayload(kBody); |
| body1->EndData(); |
| int stream_id = |
| adapter->SubmitRequest(ToHeaders({{":method", "POST"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}), |
| std::move(body1), const_cast<char*>(kSentinel)); |
| EXPECT_GT(stream_id, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); |
| EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0)); |
| |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| |
| EXPECT_EQ(kInitialFlowControlWindowSize, |
| adapter->GetStreamReceiveWindowSize(stream_id)); |
| EXPECT_EQ(kInitialFlowControlWindowSize, adapter->GetReceiveWindowSize()); |
| EXPECT_EQ(kInitialFlowControlWindowSize, |
| adapter->GetStreamReceiveWindowLimit(stream_id)); |
| |
| EXPECT_GT(adapter->GetHpackEncoderDynamicTableSize(), 0); |
| |
| // Some data was sent, so the remaining send window size should be less than |
| // the default. |
| EXPECT_LT(adapter->GetStreamSendWindowSize(stream_id), |
| kInitialFlowControlWindowSize); |
| EXPECT_GT(adapter->GetStreamSendWindowSize(stream_id), 0); |
| // Send window for a nonexistent stream is not available. |
| EXPECT_EQ(-1, adapter->GetStreamSendWindowSize(stream_id + 2)); |
| |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, |
| spdy::SpdyFrameType::DATA})); |
| EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); |
| visitor.Clear(); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| stream_id = |
| adapter->SubmitRequest(ToHeaders({{":method", "POST"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}), |
| nullptr, nullptr); |
| EXPECT_GT(stream_id, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| const char* kSentinel2 = "arbitrary pointer 2"; |
| EXPECT_EQ(nullptr, adapter->GetStreamUserData(stream_id)); |
| adapter->SetStreamUserData(stream_id, const_cast<char*>(kSentinel2)); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); |
| |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| |
| EXPECT_EQ(kSentinel2, adapter->GetStreamUserData(stream_id)); |
| |
| // No data was sent (just HEADERS), so the remaining send window size should |
| // still be the default. |
| EXPECT_EQ(adapter->GetStreamSendWindowSize(stream_id), |
| kInitialFlowControlWindowSize); |
| } |
| |
| // This is really a test of the MakeZeroCopyDataFrameSource adapter, but I |
| // wasn't sure where else to put it. |
| TEST(NgHttp2AdapterTest, ClientSubmitRequestWithDataProvider) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| // Client preface does not appear to include the mandatory SETTINGS frame. |
| EXPECT_THAT(visitor.data(), |
| testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix)); |
| visitor.Clear(); |
| |
| const std::string initial_frames = |
| TestFrameSequence().ServerPreface().Serialize(); |
| testing::InSequence s; |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| const int64_t initial_result = adapter->ProcessBytes(initial_frames); |
| EXPECT_EQ(initial_frames.size(), initial_result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| visitor.Clear(); |
| |
| EXPECT_FALSE(adapter->want_write()); |
| const absl::string_view kBody = "This is an example request body."; |
| // This test will use TestDataSource as the source of the body payload data. |
| TestDataSource body1{kBody}; |
| // The TestDataSource is wrapped in the nghttp2_data_provider data type. |
| nghttp2_data_provider provider = body1.MakeDataProvider(); |
| nghttp2_send_data_callback send_callback = &TestSendCallback; |
| |
| // This call transforms it back into a DataFrameSource, which is compatible |
| // with the Http2Adapter API. |
| std::unique_ptr<DataFrameSource> frame_source = |
| MakeZeroCopyDataFrameSource(provider, &visitor, std::move(send_callback)); |
| int stream_id = |
| adapter->SubmitRequest(ToHeaders({{":method", "POST"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}), |
| std::move(frame_source), nullptr); |
| EXPECT_GT(stream_id, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); |
| EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0)); |
| |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, |
| spdy::SpdyFrameType::DATA})); |
| EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| // This test verifies how nghttp2 behaves when a data source becomes |
| // read-blocked. |
| TEST(NgHttp2AdapterTest, ClientSubmitRequestWithDataProviderAndReadBlock) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| const absl::string_view kBody = "This is an example request body."; |
| // This test will use TestDataSource as the source of the body payload data. |
| TestDataSource body1{kBody}; |
| body1.set_is_data_available(false); |
| // The TestDataSource is wrapped in the nghttp2_data_provider data type. |
| nghttp2_data_provider provider = body1.MakeDataProvider(); |
| nghttp2_send_data_callback send_callback = &TestSendCallback; |
| |
| // This call transforms it back into a DataFrameSource, which is compatible |
| // with the Http2Adapter API. |
| std::unique_ptr<DataFrameSource> frame_source = |
| MakeZeroCopyDataFrameSource(provider, &visitor, std::move(send_callback)); |
| int stream_id = |
| adapter->SubmitRequest(ToHeaders({{":method", "POST"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}), |
| std::move(frame_source), nullptr); |
| EXPECT_GT(stream_id, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| // Client preface does not appear to include the mandatory SETTINGS frame. |
| absl::string_view serialized = visitor.data(); |
| EXPECT_THAT(serialized, |
| testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| // Resume the deferred stream. |
| body1.set_is_data_available(true); |
| EXPECT_TRUE(adapter->ResumeStream(stream_id)); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0)); |
| |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::DATA})); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| // Stream data is done, so this stream cannot be resumed. |
| EXPECT_FALSE(adapter->ResumeStream(stream_id)); |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| // This test verifies how nghttp2 behaves when a data source is read block, then |
| // ends with an empty DATA frame. |
| TEST(NgHttp2AdapterTest, ClientSubmitRequestEmptyDataWithFin) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| const absl::string_view kEmptyBody = ""; |
| // This test will use TestDataSource as the source of the body payload data. |
| TestDataSource body1{kEmptyBody}; |
| body1.set_is_data_available(false); |
| // The TestDataSource is wrapped in the nghttp2_data_provider data type. |
| nghttp2_data_provider provider = body1.MakeDataProvider(); |
| nghttp2_send_data_callback send_callback = &TestSendCallback; |
| |
| // This call transforms it back into a DataFrameSource, which is compatible |
| // with the Http2Adapter API. |
| std::unique_ptr<DataFrameSource> frame_source = |
| MakeZeroCopyDataFrameSource(provider, &visitor, std::move(send_callback)); |
| int stream_id = |
| adapter->SubmitRequest(ToHeaders({{":method", "POST"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}), |
| std::move(frame_source), nullptr); |
| EXPECT_GT(stream_id, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| // Client preface does not appear to include the mandatory SETTINGS frame. |
| absl::string_view serialized = visitor.data(); |
| EXPECT_THAT(serialized, |
| testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| // Resume the deferred stream. |
| body1.set_is_data_available(true); |
| EXPECT_TRUE(adapter->ResumeStream(stream_id)); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 0, 0x1, 0)); |
| |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::DATA})); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| // Stream data is done, so this stream cannot be resumed. |
| EXPECT_FALSE(adapter->ResumeStream(stream_id)); |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| // This test verifies how nghttp2 behaves when a connection becomes |
| // write-blocked. |
| TEST(NgHttp2AdapterTest, ClientSubmitRequestWithDataProviderAndWriteBlock) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| const absl::string_view kBody = "This is an example request body."; |
| // This test will use TestDataSource as the source of the body payload data. |
| TestDataSource body1{kBody}; |
| // The TestDataSource is wrapped in the nghttp2_data_provider data type. |
| nghttp2_data_provider provider = body1.MakeDataProvider(); |
| nghttp2_send_data_callback send_callback = &TestSendCallback; |
| |
| // This call transforms it back into a DataFrameSource, which is compatible |
| // with the Http2Adapter API. |
| std::unique_ptr<DataFrameSource> frame_source = |
| MakeZeroCopyDataFrameSource(provider, &visitor, std::move(send_callback)); |
| int stream_id = |
| adapter->SubmitRequest(ToHeaders({{":method", "POST"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}), |
| std::move(frame_source), nullptr); |
| EXPECT_GT(stream_id, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| visitor.set_is_write_blocked(true); |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), testing::IsEmpty()); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); |
| EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0)); |
| |
| visitor.set_is_write_blocked(false); |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| |
| // Client preface does not appear to include the mandatory SETTINGS frame. |
| absl::string_view serialized = visitor.data(); |
| EXPECT_THAT(serialized, |
| testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS, |
| spdy::SpdyFrameType::DATA})); |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientReceivesDataOnClosedStream) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| // Client preface does not appear to include the mandatory SETTINGS frame. |
| EXPECT_THAT(visitor.data(), |
| testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix)); |
| visitor.Clear(); |
| |
| const std::string initial_frames = |
| TestFrameSequence().ServerPreface().Serialize(); |
| testing::InSequence s; |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| const int64_t initial_result = adapter->ProcessBytes(initial_frames); |
| EXPECT_EQ(initial_frames.size(), initial_result); |
| |
| // Client SETTINGS ack |
| EXPECT_TRUE(adapter->want_write()); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| visitor.Clear(); |
| |
| // Let the client open a stream with a request. |
| int stream_id = |
| adapter->SubmitRequest(ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}), |
| nullptr, nullptr); |
| EXPECT_GT(stream_id, 0); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); |
| |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| // Let the client RST_STREAM the stream it opened. |
| adapter->SubmitRst(stream_id, Http2ErrorCode::CANCEL); |
| EXPECT_TRUE(adapter->want_write()); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, stream_id, _, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(RST_STREAM, stream_id, _, 0x0, |
| static_cast<int>(Http2ErrorCode::CANCEL))); |
| EXPECT_CALL(visitor, OnCloseStream(stream_id, Http2ErrorCode::CANCEL)); |
| |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::RST_STREAM})); |
| visitor.Clear(); |
| |
| // Let the server send a response on the stream. (It might not have received |
| // the RST_STREAM yet.) |
| const std::string response_frames = |
| TestFrameSequence() |
| .Headers(stream_id, |
| {{":status", "200"}, |
| {"server", "my-fake-server"}, |
| {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, |
| /*fin=*/false) |
| .Data(stream_id, "This is the response body.", /*fin=*/true) |
| .Serialize(); |
| |
| // The visitor gets notified about the HEADERS frame but not the DATA frame on |
| // the closed stream. No further processing for either frame occurs. |
| EXPECT_CALL(visitor, OnFrameHeader(stream_id, _, HEADERS, 0x4)); |
| EXPECT_CALL(visitor, OnFrameHeader(stream_id, _, DATA, _)).Times(0); |
| |
| const int64_t response_result = adapter->ProcessBytes(response_frames); |
| EXPECT_EQ(response_frames.size(), response_result); |
| |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| TEST(NgHttp2AdapterTest, SubmitMetadata) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| auto source = absl::make_unique<TestMetadataSource>(ToHeaderBlock(ToHeaders( |
| {{"query-cost", "is too darn high"}, {"secret-sauce", "hollandaise"}}))); |
| adapter->SubmitMetadata(1, 16384u, std::move(source)); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(kMetadataFrameType, 1, _, 0x4, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| absl::string_view serialized = visitor.data(); |
| EXPECT_THAT(serialized, |
| testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT( |
| serialized, |
| EqualsFrames({static_cast<spdy::SpdyFrameType>(kMetadataFrameType)})); |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| TEST(NgHttp2AdapterTest, SubmitMetadataMultipleFrames) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| const auto kLargeValue = std::string(63 * 1024, 'a'); |
| auto source = absl::make_unique<TestMetadataSource>( |
| ToHeaderBlock(ToHeaders({{"large-value", kLargeValue}}))); |
| adapter->SubmitMetadata(1, 16384u, std::move(source)); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| testing::InSequence seq; |
| EXPECT_CALL(visitor, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(kMetadataFrameType, 1, _, 0x0, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(kMetadataFrameType, 1, _, 0x0, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(kMetadataFrameType, 1, _, 0x0, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(kMetadataFrameType, 1, _, 0x4, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| absl::string_view serialized = visitor.data(); |
| EXPECT_THAT(serialized, |
| testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT( |
| serialized, |
| EqualsFrames({static_cast<spdy::SpdyFrameType>(kMetadataFrameType), |
| static_cast<spdy::SpdyFrameType>(kMetadataFrameType), |
| static_cast<spdy::SpdyFrameType>(kMetadataFrameType), |
| static_cast<spdy::SpdyFrameType>(kMetadataFrameType)})); |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| TEST(NgHttp2AdapterTest, SubmitConnectionMetadata) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| auto source = absl::make_unique<TestMetadataSource>(ToHeaderBlock(ToHeaders( |
| {{"query-cost", "is too darn high"}, {"secret-sauce", "hollandaise"}}))); |
| adapter->SubmitMetadata(0, 16384u, std::move(source)); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(kMetadataFrameType, 0, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(kMetadataFrameType, 0, _, 0x4, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| absl::string_view serialized = visitor.data(); |
| EXPECT_THAT(serialized, |
| testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT( |
| serialized, |
| EqualsFrames({static_cast<spdy::SpdyFrameType>(kMetadataFrameType)})); |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientObeysMaxConcurrentStreams) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| // Client preface does not appear to include the mandatory SETTINGS frame. |
| EXPECT_THAT(visitor.data(), |
| testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix)); |
| visitor.Clear(); |
| |
| const std::string initial_frames = |
| TestFrameSequence() |
| .ServerPreface({{.id = MAX_CONCURRENT_STREAMS, .value = 1}}) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Server preface (SETTINGS with MAX_CONCURRENT_STREAMS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 6, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSetting); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| const int64_t initial_result = adapter->ProcessBytes(initial_frames); |
| EXPECT_EQ(initial_frames.size(), initial_result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| visitor.Clear(); |
| |
| EXPECT_FALSE(adapter->want_write()); |
| const absl::string_view kBody = "This is an example request body."; |
| auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); |
| body1->AppendPayload(kBody); |
| body1->EndData(); |
| const int stream_id = |
| adapter->SubmitRequest(ToHeaders({{":method", "POST"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}), |
| std::move(body1), nullptr); |
| EXPECT_GT(stream_id, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); |
| EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0)); |
| |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, |
| spdy::SpdyFrameType::DATA})); |
| EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); |
| visitor.Clear(); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| const int next_stream_id = |
| adapter->SubmitRequest(ToHeaders({{":method", "POST"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/two"}}), |
| nullptr, nullptr); |
| |
| // A new pending stream is created, but because of MAX_CONCURRENT_STREAMS, the |
| // session should not want to write it at the moment. |
| EXPECT_GT(next_stream_id, stream_id); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .Headers(stream_id, |
| {{":status", "200"}, |
| {"server", "my-fake-server"}, |
| {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, |
| /*fin=*/false) |
| .Data(stream_id, "This is the response body.", /*fin=*/true) |
| .Serialize(); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(stream_id, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(stream_id)); |
| EXPECT_CALL(visitor, OnHeaderForStream(stream_id, ":status", "200")); |
| EXPECT_CALL(visitor, |
| OnHeaderForStream(stream_id, "server", "my-fake-server")); |
| EXPECT_CALL(visitor, OnHeaderForStream(stream_id, "date", |
| "Tue, 6 Apr 2021 12:54:01 GMT")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(stream_id)); |
| EXPECT_CALL(visitor, OnFrameHeader(stream_id, 26, DATA, 0x1)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(stream_id, 26)); |
| EXPECT_CALL(visitor, |
| OnDataForStream(stream_id, "This is the response body.")); |
| EXPECT_CALL(visitor, OnEndStream(stream_id)); |
| EXPECT_CALL(visitor, |
| OnCloseStream(stream_id, Http2ErrorCode::HTTP2_NO_ERROR)); |
| |
| // The first stream should close, which should make the session want to write |
| // the next stream. |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), stream_result); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, next_stream_id, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, next_stream_id, _, 0x5, 0)); |
| |
| result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientForbidsPushPromise) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| adapter->SubmitSettings({{ENABLE_PUSH, 0}}); |
| |
| testing::InSequence s; |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0)); |
| |
| int write_result = adapter->Send(); |
| EXPECT_EQ(0, write_result); |
| absl::string_view data = visitor.data(); |
| EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| |
| visitor.Clear(); |
| |
| const std::vector<Header> headers = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| const int32_t stream_id = adapter->SubmitRequest(headers, nullptr, nullptr); |
| ASSERT_GT(stream_id, 0); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); |
| write_result = adapter->Send(); |
| EXPECT_EQ(0, write_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| const std::vector<Header> push_headers = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/push"}}); |
| const std::string frames = TestFrameSequence() |
| .ServerPreface() |
| .SettingsAck() |
| .PushPromise(stream_id, 2, push_headers) |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| // SETTINGS ack (to acknowledge PUSH_ENABLED=0) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0x1)); |
| EXPECT_CALL(visitor, OnSettingsAck); |
| |
| // The PUSH_PROMISE is now treated as an invalid frame. |
| EXPECT_CALL(visitor, OnFrameHeader(stream_id, _, PUSH_PROMISE, _)); |
| EXPECT_CALL(visitor, OnInvalidFrame(stream_id, _)); |
| |
| const int64_t read_result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), read_result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); |
| EXPECT_CALL( |
| visitor, |
| OnFrameSent(GOAWAY, 0, _, 0x0, |
| static_cast<int32_t>(Http2ErrorCode::PROTOCOL_ERROR))); |
| |
| write_result = adapter->Send(); |
| EXPECT_EQ(0, write_result); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientForbidsPushStream) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| adapter->SubmitSettings({{ENABLE_PUSH, 0}}); |
| |
| testing::InSequence s; |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0)); |
| |
| int write_result = adapter->Send(); |
| EXPECT_EQ(0, write_result); |
| absl::string_view data = visitor.data(); |
| EXPECT_THAT(data, testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); |
| data.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); |
| EXPECT_THAT(data, EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| |
| visitor.Clear(); |
| |
| const std::vector<Header> headers = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}); |
| const int32_t stream_id = adapter->SubmitRequest(headers, nullptr, nullptr); |
| ASSERT_GT(stream_id, 0); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); |
| write_result = adapter->Send(); |
| EXPECT_EQ(0, write_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| const std::string frames = |
| TestFrameSequence() |
| .ServerPreface() |
| .SettingsAck() |
| .Headers(2, |
| {{":status", "200"}, |
| {"server", "my-fake-server"}, |
| {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| // Server preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| // SETTINGS ack (to acknowledge PUSH_ENABLED=0) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0x1)); |
| EXPECT_CALL(visitor, OnSettingsAck); |
| |
| // The push HEADERS are invalid. |
| EXPECT_CALL(visitor, OnFrameHeader(2, _, HEADERS, _)); |
| EXPECT_CALL(visitor, OnInvalidFrame(2, _)); |
| |
| const int64_t read_result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), read_result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); |
| EXPECT_CALL( |
| visitor, |
| OnFrameSent(GOAWAY, 0, _, 0x0, |
| static_cast<int32_t>(Http2ErrorCode::PROTOCOL_ERROR))); |
| |
| write_result = adapter->Send(); |
| EXPECT_EQ(0, write_result); |
| } |
| |
| TEST(NgHttp2AdapterTest, FailureSendingConnectionPreface) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor); |
| |
| visitor.set_has_write_error(); |
| EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kSendError)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(result, NGHTTP2_ERR_CALLBACK_FAILURE); |
| } |
| |
| TEST(NgHttp2AdapterTest, ConnectionErrorOnControlFrameSent) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string frames = |
| TestFrameSequence().ClientPreface().Ping(42).Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| // PING |
| EXPECT_CALL(visitor, OnFrameHeader(0, _, PING, 0)); |
| EXPECT_CALL(visitor, OnPing(42, false)); |
| |
| const int64_t read_result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| // SETTINGS ack |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)) |
| .WillOnce(testing::Return(-902)); |
| EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kSendError)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_LT(send_result, 0); |
| |
| // Apparently nghttp2 retries sending the frames that had failed before. |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(PING, 0, _, 0x1, 0)); |
| send_result = adapter->Send(); |
| EXPECT_EQ(send_result, 0); |
| |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| TEST(NgHttp2AdapterTest, ConnectionErrorOnDataFrameSent) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/true) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| // Stream 1 |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0x5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| |
| const int64_t read_result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); |
| |
| auto body = absl::make_unique<TestDataFrameSource>(visitor, true); |
| body->AppendPayload("Here is some data, which will lead to a fatal error"); |
| TestDataFrameSource* body_ptr = body.get(); |
| int submit_result = adapter->SubmitResponse( |
| 1, ToHeaders({{":status", "200"}}), std::move(body)); |
| ASSERT_EQ(0, submit_result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| // SETTINGS ack |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| // Stream 1, with doomed DATA |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); |
| EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)) |
| .WillOnce(testing::Return(-902)); |
| EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kSendError)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_LT(send_result, 0); |
| |
| // The test data source got a signal that the first chunk of data was sent |
| // successfully, so discarded that data internally. However, due to the send |
| // error, the next Send() from nghttp2 will try to send that exact same data |
| // again. Without this line appending the exact same data back to the data |
| // source, the test crashes. It is not clear how the data source would know to |
| // not discard the data, unless told by the session? This is not intuitive. |
| body_ptr->AppendPayload( |
| "Here is some data, which will lead to a fatal error"); |
| |
| // Apparently nghttp2 retries sending the frames that had failed before. |
| EXPECT_TRUE(adapter->want_write()); |
| EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)); |
| |
| send_result = adapter->Send(); |
| EXPECT_EQ(send_result, 0); |
| |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerConstruction) { |
| testing::StrictMock<MockHttp2Visitor> visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| ASSERT_NE(nullptr, adapter); |
| EXPECT_TRUE(adapter->want_read()); |
| EXPECT_FALSE(adapter->want_write()); |
| EXPECT_TRUE(adapter->IsServerSession()); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerHandlesFrames) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); |
| EXPECT_EQ(0, adapter->GetHpackDecoderDynamicTableSize()); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Ping(42) |
| .WindowUpdate(0, 1000) |
| .Headers(1, |
| {{":method", "POST"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/false) |
| .WindowUpdate(1, 2000) |
| .Data(1, "This is the request body.") |
| .Headers(3, |
| {{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/two"}}, |
| /*fin=*/true) |
| .RstStream(3, Http2ErrorCode::CANCEL) |
| .Ping(47) |
| .Serialize(); |
| testing::InSequence s; |
| |
| const char* kSentinel1 = "arbitrary pointer 1"; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(0, 8, PING, 0)); |
| EXPECT_CALL(visitor, OnPing(42, false)); |
| EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0)); |
| EXPECT_CALL(visitor, OnWindowUpdate(0, 1000)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)) |
| .WillOnce(testing::InvokeWithoutArgs([&adapter, kSentinel1]() { |
| adapter->SetStreamUserData(1, const_cast<char*>(kSentinel1)); |
| return true; |
| })); |
| EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0)); |
| EXPECT_CALL(visitor, OnWindowUpdate(1, 2000)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, 25, DATA, 0)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, 25)); |
| EXPECT_CALL(visitor, OnDataForStream(1, "This is the request body.")); |
| EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); |
| EXPECT_CALL(visitor, OnHeaderForStream(3, ":method", "GET")); |
| EXPECT_CALL(visitor, OnHeaderForStream(3, ":scheme", "http")); |
| EXPECT_CALL(visitor, OnHeaderForStream(3, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(3, ":path", "/this/is/request/two")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(3)); |
| EXPECT_CALL(visitor, OnEndStream(3)); |
| EXPECT_CALL(visitor, OnFrameHeader(3, 4, RST_STREAM, 0)); |
| EXPECT_CALL(visitor, OnRstStream(3, Http2ErrorCode::CANCEL)); |
| EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::CANCEL)); |
| EXPECT_CALL(visitor, OnFrameHeader(0, 8, PING, 0)); |
| EXPECT_CALL(visitor, OnPing(47, false)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), result); |
| |
| EXPECT_EQ(kSentinel1, adapter->GetStreamUserData(1)); |
| |
| EXPECT_GT(kInitialFlowControlWindowSize, |
| adapter->GetStreamReceiveWindowSize(1)); |
| EXPECT_EQ(adapter->GetStreamReceiveWindowSize(1), |
| adapter->GetReceiveWindowSize()); |
| // Upper bound should still be the original value. |
| EXPECT_EQ(kInitialFlowControlWindowSize, |
| adapter->GetStreamReceiveWindowLimit(1)); |
| |
| EXPECT_GT(adapter->GetHpackDecoderDynamicTableSize(), 0); |
| |
| // Because stream 3 has already been closed, it's not possible to set user |
| // data. |
| const char* kSentinel3 = "another arbitrary pointer"; |
| adapter->SetStreamUserData(3, const_cast<char*>(kSentinel3)); |
| EXPECT_EQ(nullptr, adapter->GetStreamUserData(3)); |
| |
| EXPECT_EQ(3, adapter->GetHighestReceivedStreamId()); |
| |
| EXPECT_EQ(adapter->GetSendWindowSize(), kInitialFlowControlWindowSize + 1000); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x1, 0)); |
| |
| int send_result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, send_result); |
| // SETTINGS ack, two PING acks. |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::PING, |
| spdy::SpdyFrameType::PING})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerHandlesDataWithPadding) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "POST"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/false) |
| .Data(1, "This is the request body.", |
| /*fin=*/true, /*padding_length=*/39) |
| .Headers(3, |
| {{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/two"}}, |
| /*fin=*/true) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, 25 + 39, DATA, 0x9)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, 25 + 39)); |
| EXPECT_CALL(visitor, OnDataForStream(1, "This is the request body.")); |
| // Note: nghttp2 passes padding information after the actual data. |
| EXPECT_CALL(visitor, OnDataPaddingLength(1, 39)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); |
| EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(3)); |
| EXPECT_CALL(visitor, OnEndStream(3)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| // Tests the case where the response body is in the progress of being sent while |
| // trailers are queued. |
| TEST(NgHttp2AdapterTest, ServerSubmitsTrailersWhileDataDeferred) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "POST"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/false) |
| .WindowUpdate(1, 2000) |
| .Data(1, "This is the request body.") |
| .WindowUpdate(0, 2000) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0)); |
| EXPECT_CALL(visitor, OnWindowUpdate(1, 2000)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, _)); |
| EXPECT_CALL(visitor, OnDataForStream(1, "This is the request body.")); |
| EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0)); |
| EXPECT_CALL(visitor, OnWindowUpdate(0, 2000)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), static_cast<size_t>(result)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| visitor.Clear(); |
| |
| const absl::string_view kBody = "This is an example response body."; |
| |
| // The body source must indicate that the end of the body is not the end of |
| // the stream. |
| auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); |
| body1->AppendPayload(kBody); |
| auto* body1_ptr = body1.get(); |
| int submit_result = adapter->SubmitResponse( |
| 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}), |
| std::move(body1)); |
| EXPECT_EQ(submit_result, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); |
| EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)); |
| |
| send_result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, send_result); |
| visitor.Clear(); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| int trailer_result = |
| adapter->SubmitTrailer(1, ToHeaders({{"final-status", "a-ok"}})); |
| ASSERT_EQ(trailer_result, 0); |
| |
| // Even though the data source has not finished sending data, nghttp2 will |
| // write the trailers anyway. |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0)); |
| |
| send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| // Resuming the stream results in the library wanting to write again. |
| body1_ptr->AppendPayload(kBody); |
| body1_ptr->EndData(); |
| adapter->ResumeStream(1); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| |
| // But no data is written for the stream. |
| EXPECT_THAT(visitor.data(), testing::IsEmpty()); |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerErrorWhileHandlingHeaders) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "POST"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}, |
| {"accept", "some bogus value!"}}, |
| /*fin=*/false) |
| .WindowUpdate(1, 2000) |
| .Data(1, "This is the request body.") |
| .WindowUpdate(0, 2000) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "accept", "some bogus value!")) |
| .WillOnce(testing::Return(Http2VisitorInterface::HEADER_RST_STREAM)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0)); |
| EXPECT_CALL(visitor, OnWindowUpdate(1, 2000)); |
| // DATA frame is not delivered to the visitor. |
| EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0)); |
| EXPECT_CALL(visitor, OnWindowUpdate(0, 2000)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 1, 4, 0x0, |
| static_cast<int>(Http2ErrorCode::INTERNAL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::INTERNAL_ERROR)); |
| |
| int send_result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, send_result); |
| // SETTINGS ack |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerErrorWhileHandlingHeadersDropsFrames) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "POST"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}, |
| {"accept", "some bogus value!"}}, |
| /*fin=*/false) |
| .WindowUpdate(1, 2000) |
| .Data(1, "This is the request body.") |
| .Metadata(1, "This is the request metadata.") |
| .RstStream(1, Http2ErrorCode::CANCEL) |
| .WindowUpdate(0, 2000) |
| .Headers(3, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/two"}}, |
| /*fin=*/false) |
| .Metadata(3, "This is the request metadata.", |
| /*multiple_frames=*/true) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "accept", "some bogus value!")) |
| .WillOnce(testing::Return(Http2VisitorInterface::HEADER_RST_STREAM)); |
| // For the RST_STREAM-marked stream, the control frames and METADATA frame but |
| // not the DATA frame are delivered to the visitor. |
| EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0)); |
| EXPECT_CALL(visitor, OnWindowUpdate(1, 2000)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 4)); |
| EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _)); |
| EXPECT_CALL(visitor, OnMetadataForStream(1, _)); |
| EXPECT_CALL(visitor, OnMetadataEndForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, 4, RST_STREAM, 0)); |
| EXPECT_CALL(visitor, OnRstStream(1, Http2ErrorCode::CANCEL)); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::CANCEL)); |
| EXPECT_CALL(visitor, OnFrameHeader(0, 4, WINDOW_UPDATE, 0)); |
| EXPECT_CALL(visitor, OnWindowUpdate(0, 2000)); |
| EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); |
| EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(3)); |
| EXPECT_CALL(visitor, OnFrameHeader(3, _, kMetadataFrameType, 0)); |
| EXPECT_CALL(visitor, OnBeginMetadataForStream(3, _)); |
| EXPECT_CALL(visitor, OnMetadataForStream(3, "This is the re")) |
| .WillOnce(testing::DoAll(testing::InvokeWithoutArgs([&adapter]() { |
| adapter->SubmitRst( |
| 3, Http2ErrorCode::REFUSED_STREAM); |
| }), |
| testing::Return(true))); |
| // The rest of the metadata is still delivered to the visitor. |
| EXPECT_CALL(visitor, OnFrameHeader(3, _, kMetadataFrameType, 4)); |
| EXPECT_CALL(visitor, OnBeginMetadataForStream(3, _)); |
| EXPECT_CALL(visitor, OnMetadataForStream(3, "quest metadata.")); |
| EXPECT_CALL(visitor, OnMetadataEndForStream(3)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), static_cast<size_t>(result)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 1, 4, 0x0, |
| static_cast<int>(Http2ErrorCode::INTERNAL_ERROR))); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 3, 4, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 3, 4, 0x0, |
| static_cast<int>(Http2ErrorCode::REFUSED_STREAM))); |
| EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::REFUSED_STREAM)); |
| |
| int send_result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerConnectionErrorWhileHandlingHeaders) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "POST"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}, |
| {"Accept", "uppercase, oh boy!"}}, |
| /*fin=*/false) |
| .WindowUpdate(1, 2000) |
| .Data(1, "This is the request body.") |
| .WindowUpdate(0, 2000) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnErrorDebug); |
| EXPECT_CALL( |
| visitor, |
| OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)) |
| .WillOnce(testing::Return(false)); |
| // Translation to nghttp2 treats this error as a general parsing error. |
| EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(result, NGHTTP2_ERR_CALLBACK_FAILURE); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 1, 4, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); |
| |
| int send_result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, send_result); |
| // SETTINGS ack and RST_STREAM |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerErrorAfterHandlingHeaders) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "POST"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/false) |
| .WindowUpdate(1, 2000) |
| .Data(1, "This is the request body.") |
| .WindowUpdate(0, 2000) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)) |
| .WillOnce(testing::Return(false)); |
| EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(-902, result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| int send_result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, send_result); |
| // SETTINGS ack |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| // Exercises the case when a visitor chooses to reject a frame based solely on |
| // the frame header, which is a fatal error for the connection. |
| TEST(NgHttp2AdapterTest, ServerRejectsFrameHeader) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Ping(64) |
| .Headers(1, |
| {{":method", "POST"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/false) |
| .WindowUpdate(1, 2000) |
| .Data(1, "This is the request body.") |
| .WindowUpdate(0, 2000) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(0, 8, PING, 0)) |
| .WillOnce(testing::Return(false)); |
| EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(-902, result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| int send_result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, send_result); |
| // SETTINGS ack |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerRejectsBeginningOfData) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "POST"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/false) |
| .Data(1, "This is the request body.") |
| .Headers(3, |
| {{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/two"}}, |
| /*fin=*/true) |
| .RstStream(3, Http2ErrorCode::CANCEL) |
| .Ping(47) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, 25, DATA, 0)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, 25)) |
| .WillOnce(testing::Return(false)); |
| EXPECT_CALL(visitor, OnConnectionError(ConnectionError::kParseError)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(NGHTTP2_ERR_CALLBACK_FAILURE, result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| int send_result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, send_result); |
| // SETTINGS ack. |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerRejectsStreamData) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "POST"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/false) |
| .Data(1, "This is the request body.") |
| .Headers(3, |
| {{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/two"}}, |
| /*fin=*/true) |
| .RstStream(3, Http2ErrorCode::CANCEL) |
| .Ping(47) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| 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(ConnectionError::kParseError)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(NGHTTP2_ERR_CALLBACK_FAILURE, result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| int send_result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, send_result); |
| // SETTINGS ack. |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerReceivesTooLargeHeader) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| // nghttp2 will accept a maximum of 64kB of huffman encoded data per header |
| // field. |
| const std::string too_large_value = std::string(80 * 1024, 'q'); |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "POST"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}, |
| {"x-toobig", too_large_value}}, |
| /*fin=*/false) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| // Further header processing is skipped, as the header field is too large. |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| // Since nghttp2 opted not to process the header, it generates a GOAWAY with |
| // error code COMPRESSION_ERROR. |
| EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, 8, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(GOAWAY, 0, 8, 0x0, |
| static_cast<int>(Http2ErrorCode::COMPRESSION_ERROR))); |
| |
| int send_result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, send_result); |
| // GOAWAY. |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::GOAWAY})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerReceivesInvalidAuthority) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "POST"}, |
| {":scheme", "https"}, |
| {":authority", "ex|ample.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/false) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL( |
| visitor, |
| OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " |
| "stream: 1, name: [:authority], value: [ex|ample.com]")); |
| EXPECT_CALL( |
| visitor, |
| OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0x0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 1, 4, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerSubmitResponse) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/true) |
| .Serialize(); |
| testing::InSequence s; |
| |
| const char* kSentinel1 = "arbitrary pointer 1"; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| // Stream 1 |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)) |
| .WillOnce(testing::InvokeWithoutArgs([&adapter, kSentinel1]() { |
| adapter->SetStreamUserData(1, const_cast<char*>(kSentinel1)); |
| return true; |
| })); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), result); |
| |
| EXPECT_EQ(1, adapter->GetHighestReceivedStreamId()); |
| |
| // Server will want to send a SETTINGS ack. |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| visitor.Clear(); |
| |
| EXPECT_EQ(0, adapter->GetHpackEncoderDynamicTableSize()); |
| |
| EXPECT_FALSE(adapter->want_write()); |
| const absl::string_view kBody = "This is an example response body."; |
| // A data fin is not sent so that the stream remains open, and the flow |
| // control state can be verified. |
| auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); |
| body1->AppendPayload(kBody); |
| int submit_result = adapter->SubmitResponse( |
| 1, |
| ToHeaders({{":status", "404"}, |
| {"x-comment", "I have no idea what you're talking about."}}), |
| std::move(body1)); |
| EXPECT_EQ(submit_result, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| // Stream user data should have been set successfully after receiving headers. |
| EXPECT_EQ(kSentinel1, adapter->GetStreamUserData(1)); |
| adapter->SetStreamUserData(1, nullptr); |
| EXPECT_EQ(nullptr, adapter->GetStreamUserData(1)); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); |
| EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)); |
| |
| send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, |
| spdy::SpdyFrameType::DATA})); |
| EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| // Some data was sent, so the remaining send window size should be less than |
| // the default. |
| EXPECT_LT(adapter->GetStreamSendWindowSize(1), kInitialFlowControlWindowSize); |
| EXPECT_GT(adapter->GetStreamSendWindowSize(1), 0); |
| // Send window for a nonexistent stream is not available. |
| EXPECT_EQ(adapter->GetStreamSendWindowSize(3), -1); |
| |
| EXPECT_GT(adapter->GetHpackEncoderDynamicTableSize(), 0); |
| } |
| |
| // Should also test: client attempts shutdown, server attempts shutdown after an |
| // explicit GOAWAY. |
| TEST(NgHttp2AdapterTest, ServerSendsShutdown) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "POST"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/false) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), result); |
| |
| adapter->SubmitShutdownNotice(); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::GOAWAY})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerSendsTrailers) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/true) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| // Stream 1 |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), result); |
| |
| // Server will want to send a SETTINGS ack. |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| visitor.Clear(); |
| |
| EXPECT_FALSE(adapter->want_write()); |
| const absl::string_view kBody = "This is an example response body."; |
| |
| // The body source must indicate that the end of the body is not the end of |
| // the stream. |
| auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); |
| body1->AppendPayload(kBody); |
| body1->EndData(); |
| int submit_result = adapter->SubmitResponse( |
| 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}), |
| std::move(body1)); |
| EXPECT_EQ(submit_result, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); |
| EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)); |
| |
| send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, |
| spdy::SpdyFrameType::DATA})); |
| EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); |
| visitor.Clear(); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| // The body source has been exhausted by the call to Send() above. |
| int trailer_result = adapter->SubmitTrailer( |
| 1, ToHeaders({{"final-status", "a-ok"}, |
| {"x-comment", "trailers sure are cool"}})); |
| ASSERT_EQ(trailer_result, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0)); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); |
| |
| send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientSendsContinuation) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/true, |
| /*add_continuation=*/true) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| // Stream 1 |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 1)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, CONTINUATION, 4)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), result); |
| } |
| |
| TEST(NgHttp2AdapterTest, ClientSendsMetadataWithContinuation) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| const std::string frames = |
| TestFrameSequence() |
| .ClientPreface() |
| .Metadata(0, "Example connection metadata in multiple frames", true) |
| .Headers(1, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/false, |
| /*add_continuation=*/true) |
| .Metadata(1, |
| "Some stream metadata that's also sent in multiple frames", |
| true) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| // Metadata on stream 0 |
| EXPECT_CALL(visitor, OnFrameHeader(0, _, kMetadataFrameType, 0)); |
| EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _)); |
| EXPECT_CALL(visitor, OnMetadataForStream(0, _)); |
| EXPECT_CALL(visitor, OnFrameHeader(0, _, kMetadataFrameType, 4)); |
| EXPECT_CALL(visitor, OnBeginMetadataForStream(0, _)); |
| EXPECT_CALL(visitor, OnMetadataForStream(0, _)); |
| EXPECT_CALL(visitor, OnMetadataEndForStream(0)); |
| |
| // Stream 1 |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, CONTINUATION, 4)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| // Metadata on stream 1 |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 0)); |
| EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _)); |
| EXPECT_CALL(visitor, OnMetadataForStream(1, _)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, kMetadataFrameType, 4)); |
| EXPECT_CALL(visitor, OnBeginMetadataForStream(1, _)); |
| EXPECT_CALL(visitor, OnMetadataForStream(1, _)); |
| EXPECT_CALL(visitor, OnMetadataEndForStream(1)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), result); |
| EXPECT_EQ("Example connection metadata in multiple frames", |
| absl::StrJoin(visitor.GetMetadata(0), "")); |
| EXPECT_EQ("Some stream metadata that's also sent in multiple frames", |
| absl::StrJoin(visitor.GetMetadata(1), "")); |
| } |
| |
| TEST(NgHttp2AdapterTest, RepeatedHeaderNames) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}, |
| {"accept", "text/plain"}, |
| {"accept", "text/html"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| // Stream 1 |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "accept", "text/plain")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "accept", "text/html")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| |
| int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), static_cast<size_t>(result)); |
| |
| const std::vector<Header> headers1 = ToHeaders( |
| {{":status", "200"}, {"content-length", "10"}, {"content-length", "10"}}); |
| auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); |
| body1->AppendPayload("perfection"); |
| body1->EndData(); |
| |
| int submit_result = adapter->SubmitResponse(1, headers1, std::move(body1)); |
| ASSERT_EQ(0, submit_result); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); |
| EXPECT_CALL(visitor, OnFrameSent(DATA, 1, 10, 0x1, 0)); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::HEADERS, |
| spdy::SpdyFrameType::DATA})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerRespondsToRequestWithTrailers) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| const std::string frames = |
| TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}) |
| .Data(1, "Example data, woohoo.") |
| .Serialize(); |
| |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| // Stream 1 |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, _)); |
| EXPECT_CALL(visitor, OnDataForStream(1, _)); |
| |
| int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), static_cast<size_t>(result)); |
| |
| const std::vector<Header> headers1 = ToHeaders({{":status", "200"}}); |
| auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); |
| TestDataFrameSource* body1_ptr = body1.get(); |
| |
| int submit_result = adapter->SubmitResponse(1, headers1, std::move(body1)); |
| ASSERT_EQ(0, submit_result); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::HEADERS})); |
| visitor.Clear(); |
| |
| const std::string more_frames = |
| TestFrameSequence() |
| .Headers(1, {{"extra-info", "Trailers are weird but good?"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, "extra-info", |
| "Trailers are weird but good?")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| |
| result = adapter->ProcessBytes(more_frames); |
| EXPECT_EQ(more_frames.size(), static_cast<size_t>(result)); |
| |
| body1_ptr->EndData(); |
| EXPECT_EQ(true, adapter->ResumeStream(1)); |
| |
| EXPECT_CALL(visitor, OnFrameSent(DATA, 1, 0, 0x1, 0)); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); |
| |
| send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::DATA})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerSubmitsResponseWithDataSourceError) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/true) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| // Stream 1 |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), static_cast<size_t>(result)); |
| |
| auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); |
| body1->SimulateError(); |
| int submit_result = adapter->SubmitResponse( |
| 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}), |
| std::move(body1)); |
| EXPECT_EQ(submit_result, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(RST_STREAM, 1, _, 0x0, 2)); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::INTERNAL_ERROR)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::HEADERS, |
| spdy::SpdyFrameType::RST_STREAM})); |
| visitor.Clear(); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| int trailer_result = |
| adapter->SubmitTrailer(1, ToHeaders({{":final-status", "a-ok"}})); |
| // The library does not object to the user queuing trailers, even through the |
| // stream has already been closed. |
| EXPECT_EQ(trailer_result, 0); |
| } |
| |
| TEST(NgHttp2AdapterTest, CompleteRequestWithServerResponse) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| const std::string frames = |
| TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/false) |
| .Data(1, "This is the response body.", /*fin=*/true) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| // Stream 1 |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 1)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, _)); |
| EXPECT_CALL(visitor, OnDataForStream(1, _)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), static_cast<size_t>(result)); |
| |
| int submit_result = |
| adapter->SubmitResponse(1, ToHeaders({{":status", "200"}}), nullptr); |
| EXPECT_EQ(submit_result, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0)); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::HEADERS})); |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| TEST(NgHttp2AdapterTest, IncompleteRequestWithServerResponse) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/false) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| // Stream 1 |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), static_cast<size_t>(result)); |
| |
| int submit_result = |
| adapter->SubmitResponse(1, ToHeaders({{":status", "200"}}), nullptr); |
| EXPECT_EQ(submit_result, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0)); |
| // BUG: Should send RST_STREAM NO_ERROR as well, but nghttp2 does not. |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::HEADERS})); |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerSendsInvalidTrailers) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/true) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| // Stream 1 |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), result); |
| |
| const absl::string_view kBody = "This is an example response body."; |
| |
| // The body source must indicate that the end of the body is not the end of |
| // the stream. |
| auto body1 = absl::make_unique<TestDataFrameSource>(visitor, false); |
| body1->AppendPayload(kBody); |
| body1->EndData(); |
| int submit_result = adapter->SubmitResponse( |
| 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}), |
| std::move(body1)); |
| EXPECT_EQ(submit_result, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); |
| EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::HEADERS, |
| spdy::SpdyFrameType::DATA})); |
| EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody)); |
| visitor.Clear(); |
| EXPECT_FALSE(adapter->want_write()); |
| |
| // The body source has been exhausted by the call to Send() above. |
| int trailer_result = |
| adapter->SubmitTrailer(1, ToHeaders({{":final-status", "a-ok"}})); |
| ASSERT_EQ(trailer_result, 0); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5)); |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0)); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); |
| |
| send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerDropsNewStreamBelowWatermark) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(3, |
| {{":method", "POST"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/false) |
| .Data(3, "This is the request body.") |
| .Headers(1, |
| {{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/two"}}, |
| /*fin=*/true) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); |
| EXPECT_CALL(visitor, OnHeaderForStream(3, ":method", "POST")); |
| EXPECT_CALL(visitor, OnHeaderForStream(3, ":scheme", "https")); |
| EXPECT_CALL(visitor, OnHeaderForStream(3, ":authority", "example.com")); |
| EXPECT_CALL(visitor, OnHeaderForStream(3, ":path", "/this/is/request/one")); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(3)); |
| EXPECT_CALL(visitor, OnFrameHeader(3, 25, DATA, 0)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(3, 25)); |
| EXPECT_CALL(visitor, OnDataForStream(3, "This is the request body.")); |
| |
| // It looks like nghttp2 delivers the under-watermark frame header but |
| // otherwise silently drops the rest of the frame without error. |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnInvalidFrame).Times(0); |
| EXPECT_CALL(visitor, OnConnectionError).Times(0); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), result); |
| |
| EXPECT_EQ(3, adapter->GetHighestReceivedStreamId()); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| int send_result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, send_result); |
| // SETTINGS ack |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| TEST(NgHttp2AdapterInteractionTest, |
| ClientServerInteractionRepeatedHeaderNames) { |
| DataSavingVisitor client_visitor; |
| auto client_adapter = NgHttp2Adapter::CreateClientAdapter(client_visitor); |
| |
| client_adapter->SubmitSettings({}); |
| |
| const std::vector<Header> headers1 = |
| ToHeaders({{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}, |
| {"accept", "text/plain"}, |
| {"accept", "text/html"}}); |
| |
| const int32_t stream_id1 = |
| client_adapter->SubmitRequest(headers1, nullptr, nullptr); |
| ASSERT_GT(stream_id1, 0); |
| |
| EXPECT_CALL(client_visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); |
| EXPECT_CALL(client_visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); |
| EXPECT_CALL(client_visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5)); |
| EXPECT_CALL(client_visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0)); |
| int send_result = client_adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| |
| DataSavingVisitor server_visitor; |
| auto server_adapter = NgHttp2Adapter::CreateServerAdapter(server_visitor); |
| |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(server_visitor, OnFrameHeader(0, _, SETTINGS, 0)); |
| EXPECT_CALL(server_visitor, OnSettingsStart()); |
| EXPECT_CALL(server_visitor, OnSetting(_)).Times(testing::AnyNumber()); |
| EXPECT_CALL(server_visitor, OnSettingsEnd()); |
| // Stream 1 |
| EXPECT_CALL(server_visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(server_visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(server_visitor, OnHeaderForStream(1, ":method", "GET")); |
| EXPECT_CALL(server_visitor, OnHeaderForStream(1, ":scheme", "http")); |
| EXPECT_CALL(server_visitor, |
| OnHeaderForStream(1, ":authority", "example.com")); |
| EXPECT_CALL(server_visitor, |
| OnHeaderForStream(1, ":path", "/this/is/request/one")); |
| EXPECT_CALL(server_visitor, OnHeaderForStream(1, "accept", "text/plain")); |
| EXPECT_CALL(server_visitor, OnHeaderForStream(1, "accept", "text/html")); |
| EXPECT_CALL(server_visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(server_visitor, OnEndStream(1)); |
| |
| int64_t result = server_adapter->ProcessBytes(client_visitor.data()); |
| EXPECT_EQ(client_visitor.data().size(), static_cast<size_t>(result)); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerForbidsWindowUpdateOnIdleStream) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); |
| |
| const std::string frames = |
| TestFrameSequence().ClientPreface().WindowUpdate(1, 42).Serialize(); |
| |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0)); |
| EXPECT_CALL(visitor, OnInvalidFrame(1, _)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), result); |
| |
| EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(GOAWAY, 0, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| |
| int send_result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, send_result); |
| // The GOAWAY apparently causes the SETTINGS ack to be dropped. |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::GOAWAY})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerForbidsDataOnIdleStream) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Data(1, "Sorry, out of order") |
| .Serialize(); |
| |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), result); |
| |
| EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| // In this case, nghttp2 goes straight to GOAWAY and does not invoke the |
| // invalid frame callback. |
| EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(GOAWAY, 0, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| |
| int send_result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, send_result); |
| // The GOAWAY apparently causes the SETTINGS ack to be dropped. |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::GOAWAY})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerForbidsRstStreamOnIdleStream) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); |
| |
| const std::string frames = |
| TestFrameSequence() |
| .ClientPreface() |
| .RstStream(1, Http2ErrorCode::ENHANCE_YOUR_CALM) |
| .Serialize(); |
| |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, RST_STREAM, 0)); |
| EXPECT_CALL(visitor, OnInvalidFrame(1, _)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(frames.size(), result); |
| |
| EXPECT_EQ(0, adapter->GetHighestReceivedStreamId()); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(GOAWAY, 0, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| |
| int send_result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, send_result); |
| // The GOAWAY apparently causes the SETTINGS ack to be dropped. |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::GOAWAY})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerForbidsNewStreamAboveStreamLimit) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| adapter->SubmitSettings({{MAX_CONCURRENT_STREAMS, 1}}); |
| |
| const std::string initial_frames = |
| TestFrameSequence().ClientPreface().Serialize(); |
| |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| const int64_t initial_result = adapter->ProcessBytes(initial_frames); |
| EXPECT_EQ(initial_frames.size(), initial_result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| // Server initial SETTINGS (with MAX_CONCURRENT_STREAMS) and SETTINGS ack. |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::SETTINGS})); |
| visitor.Clear(); |
| |
| // Let the client send a SETTINGS ack and then attempt to open more than the |
| // advertised number of streams. The overflow stream should be rejected. |
| const std::string stream_frames = |
| TestFrameSequence() |
| .SettingsAck() |
| .Headers(1, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/true) |
| .Headers(3, |
| {{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/two"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0x1)); |
| EXPECT_CALL(visitor, OnSettingsAck()); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0x5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 0x5)); |
| EXPECT_CALL( |
| visitor, |
| OnInvalidFrame(3, Http2VisitorInterface::InvalidFrameError::kProtocol)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), stream_result); |
| |
| // The server should send a GOAWAY for this error, even though |
| // OnInvalidFrame() returns true. |
| EXPECT_TRUE(adapter->want_write()); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(GOAWAY, 0, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| |
| send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::GOAWAY})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerRstStreamsNewStreamAboveStreamLimitBeforeAck) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| adapter->SubmitSettings({{MAX_CONCURRENT_STREAMS, 1}}); |
| |
| const std::string initial_frames = |
| TestFrameSequence().ClientPreface().Serialize(); |
| |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| const int64_t initial_result = adapter->ProcessBytes(initial_frames); |
| EXPECT_EQ(initial_frames.size(), initial_result); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| // Server initial SETTINGS (with MAX_CONCURRENT_STREAMS) and SETTINGS ack. |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::SETTINGS})); |
| visitor.Clear(); |
| |
| // Let the client avoid sending a SETTINGS ack and attempt to open more than |
| // the advertised number of streams. The server should still reject the |
| // overflow stream, albeit with RST_STREAM REFUSED_STREAM instead of GOAWAY. |
| const std::string stream_frames = |
| TestFrameSequence() |
| .Headers(1, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/true) |
| .Headers(3, |
| {{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/two"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0x5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 0x5)); |
| EXPECT_CALL(visitor, |
| OnInvalidFrame( |
| 3, Http2VisitorInterface::InvalidFrameError::kRefusedStream)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_result, stream_frames.size()); |
| |
| // The server sends a RST_STREAM for the offending stream. |
| EXPECT_TRUE(adapter->want_write()); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 3, _, 0x0, |
| static_cast<int>(Http2ErrorCode::REFUSED_STREAM))); |
| |
| send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, AutomaticSettingsAndPingAcks) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| const std::string frames = |
| TestFrameSequence().ClientPreface().Ping(42).Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| // PING |
| EXPECT_CALL(visitor, OnFrameHeader(0, _, PING, 0)); |
| EXPECT_CALL(visitor, OnPing(42, false)); |
| |
| const int64_t read_result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| // Server preface does not appear to include the mandatory SETTINGS frame. |
| // SETTINGS ack |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| // PING ack |
| EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(PING, 0, _, 0x1, 0)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::PING})); |
| } |
| |
| TEST(NgHttp2AdapterTest, AutomaticPingAcksDisabled) { |
| DataSavingVisitor visitor; |
| nghttp2_option* options; |
| nghttp2_option_new(&options); |
| nghttp2_option_set_no_auto_ping_ack(options, 1); |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor, options); |
| nghttp2_option_del(options); |
| |
| const std::string frames = |
| TestFrameSequence().ClientPreface().Ping(42).Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| // PING |
| EXPECT_CALL(visitor, OnFrameHeader(0, _, PING, 0)); |
| EXPECT_CALL(visitor, OnPing(42, false)); |
| |
| const int64_t read_result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| // Server preface does not appear to include the mandatory SETTINGS frame. |
| // SETTINGS ack |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| // No PING ack expected because automatic PING acks are disabled. |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerForbidsProtocolPseudoheaderBeforeAck) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string initial_frames = |
| TestFrameSequence().ClientPreface().Serialize(); |
| |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| const int64_t initial_result = adapter->ProcessBytes(initial_frames); |
| EXPECT_EQ(static_cast<size_t>(initial_result), initial_frames.size()); |
| |
| // The client attempts to send a CONNECT request with the `:protocol` |
| // pseudoheader before receiving the server's SETTINGS frame. |
| const std::string stream1_frames = |
| TestFrameSequence() |
| .Headers(1, |
| {{":method", "CONNECT"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}, |
| {":protocol", "websocket"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0x5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); |
| EXPECT_CALL( |
| visitor, |
| OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " |
| "stream: 1, name: [:protocol], value: [websocket]")); |
| EXPECT_CALL( |
| visitor, |
| OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); |
| |
| int64_t stream_result = adapter->ProcessBytes(stream1_frames); |
| EXPECT_EQ(static_cast<size_t>(stream_result), stream1_frames.size()); |
| |
| // Server sends a SETTINGS ack and initial SETTINGS (with |
| // ENABLE_CONNECT_PROTOCOL). |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0)); |
| |
| // The server sends a RST_STREAM for the offending stream. |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 1, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); |
| |
| adapter->SubmitSettings({{ENABLE_CONNECT_PROTOCOL, 1}}); |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM})); |
| visitor.Clear(); |
| |
| // The client attempts to send a CONNECT request with the `:protocol` |
| // pseudoheader before acking the server's SETTINGS frame. |
| const std::string stream3_frames = |
| TestFrameSequence() |
| .Headers(3, |
| {{":method", "CONNECT"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/two"}, |
| {":protocol", "websocket"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| // Surprisingly, nghttp2 is okay with this. |
| EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 0x5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); |
| EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(5); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(3)); |
| EXPECT_CALL(visitor, OnEndStream(3)); |
| |
| stream_result = adapter->ProcessBytes(stream3_frames); |
| EXPECT_EQ(static_cast<size_t>(stream_result), stream3_frames.size()); |
| |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerAllowsProtocolPseudoheaderAfterAck) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| adapter->SubmitSettings({{ENABLE_CONNECT_PROTOCOL, 1}}); |
| |
| const std::string initial_frames = |
| TestFrameSequence().ClientPreface().Serialize(); |
| |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| const int64_t initial_result = adapter->ProcessBytes(initial_frames); |
| EXPECT_EQ(static_cast<size_t>(initial_result), initial_frames.size()); |
| |
| // Server initial SETTINGS (with ENABLE_CONNECT_PROTOCOL) and SETTINGS ack. |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| visitor.Clear(); |
| |
| // The client attempts to send a CONNECT request with the `:protocol` |
| // pseudoheader after acking the server's SETTINGS frame. |
| const std::string stream_frames = |
| TestFrameSequence() |
| .SettingsAck() |
| .Headers(1, |
| {{":method", "CONNECT"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}, |
| {":protocol", "websocket"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(0, _, SETTINGS, 0x1)); |
| EXPECT_CALL(visitor, OnSettingsAck()); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0x5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(static_cast<size_t>(stream_result), stream_frames.size()); |
| |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| TEST(NgHttp2AdapterTest, SkipsSendingFramesForRejectedStream) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string initial_frames = |
| TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "GET"}, |
| {":scheme", "http"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0x5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| |
| const int64_t initial_result = adapter->ProcessBytes(initial_frames); |
| EXPECT_EQ(static_cast<size_t>(initial_result), initial_frames.size()); |
| |
| auto body = absl::make_unique<TestDataFrameSource>(visitor, true); |
| body->AppendPayload("Here is some data, which will be completely ignored!"); |
| |
| int submit_result = adapter->SubmitResponse( |
| 1, ToHeaders({{":status", "200"}}), std::move(body)); |
| ASSERT_EQ(0, submit_result); |
| |
| auto source = absl::make_unique<TestMetadataSource>(ToHeaderBlock(ToHeaders( |
| {{"query-cost", "is too darn high"}, {"secret-sauce", "hollandaise"}}))); |
| adapter->SubmitMetadata(1, 16384u, std::move(source)); |
| |
| adapter->SubmitWindowUpdate(1, 1024); |
| adapter->SubmitRst(1, Http2ErrorCode::INTERNAL_ERROR); |
| |
| // Server initial SETTINGS and SETTINGS ack. |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0)); |
| |
| // nghttp2 apparently allows extension frames to be sent on reset streams. |
| EXPECT_CALL(visitor, OnBeforeFrameSent(kMetadataFrameType, 1, _, 0x4)); |
| EXPECT_CALL(visitor, OnFrameSent(kMetadataFrameType, 1, _, 0x4, 0)); |
| |
| // The server sends a RST_STREAM for the offending stream. |
| // The response HEADERS, DATA and WINDOW_UPDATE are all ignored. |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 1, _, 0x0, |
| static_cast<int>(Http2ErrorCode::INTERNAL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::INTERNAL_ERROR)); |
| |
| int send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT( |
| visitor.data(), |
| EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| static_cast<spdy::SpdyFrameType>(kMetadataFrameType), |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerStartsShutdown) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| EXPECT_FALSE(adapter->want_write()); |
| |
| adapter->SubmitShutdownNotice(); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::GOAWAY})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerStartsShutdownAfterGoaway) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| EXPECT_FALSE(adapter->want_write()); |
| |
| adapter->SubmitGoAway(1, Http2ErrorCode::HTTP2_NO_ERROR, |
| "and don't come back!"); |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0)); |
| |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::GOAWAY})); |
| |
| // No-op, since a GOAWAY has previously been enqueued. |
| adapter->SubmitShutdownNotice(); |
| EXPECT_FALSE(adapter->want_write()); |
| } |
| |
| // Verifies that a connection-level processing error results in repeatedly |
| // returning a positive value for ProcessBytes() to mark all data as consumed. |
| TEST(NgHttp2AdapterTest, ConnectionErrorWithBlackholeSinkingData) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| const std::string frames = |
| TestFrameSequence().ClientPreface().WindowUpdate(1, 42).Serialize(); |
| |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, 4, WINDOW_UPDATE, 0)); |
| EXPECT_CALL(visitor, OnInvalidFrame(1, _)); |
| |
| const int64_t result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(static_cast<size_t>(result), frames.size()); |
| |
| // Ask the connection to process more bytes. Because the option is enabled, |
| // the data should be marked as consumed. |
| const std::string next_frame = TestFrameSequence().Ping(42).Serialize(); |
| const int64_t next_result = adapter->ProcessBytes(next_frame); |
| EXPECT_EQ(static_cast<size_t>(next_result), next_frame.size()); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerDoesNotSendFramesAfterImmediateGoAway) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| // Submit a custom initial SETTINGS frame with one setting. |
| adapter->SubmitSettings({{HEADER_TABLE_SIZE, 100u}}); |
| |
| const std::string frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}}, |
| /*fin=*/true) |
| .Serialize(); |
| testing::InSequence s; |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| // Stream 1 |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 0x5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| |
| const int64_t read_result = adapter->ProcessBytes(frames); |
| EXPECT_EQ(static_cast<size_t>(read_result), frames.size()); |
| |
| // Submit a response for the stream. |
| auto body = absl::make_unique<TestDataFrameSource>(visitor, true); |
| body->AppendPayload("This data is doomed to never be written."); |
| int submit_result = adapter->SubmitResponse( |
| 1, ToHeaders({{":status", "200"}}), std::move(body)); |
| ASSERT_EQ(0, submit_result); |
| |
| // Submit a WINDOW_UPDATE frame. |
| adapter->SubmitWindowUpdate(kConnectionStreamId, 42); |
| |
| // Submit another SETTINGS frame. |
| adapter->SubmitSettings({}); |
| |
| // Submit some metadata. |
| auto source = absl::make_unique<TestMetadataSource>(ToHeaderBlock(ToHeaders( |
| {{"query-cost", "is too darn high"}, {"secret-sauce", "hollandaise"}}))); |
| adapter->SubmitMetadata(1, 16384u, std::move(source)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| // Trigger a connection error. Only the response headers will be written. |
| const std::string connection_error_frames = |
| TestFrameSequence().WindowUpdate(3, 42).Serialize(); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(3, 4, WINDOW_UPDATE, 0)); |
| EXPECT_CALL(visitor, OnInvalidFrame(3, _)); |
| |
| const int64_t result = adapter->ProcessBytes(connection_error_frames); |
| EXPECT_EQ(static_cast<size_t>(result), connection_error_frames.size()); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 6, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 6, 0x0, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x0)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x0, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(GOAWAY, 0, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| |
| int send_result = adapter->Send(); |
| // Some bytes should have been serialized. |
| EXPECT_EQ(0, send_result); |
| // The GOAWAY apparently causes the other frames to be dropped except for the |
| // non-ack SETTINGS frames; nghttp2 sends non-ack SETTINGS frames because they |
| // could be the initial SETTINGS frame. However, nghttp2 still allows sending |
| // multiple non-ack SETTINGS, which feels non-ideal. |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::GOAWAY})); |
| visitor.Clear(); |
| |
| // Try to submit more frames for writing. They should not be written. |
| adapter->SubmitPing(42); |
| EXPECT_FALSE(adapter->want_write()); |
| send_result = adapter->Send(); |
| EXPECT_EQ(0, send_result); |
| EXPECT_THAT(visitor.data(), testing::IsEmpty()); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerHandlesContentLength) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/one"}, |
| {"content-length", "2"}}) |
| .Data(1, "hi", /*fin=*/true) |
| .Headers(3, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/two"}, |
| {"content-length", "nan"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| // Stream 1: content-length is correct |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 1)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, 2)); |
| EXPECT_CALL(visitor, OnDataForStream(1, "hi")); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| |
| // Stream 3: content-length is not a number |
| EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); |
| EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); |
| EXPECT_CALL( |
| visitor, |
| OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " |
| "stream: 3, name: [content-length], value: [nan]")); |
| EXPECT_CALL( |
| visitor, |
| OnInvalidFrame(3, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 3, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::PROTOCOL_ERROR)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerHandlesContentLengthMismatch) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/two"}, |
| {"content-length", "2"}}) |
| .Data(1, "h", /*fin=*/true) |
| .Headers(3, {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/three"}, |
| {"content-length", "2"}}) |
| .Data(3, "howdy", /*fin=*/true) |
| .Headers(5, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/four"}, |
| {"content-length", "2"}}, |
| /*fin=*/true) |
| .Headers(7, |
| {{":method", "GET"}, |
| {":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/this/is/request/four"}, |
| {"content-length", "2"}}, |
| /*fin=*/false) |
| .Data(7, "h", /*fin=*/false) |
| .Headers(7, {{"extra-info", "Trailers with content-length mismatch"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| // Stream 1: content-length is larger than actual data |
| // All data is delivered to the visitor, but OnInvalidFrame() is not. |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 1)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(1, 1)); |
| EXPECT_CALL(visitor, OnDataForStream(1, "h")); |
| |
| // Stream 3: content-length is smaller than actual data |
| // The beginning of data is delivered to the visitor, but not the actual data, |
| // and neither is OnInvalidFrame(). |
| EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); |
| EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(5); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(3)); |
| EXPECT_CALL(visitor, OnFrameHeader(3, _, DATA, 1)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(3, 5)); |
| |
| // Stream 5: content-length is invalid and HEADERS ends the stream |
| // When the stream ends with HEADERS, nghttp2 invokes OnInvalidFrame(). |
| EXPECT_CALL(visitor, OnFrameHeader(5, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(5)); |
| EXPECT_CALL(visitor, OnHeaderForStream(5, _, _)).Times(5); |
| EXPECT_CALL(visitor, |
| OnInvalidFrame( |
| 5, Http2VisitorInterface::InvalidFrameError::kHttpMessaging)); |
| |
| // Stream 7: content-length is invalid and trailers end the stream |
| // When the stream ends with trailers, nghttp2 invokes OnInvalidFrame(). |
| EXPECT_CALL(visitor, OnFrameHeader(7, _, HEADERS, 4)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(7)); |
| EXPECT_CALL(visitor, OnHeaderForStream(7, _, _)).Times(5); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(7)); |
| EXPECT_CALL(visitor, OnFrameHeader(7, _, DATA, 0)); |
| EXPECT_CALL(visitor, OnBeginDataForStream(7, 1)); |
| EXPECT_CALL(visitor, OnDataForStream(7, "h")); |
| EXPECT_CALL(visitor, OnFrameHeader(7, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(7)); |
| EXPECT_CALL(visitor, OnHeaderForStream(7, _, _)); |
| EXPECT_CALL(visitor, |
| OnInvalidFrame( |
| 7, Http2VisitorInterface::InvalidFrameError::kHttpMessaging)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 1, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 3, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::PROTOCOL_ERROR)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 5, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 5, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(5, Http2ErrorCode::PROTOCOL_ERROR)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 7, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 7, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(7, Http2ErrorCode::PROTOCOL_ERROR)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM, |
| spdy::SpdyFrameType::RST_STREAM, |
| spdy::SpdyFrameType::RST_STREAM, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerHandlesAsteriskPathForOptions) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::string stream_frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "*"}, |
| {":method", "OPTIONS"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerHandlesInvalidPath) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::string stream_frames = |
| TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "*"}, |
| {":method", "GET"}}, |
| /*fin=*/true) |
| .Headers(3, |
| {{":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "other/non/slash/starter"}, |
| {":method", "GET"}}, |
| /*fin=*/true) |
| .Headers(5, |
| {{":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", ""}, |
| {":method", "GET"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); |
| EXPECT_CALL(visitor, |
| OnInvalidFrame( |
| 1, Http2VisitorInterface::InvalidFrameError::kHttpMessaging)); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); |
| EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); |
| EXPECT_CALL(visitor, |
| OnInvalidFrame( |
| 3, Http2VisitorInterface::InvalidFrameError::kHttpMessaging)); |
| |
| EXPECT_CALL(visitor, OnFrameHeader(5, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(5)); |
| EXPECT_CALL(visitor, OnHeaderForStream(5, _, _)).Times(2); |
| EXPECT_CALL( |
| visitor, |
| OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " |
| "stream: 5, name: [:path], value: []")); |
| EXPECT_CALL( |
| visitor, |
| OnInvalidFrame(5, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 1, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 3, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::PROTOCOL_ERROR)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 5, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 5, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(5, Http2ErrorCode::PROTOCOL_ERROR)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM, |
| spdy::SpdyFrameType::RST_STREAM, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| TEST(NgHttp2AdapterTest, ServerHandlesTeHeader) { |
| DataSavingVisitor visitor; |
| auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); |
| |
| testing::InSequence s; |
| |
| const std::string stream_frames = TestFrameSequence() |
| .ClientPreface() |
| .Headers(1, |
| {{":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/"}, |
| {":method", "GET"}, |
| {"te", "trailers"}}, |
| /*fin=*/true) |
| .Headers(3, |
| {{":scheme", "https"}, |
| {":authority", "example.com"}, |
| {":path", "/"}, |
| {":method", "GET"}, |
| {"te", "trailers, deflate"}}, |
| /*fin=*/true) |
| .Serialize(); |
| |
| // Client preface (empty SETTINGS) |
| EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); |
| EXPECT_CALL(visitor, OnSettingsStart()); |
| EXPECT_CALL(visitor, OnSettingsEnd()); |
| |
| // Stream 1: TE: trailers should be allowed. |
| EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5); |
| EXPECT_CALL(visitor, OnEndHeadersForStream(1)); |
| EXPECT_CALL(visitor, OnEndStream(1)); |
| |
| // Stream 3: TE: <non-trailers> should be rejected. |
| EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5)); |
| EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); |
| EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(4); |
| EXPECT_CALL( |
| visitor, |
| OnErrorDebug("Invalid HTTP header field was received: frame type: 1, " |
| "stream: 3, name: [te], value: [trailers, deflate]")); |
| EXPECT_CALL( |
| visitor, |
| OnInvalidFrame(3, Http2VisitorInterface::InvalidFrameError::kHttpHeader)); |
| |
| const int64_t stream_result = adapter->ProcessBytes(stream_frames); |
| EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); |
| |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); |
| EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0)); |
| EXPECT_CALL(visitor, |
| OnFrameSent(RST_STREAM, 3, _, 0x0, |
| static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); |
| EXPECT_CALL(visitor, OnCloseStream(3, Http2ErrorCode::PROTOCOL_ERROR)); |
| |
| EXPECT_TRUE(adapter->want_write()); |
| int result = adapter->Send(); |
| EXPECT_EQ(0, result); |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, |
| spdy::SpdyFrameType::RST_STREAM})); |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace adapter |
| } // namespace http2 |