| #include "http2/adapter/oghttp2_session.h" | |
| #include "http2/adapter/mock_http2_visitor.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 spdy::SpdyFrameType; | |
| using testing::_; | |
| enum FrameType { | |
| DATA, | |
| HEADERS, | |
| PRIORITY, | |
| RST_STREAM, | |
| SETTINGS, | |
| PUSH_PROMISE, | |
| PING, | |
| GOAWAY, | |
| WINDOW_UPDATE, | |
| }; | |
| } // namespace | |
| TEST(OgHttp2SessionTest, ClientConstruction) { | |
| testing::StrictMock<MockHttp2Visitor> visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); | |
| EXPECT_TRUE(session.want_read()); | |
| EXPECT_FALSE(session.want_write()); | |
| EXPECT_EQ(session.GetRemoteWindowSize(), kInitialFlowControlWindowSize); | |
| EXPECT_FALSE(session.IsServerSession()); | |
| EXPECT_EQ(0, session.GetHighestReceivedStreamId()); | |
| } | |
| TEST(OgHttp2SessionTest, ClientHandlesFrames) { | |
| testing::StrictMock<MockHttp2Visitor> visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); | |
| 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 = session.ProcessBytes(initial_frames); | |
| EXPECT_EQ(initial_frames.size(), static_cast<size_t>(initial_result)); | |
| EXPECT_EQ(session.GetRemoteWindowSize(), | |
| kInitialFlowControlWindowSize + 1000); | |
| EXPECT_EQ(0, session.GetHighestReceivedStreamId()); | |
| // Connection has not yet received any data. | |
| EXPECT_EQ(kInitialFlowControlWindowSize, session.GetReceiveWindowSize()); | |
| EXPECT_EQ(0, session.GetHpackDecoderDynamicTableSize()); | |
| // Should OgHttp2Session require that streams 1 and 3 have been created? | |
| // Submit a request to ensure the first stream is created. | |
| const char* kSentinel1 = "arbitrary pointer 1"; | |
| auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); | |
| body1->AppendPayload("This is an example request body."); | |
| body1->EndData(); | |
| int stream_id = | |
| session.SubmitRequest(ToHeaders({{":method", "POST"}, | |
| {":scheme", "http"}, | |
| {":authority", "example.com"}, | |
| {":path", "/this/is/request/one"}}), | |
| std::move(body1), const_cast<char*>(kSentinel1)); | |
| EXPECT_EQ(stream_id, 1); | |
| 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.") | |
| .RstStream(3, Http2ErrorCode::INTERNAL_ERROR) | |
| .GoAway(5, Http2ErrorCode::ENHANCE_YOUR_CALM, "calm down!!") | |
| .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, 0)); | |
| EXPECT_CALL(visitor, OnBeginDataForStream(stream_id, 26)); | |
| EXPECT_CALL(visitor, | |
| OnDataForStream(stream_id, "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, "")); | |
| const int64_t stream_result = session.ProcessBytes(stream_frames); | |
| EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); | |
| EXPECT_EQ(3, session.GetHighestReceivedStreamId()); | |
| // The first stream is active and has received some data. | |
| EXPECT_GT(kInitialFlowControlWindowSize, | |
| session.GetStreamReceiveWindowSize(stream_id)); | |
| // Connection receive window is equivalent to the first stream's. | |
| EXPECT_EQ(session.GetReceiveWindowSize(), | |
| session.GetStreamReceiveWindowSize(stream_id)); | |
| // Receive window upper bound is still the initial value. | |
| EXPECT_EQ(kInitialFlowControlWindowSize, | |
| session.GetStreamReceiveWindowLimit(stream_id)); | |
| EXPECT_GT(session.GetHpackDecoderDynamicTableSize(), 0); | |
| } | |
| // Verifies that a client session enqueues initial SETTINGS if Send() is called | |
| // before any frames are explicitly queued. | |
| TEST(OgHttp2SessionTest, ClientEnqueuesSettingsOnSend) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); | |
| EXPECT_FALSE(session.want_write()); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| int result = session.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({SpdyFrameType::SETTINGS})); | |
| } | |
| // Verifies that a client session enqueues initial SETTINGS before whatever | |
| // frame type is passed to the first invocation of EnqueueFrame(). | |
| TEST(OgHttp2SessionTest, ClientEnqueuesSettingsBeforeOtherFrame) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); | |
| EXPECT_FALSE(session.want_write()); | |
| session.EnqueueFrame(absl::make_unique<spdy::SpdyPingIR>(42)); | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x0, 0)); | |
| int result = session.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({SpdyFrameType::SETTINGS, SpdyFrameType::PING})); | |
| } | |
| // Verifies that if the first call to EnqueueFrame() passes a SETTINGS frame, | |
| // the client session will not enqueue an additional SETTINGS frame. | |
| TEST(OgHttp2SessionTest, ClientEnqueuesSettingsOnce) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); | |
| EXPECT_FALSE(session.want_write()); | |
| session.EnqueueFrame(absl::make_unique<spdy::SpdySettingsIR>()); | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| int result = session.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({SpdyFrameType::SETTINGS})); | |
| } | |
| TEST(OgHttp2SessionTest, ClientSubmitRequest) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); | |
| EXPECT_FALSE(session.want_write()); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| // Even though the user has not queued any frames for the session, it should | |
| // still send the connection preface. | |
| int result = session.Send(); | |
| EXPECT_EQ(0, result); | |
| absl::string_view serialized = visitor.data(); | |
| EXPECT_THAT(serialized, | |
| testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix)); | |
| serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix)); | |
| // Initial SETTINGS. | |
| EXPECT_THAT(serialized, EqualsFrames({SpdyFrameType::SETTINGS})); | |
| 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 = session.ProcessBytes(initial_frames); | |
| EXPECT_EQ(initial_frames.size(), static_cast<size_t>(initial_result)); | |
| // Session will want to write a SETTINGS ack. | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); | |
| result = session.Send(); | |
| EXPECT_EQ(0, result); | |
| EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); | |
| visitor.Clear(); | |
| EXPECT_EQ(0, session.GetHpackEncoderDynamicTableSize()); | |
| const char* kSentinel1 = "arbitrary pointer 1"; | |
| auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); | |
| body1->AppendPayload("This is an example request body."); | |
| body1->EndData(); | |
| int stream_id = | |
| session.SubmitRequest(ToHeaders({{":method", "POST"}, | |
| {":scheme", "http"}, | |
| {":authority", "example.com"}, | |
| {":path", "/this/is/request/one"}}), | |
| std::move(body1), const_cast<char*>(kSentinel1)); | |
| EXPECT_GT(stream_id, 0); | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_EQ(kSentinel1, session.GetStreamUserData(stream_id)); | |
| 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 = session.Send(); | |
| EXPECT_EQ(0, result); | |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS, | |
| spdy::SpdyFrameType::DATA})); | |
| visitor.Clear(); | |
| EXPECT_FALSE(session.want_write()); | |
| // Some data was sent, so the remaining send window size should be less than | |
| // the default. | |
| EXPECT_LT(session.GetStreamSendWindowSize(stream_id), | |
| kInitialFlowControlWindowSize); | |
| EXPECT_GT(session.GetStreamSendWindowSize(stream_id), 0); | |
| // Send window for a nonexistent stream is not available. | |
| EXPECT_EQ(-1, session.GetStreamSendWindowSize(stream_id + 2)); | |
| EXPECT_GT(session.GetHpackEncoderDynamicTableSize(), 0); | |
| stream_id = | |
| session.SubmitRequest(ToHeaders({{":method", "POST"}, | |
| {":scheme", "http"}, | |
| {":authority", "example.com"}, | |
| {":path", "/this/is/request/two"}}), | |
| nullptr, nullptr); | |
| EXPECT_GT(stream_id, 0); | |
| EXPECT_TRUE(session.want_write()); | |
| const char* kSentinel2 = "arbitrary pointer 2"; | |
| EXPECT_EQ(nullptr, session.GetStreamUserData(stream_id)); | |
| session.SetStreamUserData(stream_id, const_cast<char*>(kSentinel2)); | |
| EXPECT_EQ(kSentinel2, session.GetStreamUserData(stream_id)); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5)); | |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0)); | |
| result = session.Send(); | |
| EXPECT_EQ(0, result); | |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS})); | |
| // No data was sent (just HEADERS), so the remaining send window size should | |
| // still be the default. | |
| EXPECT_EQ(session.GetStreamSendWindowSize(stream_id), | |
| kInitialFlowControlWindowSize); | |
| } | |
| // This test exercises the case where the client request body source is read | |
| // blocked. | |
| TEST(OgHttp2SessionTest, ClientSubmitRequestWithReadBlock) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); | |
| EXPECT_FALSE(session.want_write()); | |
| const char* kSentinel1 = "arbitrary pointer 1"; | |
| auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); | |
| TestDataFrameSource* body_ref = body1.get(); | |
| int stream_id = | |
| session.SubmitRequest(ToHeaders({{":method", "POST"}, | |
| {":scheme", "http"}, | |
| {":authority", "example.com"}, | |
| {":path", "/this/is/request/one"}}), | |
| std::move(body1), const_cast<char*>(kSentinel1)); | |
| EXPECT_GT(stream_id, 0); | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_EQ(kSentinel1, session.GetStreamUserData(stream_id)); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); | |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); | |
| int result = session.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({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); | |
| // No data frame, as body1 was read blocked. | |
| visitor.Clear(); | |
| EXPECT_FALSE(session.want_write()); | |
| body_ref->AppendPayload("This is an example request body."); | |
| body_ref->EndData(); | |
| EXPECT_TRUE(session.ResumeStream(stream_id)); | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0)); | |
| result = session.Send(); | |
| EXPECT_EQ(0, result); | |
| EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::DATA})); | |
| EXPECT_FALSE(session.want_write()); | |
| // Stream data is done, so this stream cannot be resumed. | |
| EXPECT_FALSE(session.ResumeStream(stream_id)); | |
| EXPECT_FALSE(session.want_write()); | |
| } | |
| // This test exercises the case where the client request body source is read | |
| // blocked, then ends with an empty DATA frame. | |
| TEST(OgHttp2SessionTest, ClientSubmitRequestEmptyDataWithFin) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); | |
| EXPECT_FALSE(session.want_write()); | |
| const char* kSentinel1 = "arbitrary pointer 1"; | |
| auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); | |
| TestDataFrameSource* body_ref = body1.get(); | |
| int stream_id = | |
| session.SubmitRequest(ToHeaders({{":method", "POST"}, | |
| {":scheme", "http"}, | |
| {":authority", "example.com"}, | |
| {":path", "/this/is/request/one"}}), | |
| std::move(body1), const_cast<char*>(kSentinel1)); | |
| EXPECT_GT(stream_id, 0); | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_EQ(kSentinel1, session.GetStreamUserData(stream_id)); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4)); | |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0)); | |
| int result = session.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({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS})); | |
| // No data frame, as body1 was read blocked. | |
| visitor.Clear(); | |
| EXPECT_FALSE(session.want_write()); | |
| body_ref->EndData(); | |
| EXPECT_TRUE(session.ResumeStream(stream_id)); | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, 0, 0x1, 0)); | |
| result = session.Send(); | |
| EXPECT_EQ(0, result); | |
| EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::DATA})); | |
| EXPECT_FALSE(session.want_write()); | |
| // Stream data is done, so this stream cannot be resumed. | |
| EXPECT_FALSE(session.ResumeStream(stream_id)); | |
| EXPECT_FALSE(session.want_write()); | |
| } | |
| // This test exercises the case where the connection to the peer is write | |
| // blocked. | |
| TEST(OgHttp2SessionTest, ClientSubmitRequestWithWriteBlock) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); | |
| EXPECT_FALSE(session.want_write()); | |
| const char* kSentinel1 = "arbitrary pointer 1"; | |
| auto body1 = absl::make_unique<TestDataFrameSource>(visitor, true); | |
| body1->AppendPayload("This is an example request body."); | |
| body1->EndData(); | |
| int stream_id = | |
| session.SubmitRequest(ToHeaders({{":method", "POST"}, | |
| {":scheme", "http"}, | |
| {":authority", "example.com"}, | |
| {":path", "/this/is/request/one"}}), | |
| std::move(body1), const_cast<char*>(kSentinel1)); | |
| EXPECT_GT(stream_id, 0); | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_EQ(kSentinel1, session.GetStreamUserData(stream_id)); | |
| visitor.set_is_write_blocked(true); | |
| int result = session.Send(); | |
| EXPECT_EQ(0, result); | |
| EXPECT_THAT(visitor.data(), testing::IsEmpty()); | |
| EXPECT_TRUE(session.want_write()); | |
| visitor.set_is_write_blocked(false); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| 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 = session.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({SpdyFrameType::SETTINGS, SpdyFrameType::HEADERS, | |
| SpdyFrameType::DATA})); | |
| EXPECT_FALSE(session.want_write()); | |
| } | |
| TEST(OgHttp2SessionTest, ClientStartShutdown) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kClient}); | |
| EXPECT_FALSE(session.want_write()); | |
| // No-op (except for logging) for a client implementation. | |
| session.StartGracefulShutdown(); | |
| EXPECT_FALSE(session.want_write()); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| int result = session.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({SpdyFrameType::SETTINGS})); | |
| } | |
| TEST(OgHttp2SessionTest, ServerConstruction) { | |
| testing::StrictMock<MockHttp2Visitor> visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); | |
| EXPECT_TRUE(session.want_read()); | |
| EXPECT_FALSE(session.want_write()); | |
| EXPECT_EQ(session.GetRemoteWindowSize(), kInitialFlowControlWindowSize); | |
| EXPECT_TRUE(session.IsServerSession()); | |
| EXPECT_EQ(0, session.GetHighestReceivedStreamId()); | |
| } | |
| TEST(OgHttp2SessionTest, ServerHandlesFrames) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); | |
| EXPECT_EQ(0, session.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([&session, kSentinel1]() { | |
| session.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 = session.ProcessBytes(frames); | |
| EXPECT_EQ(frames.size(), static_cast<size_t>(result)); | |
| EXPECT_EQ(kSentinel1, session.GetStreamUserData(1)); | |
| // The first stream is active and has received some data. | |
| EXPECT_GT(kInitialFlowControlWindowSize, | |
| session.GetStreamReceiveWindowSize(1)); | |
| // Connection receive window is equivalent to the first stream's. | |
| EXPECT_EQ(session.GetReceiveWindowSize(), | |
| session.GetStreamReceiveWindowSize(1)); | |
| // Receive window upper bound is still the initial value. | |
| EXPECT_EQ(kInitialFlowControlWindowSize, | |
| session.GetStreamReceiveWindowLimit(1)); | |
| EXPECT_GT(session.GetHpackDecoderDynamicTableSize(), 0); | |
| // TODO(birenroy): drop stream state when streams are closed. It should no | |
| // longer be possible to set user data. | |
| const char* kSentinel3 = "another arbitrary pointer"; | |
| session.SetStreamUserData(3, const_cast<char*>(kSentinel3)); | |
| EXPECT_EQ(kSentinel3, session.GetStreamUserData(3)); | |
| EXPECT_EQ(session.GetRemoteWindowSize(), | |
| kInitialFlowControlWindowSize + 1000); | |
| EXPECT_EQ(3, session.GetHighestReceivedStreamId()); | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); | |
| // Some bytes should have been serialized. | |
| int send_result = session.Send(); | |
| EXPECT_EQ(0, send_result); | |
| // Initial SETTINGS, SETTINGS ack. | |
| // TODO(birenroy): automatically queue PING acks. | |
| EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS, | |
| spdy::SpdyFrameType::SETTINGS})); | |
| } | |
| // Verifies that a server session enqueues initial SETTINGS before whatever | |
| // frame type is passed to the first invocation of EnqueueFrame(). | |
| TEST(OgHttp2SessionTest, ServerEnqueuesSettingsBeforeOtherFrame) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); | |
| EXPECT_FALSE(session.want_write()); | |
| session.EnqueueFrame(absl::make_unique<spdy::SpdyPingIR>(42)); | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(PING, 0, _, 0x0, 0)); | |
| int result = session.Send(); | |
| EXPECT_EQ(0, result); | |
| EXPECT_THAT(visitor.data(), | |
| EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PING})); | |
| } | |
| // Verifies that if the first call to EnqueueFrame() passes a SETTINGS frame, | |
| // the server session will not enqueue an additional SETTINGS frame. | |
| TEST(OgHttp2SessionTest, ServerEnqueuesSettingsOnce) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); | |
| EXPECT_FALSE(session.want_write()); | |
| session.EnqueueFrame(absl::make_unique<spdy::SpdySettingsIR>()); | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| int result = session.Send(); | |
| EXPECT_EQ(0, result); | |
| EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS})); | |
| } | |
| TEST(OgHttp2SessionTest, ServerSubmitResponse) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); | |
| EXPECT_FALSE(session.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([&session, kSentinel1]() { | |
| session.SetStreamUserData(1, const_cast<char*>(kSentinel1)); | |
| return true; | |
| })); | |
| EXPECT_CALL(visitor, OnEndStream(1)); | |
| const int64_t result = session.ProcessBytes(frames); | |
| EXPECT_EQ(frames.size(), static_cast<size_t>(result)); | |
| EXPECT_EQ(1, session.GetHighestReceivedStreamId()); | |
| EXPECT_EQ(0, session.GetHpackEncoderDynamicTableSize()); | |
| // Server will want to send initial SETTINGS, and a SETTINGS ack. | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); | |
| int send_result = session.Send(); | |
| EXPECT_EQ(0, send_result); | |
| EXPECT_THAT(visitor.data(), | |
| EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS})); | |
| visitor.Clear(); | |
| EXPECT_FALSE(session.want_write()); | |
| // 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("This is an example response body."); | |
| int submit_result = session.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(session.want_write()); | |
| // Stream user data should have been set successfully after receiving headers. | |
| EXPECT_EQ(kSentinel1, session.GetStreamUserData(1)); | |
| session.SetStreamUserData(1, nullptr); | |
| EXPECT_EQ(nullptr, session.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 = session.Send(); | |
| EXPECT_EQ(0, send_result); | |
| EXPECT_THAT(visitor.data(), | |
| EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA})); | |
| EXPECT_FALSE(session.want_write()); | |
| // Some data was sent, so the remaining send window size should be less than | |
| // the default. | |
| EXPECT_LT(session.GetStreamSendWindowSize(1), kInitialFlowControlWindowSize); | |
| EXPECT_GT(session.GetStreamSendWindowSize(1), 0); | |
| // Send window for a nonexistent stream is not available. | |
| EXPECT_EQ(session.GetStreamSendWindowSize(3), -1); | |
| EXPECT_GT(session.GetHpackEncoderDynamicTableSize(), 0); | |
| } | |
| TEST(OgHttp2SessionTest, ServerStartShutdown) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); | |
| EXPECT_FALSE(session.want_write()); | |
| session.StartGracefulShutdown(); | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0)); | |
| int result = session.Send(); | |
| EXPECT_EQ(0, result); | |
| EXPECT_THAT(visitor.data(), | |
| EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); | |
| } | |
| TEST(OgHttp2SessionTest, ServerStartShutdownAfterGoaway) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); | |
| EXPECT_FALSE(session.want_write()); | |
| auto goaway = absl::make_unique<spdy::SpdyGoAwayIR>( | |
| 1, spdy::ERROR_CODE_NO_ERROR, "and don't come back!"); | |
| session.EnqueueFrame(std::move(goaway)); | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0)); | |
| int result = session.Send(); | |
| EXPECT_EQ(0, result); | |
| EXPECT_THAT(visitor.data(), | |
| EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::GOAWAY})); | |
| // No-op, since a GOAWAY has previously been enqueued. | |
| session.StartGracefulShutdown(); | |
| EXPECT_FALSE(session.want_write()); | |
| } | |
| // Tests the case where the server queues trailers after the data stream is | |
| // exhausted. | |
| TEST(OgHttp2SessionTest, ServerSendsTrailers) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); | |
| EXPECT_FALSE(session.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 = session.ProcessBytes(frames); | |
| EXPECT_EQ(frames.size(), static_cast<size_t>(result)); | |
| // Server will want to send initial SETTINGS, and a SETTINGS ack. | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); | |
| int send_result = session.Send(); | |
| EXPECT_EQ(0, send_result); | |
| EXPECT_THAT(visitor.data(), | |
| EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS})); | |
| visitor.Clear(); | |
| EXPECT_FALSE(session.want_write()); | |
| // 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("This is an example response body."); | |
| body1->EndData(); | |
| int submit_result = session.SubmitResponse( | |
| 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}), | |
| std::move(body1)); | |
| EXPECT_EQ(submit_result, 0); | |
| EXPECT_TRUE(session.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 = session.Send(); | |
| EXPECT_EQ(0, send_result); | |
| EXPECT_THAT(visitor.data(), | |
| EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA})); | |
| visitor.Clear(); | |
| EXPECT_FALSE(session.want_write()); | |
| // The body source has been exhausted by the call to Send() above. | |
| // TODO(birenroy): Fix this strange ordering. | |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR)); | |
| int trailer_result = session.SubmitTrailer( | |
| 1, ToHeaders({{"final-status", "a-ok"}, | |
| {"x-comment", "trailers sure are cool"}})); | |
| ASSERT_EQ(trailer_result, 0); | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5)); | |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0)); | |
| send_result = session.Send(); | |
| EXPECT_EQ(0, send_result); | |
| EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS})); | |
| } | |
| // Tests the case where the server queues trailers immediately after headers and | |
| // data, and before any writes have taken place. | |
| TEST(OgHttp2SessionTest, ServerQueuesTrailersWithResponse) { | |
| DataSavingVisitor visitor; | |
| OgHttp2Session session( | |
| visitor, OgHttp2Session::Options{.perspective = Perspective::kServer}); | |
| EXPECT_FALSE(session.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 = session.ProcessBytes(frames); | |
| EXPECT_EQ(frames.size(), static_cast<size_t>(result)); | |
| // Server will want to send initial SETTINGS, and a SETTINGS ack. | |
| EXPECT_TRUE(session.want_write()); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1)); | |
| EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0)); | |
| int send_result = session.Send(); | |
| EXPECT_EQ(0, send_result); | |
| EXPECT_THAT(visitor.data(), | |
| EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS})); | |
| visitor.Clear(); | |
| EXPECT_FALSE(session.want_write()); | |
| // 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("This is an example response body."); | |
| body1->EndData(); | |
| int submit_result = session.SubmitResponse( | |
| 1, ToHeaders({{":status", "200"}, {"x-comment", "Sure, sounds good."}}), | |
| std::move(body1)); | |
| EXPECT_EQ(submit_result, 0); | |
| EXPECT_TRUE(session.want_write()); | |
| // There has not been a call to Send() yet, so neither headers nor body have | |
| // been written. | |
| int trailer_result = session.SubmitTrailer( | |
| 1, ToHeaders({{"final-status", "a-ok"}, | |
| {"x-comment", "trailers sure are cool"}})); | |
| ASSERT_EQ(trailer_result, 0); | |
| EXPECT_TRUE(session.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)); | |
| // TODO(birenroy): Fix this strange ordering. | |
| EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR)); | |
| EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5)); | |
| EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0)); | |
| send_result = session.Send(); | |
| EXPECT_EQ(0, send_result); | |
| EXPECT_THAT(visitor.data(), | |
| EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA, | |
| SpdyFrameType::HEADERS})); | |
| } | |
| } // namespace test | |
| } // namespace adapter | |
| } // namespace http2 |