Adds the initial version of NgHttp2Adapter.
This mostly passes calls on the adapter through to the nghttp2 library with some simple type conversion. Based on prior art by diannahu@.
PiperOrigin-RevId: 372668442
diff --git a/http2/adapter/nghttp2_adapter.cc b/http2/adapter/nghttp2_adapter.cc
new file mode 100644
index 0000000..878c040
--- /dev/null
+++ b/http2/adapter/nghttp2_adapter.cc
@@ -0,0 +1,149 @@
+#include "http2/adapter/nghttp2_adapter.h"
+
+#include "absl/algorithm/container.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "http2/adapter/nghttp2_callbacks.h"
+#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
+#include "common/platform/api/quiche_logging.h"
+#include "common/quiche_endian.h"
+
+namespace http2 {
+namespace adapter {
+
+/* static */
+std::unique_ptr<NgHttp2Adapter> NgHttp2Adapter::CreateClientAdapter(
+ Http2VisitorInterface& visitor) {
+ auto adapter = new NgHttp2Adapter(visitor, Perspective::kClient);
+ adapter->Initialize();
+ return absl::WrapUnique(adapter);
+}
+
+/* static */
+std::unique_ptr<NgHttp2Adapter> NgHttp2Adapter::CreateServerAdapter(
+ Http2VisitorInterface& visitor) {
+ auto adapter = new NgHttp2Adapter(visitor, Perspective::kServer);
+ adapter->Initialize();
+ return absl::WrapUnique(adapter);
+}
+
+ssize_t NgHttp2Adapter::ProcessBytes(absl::string_view bytes) {
+ const ssize_t processed_bytes = session_->ProcessBytes(bytes);
+ if (processed_bytes < 0) {
+ visitor_.OnConnectionError();
+ }
+ return processed_bytes;
+}
+
+void NgHttp2Adapter::SubmitSettings(absl::Span<const Http2Setting> settings) {
+ // Submit SETTINGS, converting each Http2Setting to an nghttp2_settings_entry.
+ std::vector<nghttp2_settings_entry> nghttp2_settings;
+ absl::c_transform(settings, std::back_inserter(nghttp2_settings),
+ [](const Http2Setting& setting) {
+ return nghttp2_settings_entry{setting.id, setting.value};
+ });
+ nghttp2_submit_settings(session_->raw_ptr(), NGHTTP2_FLAG_NONE,
+ nghttp2_settings.data(), nghttp2_settings.size());
+}
+
+void NgHttp2Adapter::SubmitPriorityForStream(Http2StreamId stream_id,
+ Http2StreamId parent_stream_id,
+ int weight,
+ bool exclusive) {
+ nghttp2_priority_spec priority_spec;
+ nghttp2_priority_spec_init(&priority_spec, parent_stream_id, weight,
+ static_cast<int>(exclusive));
+ nghttp2_submit_priority(session_->raw_ptr(), NGHTTP2_FLAG_NONE, stream_id,
+ &priority_spec);
+}
+
+void NgHttp2Adapter::SubmitPing(Http2PingId ping_id) {
+ uint8_t opaque_data[8] = {};
+ Http2PingId ping_id_to_serialize = quiche::QuicheEndian::HostToNet64(ping_id);
+ std::memcpy(opaque_data, &ping_id_to_serialize, sizeof(Http2PingId));
+ nghttp2_submit_ping(session_->raw_ptr(), NGHTTP2_FLAG_NONE, opaque_data);
+}
+
+void NgHttp2Adapter::SubmitGoAway(Http2StreamId last_accepted_stream_id,
+ Http2ErrorCode error_code,
+ absl::string_view opaque_data) {
+ nghttp2_submit_goaway(session_->raw_ptr(), NGHTTP2_FLAG_NONE,
+ last_accepted_stream_id,
+ static_cast<uint32_t>(error_code),
+ ToUint8Ptr(opaque_data.data()), opaque_data.size());
+}
+
+void NgHttp2Adapter::SubmitWindowUpdate(Http2StreamId stream_id,
+ int window_increment) {
+ nghttp2_submit_window_update(session_->raw_ptr(), NGHTTP2_FLAG_NONE,
+ stream_id, window_increment);
+}
+
+void NgHttp2Adapter::SubmitMetadata(Http2StreamId stream_id,
+ bool end_metadata) {
+ QUICHE_LOG(DFATAL) << "Not implemented";
+}
+
+std::string NgHttp2Adapter::GetBytesToWrite(absl::optional<size_t> max_bytes) {
+ ssize_t num_bytes = 0;
+ std::string result;
+ do {
+ const uint8_t* data = nullptr;
+ num_bytes = nghttp2_session_mem_send(session_->raw_ptr(), &data);
+ if (num_bytes > 0) {
+ absl::StrAppend(
+ &result,
+ absl::string_view(reinterpret_cast<const char*>(data), num_bytes));
+ } else if (num_bytes < 0) {
+ visitor_.OnConnectionError();
+ }
+ } while (num_bytes > 0);
+ return result;
+}
+
+int NgHttp2Adapter::GetPeerConnectionWindow() const {
+ return session_->GetRemoteWindowSize();
+}
+
+void NgHttp2Adapter::MarkDataConsumedForStream(Http2StreamId stream_id,
+ size_t num_bytes) {
+ int rc = session_->Consume(stream_id, num_bytes);
+ if (rc != 0) {
+ QUICHE_LOG(ERROR) << "Error " << rc << " marking " << num_bytes
+ << " bytes consumed for stream " << stream_id;
+ }
+}
+
+void NgHttp2Adapter::SubmitRst(Http2StreamId stream_id,
+ Http2ErrorCode error_code) {
+ int status =
+ nghttp2_submit_rst_stream(session_->raw_ptr(), NGHTTP2_FLAG_NONE,
+ stream_id, static_cast<uint32_t>(error_code));
+ if (status < 0) {
+ QUICHE_LOG(WARNING) << "Reset stream failed: " << stream_id
+ << " with status code " << status;
+ }
+}
+
+NgHttp2Adapter::NgHttp2Adapter(Http2VisitorInterface& visitor,
+ Perspective perspective)
+ : Http2Adapter(visitor), visitor_(visitor), perspective_(perspective) {}
+
+NgHttp2Adapter::~NgHttp2Adapter() {}
+
+void NgHttp2Adapter::Initialize() {
+ nghttp2_option* options;
+ nghttp2_option_new(&options);
+ // Set some common options for compatibility.
+ nghttp2_option_set_no_closed_streams(options, 1);
+ nghttp2_option_set_no_auto_window_update(options, 1);
+ nghttp2_option_set_max_send_header_block_length(options, 0x2000000);
+ nghttp2_option_set_max_outbound_ack(options, 10000);
+
+ session_ =
+ absl::make_unique<NgHttp2Session>(perspective_, callbacks::Create(),
+ options, static_cast<void*>(&visitor_));
+}
+
+} // namespace adapter
+} // namespace http2
diff --git a/http2/adapter/nghttp2_adapter.h b/http2/adapter/nghttp2_adapter.h
new file mode 100644
index 0000000..13c2ffc
--- /dev/null
+++ b/http2/adapter/nghttp2_adapter.h
@@ -0,0 +1,97 @@
+#ifndef QUICHE_HTTP2_ADAPTER_NGHTTP2_ADAPTER_H_
+#define QUICHE_HTTP2_ADAPTER_NGHTTP2_ADAPTER_H_
+
+#include "http2/adapter/http2_adapter.h"
+#include "http2/adapter/http2_protocol.h"
+#include "http2/adapter/nghttp2_session.h"
+#include "http2/adapter/nghttp2_util.h"
+
+namespace http2 {
+namespace adapter {
+
+class NgHttp2Adapter : public Http2Adapter {
+ public:
+ ~NgHttp2Adapter() override;
+
+ // Creates an adapter that functions as a client.
+ static std::unique_ptr<NgHttp2Adapter> CreateClientAdapter(
+ Http2VisitorInterface& visitor);
+
+ // Creates an adapter that functions as a server.
+ static std::unique_ptr<NgHttp2Adapter> CreateServerAdapter(
+ Http2VisitorInterface& visitor);
+
+ // Processes the incoming |bytes| as HTTP/2 and invokes callbacks on the
+ // |visitor_| as appropriate.
+ ssize_t ProcessBytes(absl::string_view bytes) override;
+
+ // Submits the |settings| to be written to the peer, e.g., as part of the
+ // HTTP/2 connection preface.
+ void SubmitSettings(absl::Span<const Http2Setting> settings) override;
+
+ // Submits a PRIORITY frame for the given stream.
+ void SubmitPriorityForStream(Http2StreamId stream_id,
+ Http2StreamId parent_stream_id,
+ int weight,
+ bool exclusive) override;
+
+ // Submits a PING on the connection. Note that nghttp2 automatically submits
+ // PING acks upon receiving non-ack PINGs from the peer, so callers only use
+ // this method to originate PINGs. See nghttp2_option_set_no_auto_ping_ack().
+ void SubmitPing(Http2PingId ping_id) override;
+
+ // Submits a GOAWAY on the connection. Note that |last_accepted_stream_id|
+ // refers to stream IDs initiated by the peer. For client-side, this last
+ // stream ID must be even (or 0); for server-side, this last stream ID must be
+ // odd (or 0).
+ // TODO(birenroy): Add a graceful shutdown behavior to the API.
+ void SubmitGoAway(Http2StreamId last_accepted_stream_id,
+ Http2ErrorCode error_code,
+ absl::string_view opaque_data) override;
+
+ // Submits a WINDOW_UPDATE for the given stream (a |stream_id| of 0 indicates
+ // a connection-level WINDOW_UPDATE).
+ void SubmitWindowUpdate(Http2StreamId stream_id,
+ int window_increment) override;
+
+ // Submits a METADATA frame for the given stream (a |stream_id| of 0 indicates
+ // connection-level METADATA). If |end_metadata|, the frame will also have the
+ // END_METADATA flag set.
+ void SubmitMetadata(Http2StreamId stream_id, bool end_metadata) override;
+
+ // Returns serialized bytes for writing to the wire. Writes should be
+ // submitted to Nghttp2Adapter first, so that Nghttp2Adapter has data to
+ // serialize and return in this method.
+ std::string GetBytesToWrite(absl::optional<size_t> max_bytes) override;
+
+ // Returns the connection-level flow control window for the peer.
+ int GetPeerConnectionWindow() const override;
+
+ // Marks the given amount of data as consumed for the given stream, which
+ // enables the nghttp2 layer to trigger WINDOW_UPDATEs as appropriate.
+ void MarkDataConsumedForStream(Http2StreamId stream_id,
+ size_t num_bytes) override;
+
+ // Submits a RST_STREAM with the desired |error_code|.
+ void SubmitRst(Http2StreamId stream_id, Http2ErrorCode error_code) override;
+
+ // TODO(b/181586191): Temporary accessor until equivalent functionality is
+ // available in this adapter class.
+ NgHttp2Session& session() { return *session_; }
+
+ private:
+ NgHttp2Adapter(Http2VisitorInterface& visitor, Perspective perspective);
+
+ // Performs any necessary initialization of the underlying HTTP/2 session,
+ // such as preparing initial SETTINGS.
+ void Initialize();
+
+ std::unique_ptr<NgHttp2Session> session_;
+ Http2VisitorInterface& visitor_;
+ Perspective perspective_;
+};
+
+} // namespace adapter
+} // namespace http2
+
+#endif // QUICHE_HTTP2_ADAPTER_NGHTTP2_ADAPTER_H_
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc
new file mode 100644
index 0000000..d7e1fe6
--- /dev/null
+++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -0,0 +1,252 @@
+#include "http2/adapter/nghttp2_adapter.h"
+
+#include "http2/adapter/mock_http2_visitor.h"
+#include "http2/adapter/test_frame_sequence.h"
+#include "http2/adapter/test_utils.h"
+#include "common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+namespace {
+
+using testing::_;
+
+enum FrameType {
+ DATA,
+ HEADERS,
+ PRIORITY,
+ RST_STREAM,
+ SETTINGS,
+ PUSH_PROMISE,
+ PING,
+ GOAWAY,
+ WINDOW_UPDATE,
+};
+
+TEST(NgHttp2AdapterTest, ClientConstruction) {
+ testing::StrictMock<MockHttp2Visitor> visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+ ASSERT_NE(nullptr, adapter);
+ EXPECT_TRUE(adapter->session().want_read());
+ EXPECT_FALSE(adapter->session().want_write());
+}
+
+TEST(NgHttp2AdapterTest, ClientHandlesFrames) {
+ testing::StrictMock<MockHttp2Visitor> visitor;
+ auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+ std::string serialized = adapter->GetBytesToWrite(absl::nullopt);
+ EXPECT_THAT(serialized, testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix));
+
+ 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 ssize_t initial_result = adapter->ProcessBytes(initial_frames);
+ EXPECT_EQ(initial_frames.size(), initial_result);
+
+ EXPECT_EQ(adapter->GetPeerConnectionWindow(),
+ kDefaultInitialStreamWindowSize + 1000);
+ // Some bytes should have been serialized.
+ serialized = adapter->GetBytesToWrite(absl::nullopt);
+ EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::PING}));
+
+ const std::vector<Header> headers1 = {{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"}};
+ const auto nvs1 = GetRequestNghttp2Nvs(headers1);
+
+ const std::vector<Header> headers2 = {{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/two"}};
+ const auto nvs2 = GetRequestNghttp2Nvs(headers2);
+
+ const std::vector<Header> headers3 = {{":method", "GET"},
+ {":scheme", "http"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/three"}};
+ const auto nvs3 = GetRequestNghttp2Nvs(headers3);
+
+ const int32_t stream_id1 =
+ nghttp2_submit_request(adapter->session().raw_ptr(), nullptr, nvs1.data(),
+ nvs1.size(), nullptr, nullptr);
+ ASSERT_GT(stream_id1, 0);
+ QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
+
+ const int32_t stream_id2 =
+ nghttp2_submit_request(adapter->session().raw_ptr(), nullptr, nvs2.data(),
+ nvs2.size(), nullptr, nullptr);
+ ASSERT_GT(stream_id2, 0);
+ QUICHE_LOG(INFO) << "Created stream: " << stream_id2;
+
+ const int32_t stream_id3 =
+ nghttp2_submit_request(adapter->session().raw_ptr(), nullptr, nvs3.data(),
+ nvs3.size(), nullptr, nullptr);
+ ASSERT_GT(stream_id3, 0);
+ QUICHE_LOG(INFO) << "Created stream: " << stream_id3;
+
+ serialized = adapter->GetBytesToWrite(absl::nullopt);
+ EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS,
+ spdy::SpdyFrameType::HEADERS,
+ spdy::SpdyFrameType::HEADERS}));
+
+ 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 ssize_t stream_result = adapter->ProcessBytes(stream_frames);
+ EXPECT_EQ(stream_frames.size(), stream_result);
+
+ // Even though the client recieved a GOAWAY, streams 1 and 5 are still active.
+ EXPECT_TRUE(adapter->session().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::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());
+ // After receiving END_STREAM for 1 and RST_STREAM for 5, the session no
+ // longer expects reads.
+ EXPECT_FALSE(adapter->session().want_read());
+
+ // Client will not have anything else to write.
+ EXPECT_FALSE(adapter->session().want_write());
+ serialized = adapter->GetBytesToWrite(absl::nullopt);
+ EXPECT_THAT(serialized, testing::IsEmpty());
+}
+
+TEST(NgHttp2AdapterTest, ServerConstruction) {
+ testing::StrictMock<MockHttp2Visitor> visitor;
+ auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+ ASSERT_NE(nullptr, adapter);
+ EXPECT_TRUE(adapter->session().want_read());
+ EXPECT_FALSE(adapter->session().want_write());
+}
+
+TEST(NgHttp2AdapterTest, ServerHandlesFrames) {
+ testing::StrictMock<MockHttp2Visitor> visitor;
+ auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+
+ 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;
+
+ // 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));
+ 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 ssize_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+
+ EXPECT_EQ(adapter->GetPeerConnectionWindow(),
+ kDefaultInitialStreamWindowSize + 1000);
+
+ EXPECT_TRUE(adapter->session().want_write());
+ // Some bytes should have been serialized.
+ std::string serialized = adapter->GetBytesToWrite(absl::nullopt);
+ // SETTINGS ack, two PING acks.
+ EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::PING,
+ spdy::SpdyFrameType::PING}));
+}
+
+} // namespace
+} // namespace test
+} // namespace adapter
+} // namespace http2
diff --git a/http2/adapter/nghttp2_session.cc b/http2/adapter/nghttp2_session.cc
index dec4c03..d434b06 100644
--- a/http2/adapter/nghttp2_session.cc
+++ b/http2/adapter/nghttp2_session.cc
@@ -7,7 +7,9 @@
namespace {
void DeleteOptions(nghttp2_option* options) {
- nghttp2_option_del(options);
+ if (options) {
+ nghttp2_option_del(options);
+ }
}
} // namespace
diff --git a/http2/adapter/nghttp2_session.h b/http2/adapter/nghttp2_session.h
index 5dc1ddf..d446a07 100644
--- a/http2/adapter/nghttp2_session.h
+++ b/http2/adapter/nghttp2_session.h
@@ -11,6 +11,7 @@
// A C++ wrapper around common nghttp2_session operations.
class NgHttp2Session : public Http2Session {
public:
+ // Takes ownership of |options|.
NgHttp2Session(Perspective perspective,
nghttp2_session_callbacks_unique_ptr callbacks,
nghttp2_option* options,
diff --git a/http2/adapter/nghttp2_session_test.cc b/http2/adapter/nghttp2_session_test.cc
index 9491412..85f8922 100644
--- a/http2/adapter/nghttp2_session_test.cc
+++ b/http2/adapter/nghttp2_session_test.cc
@@ -26,21 +26,10 @@
WINDOW_UPDATE,
};
-class DataSavingVisitor : public testing::StrictMock<MockHttp2Visitor> {
- public:
- void Save(absl::string_view data) { absl::StrAppend(&data_, data); }
-
- const std::string& data() { return data_; }
- void Clear() { data_.clear(); }
-
- private:
- std::string data_;
-};
-
-ssize_t SaveSessionOutput(nghttp2_session* session,
+ssize_t SaveSessionOutput(nghttp2_session* /* session*/,
const uint8_t* data,
size_t length,
- int flags,
+ int /* flags */,
void* user_data) {
auto visitor = static_cast<DataSavingVisitor*>(user_data);
visitor->Save(ToStringView(data, length));
diff --git a/http2/adapter/test_utils.h b/http2/adapter/test_utils.h
index c899ac0..ef1ae29 100644
--- a/http2/adapter/test_utils.h
+++ b/http2/adapter/test_utils.h
@@ -6,6 +6,7 @@
#include "absl/strings/string_view.h"
#include "http2/adapter/http2_protocol.h"
+#include "http2/adapter/mock_http2_visitor.h"
#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
#include "common/platform/api/quiche_test.h"
#include "spdy/core/spdy_protocol.h"
@@ -14,6 +15,17 @@
namespace adapter {
namespace test {
+class DataSavingVisitor : public testing::StrictMock<MockHttp2Visitor> {
+ public:
+ void Save(absl::string_view data) { absl::StrAppend(&data_, data); }
+
+ const std::string& data() { return data_; }
+ void Clear() { data_.clear(); }
+
+ private:
+ std::string data_;
+};
+
// These matchers check whether a string consists entirely of HTTP/2 frames of
// the specified ordered sequence. This is useful in tests where we want to show
// that one or more particular frame types are serialized for sending to the