blob: 0b570c33064ba7e5b1c1869ca729fbb20141b28b [file] [log] [blame]
#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