blob: 28a143aac811b2d4e7621073fd7ec91bddc72259 [file] [log] [blame]
#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());
// 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);
// Submit another request to ensure the next stream is created.
int stream_id2 =
session.SubmitRequest(ToHeaders({{":method", "GET"},
{":scheme", "http"},
{":authority", "example.com"},
{":path", "/this/is/request/two"}}),
nullptr, nullptr);
EXPECT_EQ(stream_id2, 3);
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(stream_id2, 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(stream_id2, 4, RST_STREAM, 0));
EXPECT_CALL(visitor, OnRstStream(stream_id2, Http2ErrorCode::INTERNAL_ERROR));
EXPECT_CALL(visitor,
OnCloseStream(stream_id2, 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(stream_id2, 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);
// It should no longer be possible to set user data on a closed stream.
const char* kSentinel3 = "another arbitrary pointer";
session.SetStreamUserData(3, const_cast<char*>(kSentinel3));
EXPECT_EQ(nullptr, 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));
EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, _, 0x1));
EXPECT_CALL(visitor, OnFrameSent(PING, 0, _, 0x1, 0));
EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, _, 0x1));
EXPECT_CALL(visitor, OnFrameSent(PING, 0, _, 0x1, 0));
// Some bytes should have been serialized.
int send_result = session.Send();
EXPECT_EQ(0, send_result);
// Initial SETTINGS, SETTINGS ack, and PING acks (for PING IDs 42 and 47).
EXPECT_THAT(visitor.data(),
EqualsFrames(
{spdy::SpdyFrameType::SETTINGS, spdy::SpdyFrameType::SETTINGS,
spdy::SpdyFrameType::PING, spdy::SpdyFrameType::PING}));
}
// 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.
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));
EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR));
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));
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 = session.Send();
EXPECT_EQ(0, send_result);
EXPECT_THAT(visitor.data(),
EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA,
SpdyFrameType::HEADERS}));
}
} // namespace test
} // namespace adapter
} // namespace http2