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