Initial implementation of a Chromium-based Http2Adapter.

This changelist only implements some of the encoding side of things. Decoding will come in a future change.

PiperOrigin-RevId: 364911729
Change-Id: Ifdbc913924faf9d2a89c38847c7f7b528d0d7aa4
diff --git a/http2/adapter/http2_adapter.h b/http2/adapter/http2_adapter.h
index 4ce8a48..5ea6045 100644
--- a/http2/adapter/http2_adapter.h
+++ b/http2/adapter/http2_adapter.h
@@ -19,6 +19,11 @@
 // implementations.
 class Http2Adapter {
  public:
+  enum class Perspective {
+    kClient,
+    kServer,
+  };
+
   Http2Adapter(const Http2Adapter&) = delete;
   Http2Adapter& operator=(const Http2Adapter&) = delete;
 
diff --git a/http2/adapter/oghttp2_adapter.cc b/http2/adapter/oghttp2_adapter.cc
new file mode 100644
index 0000000..2e40bc3
--- /dev/null
+++ b/http2/adapter/oghttp2_adapter.cc
@@ -0,0 +1,196 @@
+#include "http2/adapter/oghttp2_adapter.h"
+
+#include <list>
+
+#include "absl/memory/memory.h"
+#include "http2/adapter/window_manager.h"
+#include "http2/platform/api/http2_string_utils.h"
+#include "spdy/core/spdy_framer.h"
+#include "spdy/core/spdy_protocol.h"
+
+namespace http2 {
+namespace adapter {
+
+namespace {
+
+using spdy::SpdyFrameIR;
+using spdy::SpdyGoAwayIR;
+using spdy::SpdyPingIR;
+using spdy::SpdyPriorityIR;
+using spdy::SpdySettingsIR;
+using spdy::SpdyWindowUpdateIR;
+
+spdy::SpdyErrorCode TranslateErrorCode(Http2ErrorCode code) {
+  switch (code) {
+    case Http2ErrorCode::NO_ERROR:
+      return spdy::ERROR_CODE_NO_ERROR;
+    case Http2ErrorCode::PROTOCOL_ERROR:
+      return spdy::ERROR_CODE_PROTOCOL_ERROR;
+    case Http2ErrorCode::INTERNAL_ERROR:
+      return spdy::ERROR_CODE_INTERNAL_ERROR;
+    case Http2ErrorCode::FLOW_CONTROL_ERROR:
+      return spdy::ERROR_CODE_FLOW_CONTROL_ERROR;
+    case Http2ErrorCode::SETTINGS_TIMEOUT:
+      return spdy::ERROR_CODE_SETTINGS_TIMEOUT;
+    case Http2ErrorCode::STREAM_CLOSED:
+      return spdy::ERROR_CODE_STREAM_CLOSED;
+    case Http2ErrorCode::FRAME_SIZE_ERROR:
+      return spdy::ERROR_CODE_FRAME_SIZE_ERROR;
+    case Http2ErrorCode::REFUSED_STREAM:
+      return spdy::ERROR_CODE_REFUSED_STREAM;
+    case Http2ErrorCode::CANCEL:
+      return spdy::ERROR_CODE_CANCEL;
+    case Http2ErrorCode::COMPRESSION_ERROR:
+      return spdy::ERROR_CODE_COMPRESSION_ERROR;
+    case Http2ErrorCode::CONNECT_ERROR:
+      return spdy::ERROR_CODE_CONNECT_ERROR;
+    case Http2ErrorCode::ENHANCE_YOUR_CALM:
+      return spdy::ERROR_CODE_ENHANCE_YOUR_CALM;
+    case Http2ErrorCode::INADEQUATE_SECURITY:
+      return spdy::ERROR_CODE_INADEQUATE_SECURITY;
+    case Http2ErrorCode::HTTP_1_1_REQUIRED:
+      return spdy::ERROR_CODE_HTTP_1_1_REQUIRED;
+  }
+}
+
+}  // namespace
+
+struct StreamState {
+  WindowManager window_manager;
+};
+
+class OgHttp2Adapter::OgHttp2Session : public Http2Session {
+ public:
+  OgHttp2Session(Http2VisitorInterface& /*visitor*/, Options /*options*/) {}
+  ~OgHttp2Session() override {}
+
+  ssize_t ProcessBytes(absl::string_view bytes) override {
+    SPDY_BUG(oghttp2_process_bytes) << "Not implemented";
+    return 0;
+  }
+
+  int Consume(Http2StreamId stream_id, size_t num_bytes) override {
+    auto it = stream_map_.find(stream_id);
+    if (it == stream_map_.end()) {
+      // TODO(b/181586191): LOG_ERROR rather than SPDY_BUG.
+      SPDY_BUG(stream_consume_notfound)
+          << "Stream " << stream_id << " not found";
+    } else {
+      it->second.window_manager.MarkDataFlushed(num_bytes);
+    }
+    return 0;  // Remove?
+  }
+
+  bool want_read() const override { return false; }
+  bool want_write() const override {
+    return !frames_.empty() || !serialized_prefix_.empty();
+  }
+  int GetRemoteWindowSize() const override {
+    SPDY_BUG(peer_window_not_updated) << "Not implemented";
+    return peer_window_;
+  }
+
+  void EnqueueFrame(std::unique_ptr<spdy::SpdyFrameIR> frame) {
+    frames_.push_back(std::move(frame));
+  }
+
+  std::string GetBytesToWrite(absl::optional<size_t> max_bytes) {
+    const size_t serialized_max =
+        max_bytes ? max_bytes.value() : std::numeric_limits<size_t>::max();
+    std::string serialized = std::move(serialized_prefix_);
+    while (serialized.size() < serialized_max && !frames_.empty()) {
+      spdy::SpdySerializedFrame frame =
+          framer_.SerializeFrame(*frames_.front());
+      Http2StrAppend(&serialized, absl::string_view(frame));
+      frames_.pop_front();
+    }
+    if (serialized.size() > serialized_max) {
+      serialized_prefix_ = serialized.substr(serialized_max);
+      serialized.resize(serialized_max);
+    }
+    return serialized;
+  }
+
+ private:
+  spdy::SpdyFramer framer_{spdy::SpdyFramer::ENABLE_COMPRESSION};
+  absl::flat_hash_map<Http2StreamId, StreamState> stream_map_;
+  std::list<std::unique_ptr<SpdyFrameIR>> frames_;
+  std::string serialized_prefix_;
+  int peer_window_ = 65535;
+};
+
+/* static */
+std::unique_ptr<OgHttp2Adapter> OgHttp2Adapter::Create(
+    Http2VisitorInterface& visitor,
+    Options options) {
+  // Using `new` to access a non-public constructor.
+  return absl::WrapUnique(new OgHttp2Adapter(visitor, std::move(options)));
+}
+
+OgHttp2Adapter::~OgHttp2Adapter() {}
+
+ssize_t OgHttp2Adapter::ProcessBytes(absl::string_view bytes) {
+  return session_->ProcessBytes(bytes);
+}
+
+void OgHttp2Adapter::SubmitSettings(absl::Span<const Http2Setting> settings) {
+  auto settings_ir = absl::make_unique<SpdySettingsIR>();
+  for (const Http2Setting& setting : settings) {
+    settings_ir->AddSetting(setting.id, setting.value);
+  }
+  session_->EnqueueFrame(std::move(settings_ir));
+}
+
+void OgHttp2Adapter::SubmitPriorityForStream(Http2StreamId stream_id,
+                                             Http2StreamId parent_stream_id,
+                                             int weight,
+                                             bool exclusive) {
+  session_->EnqueueFrame(absl::make_unique<SpdyPriorityIR>(
+      stream_id, parent_stream_id, weight, exclusive));
+}
+
+void OgHttp2Adapter::SubmitPing(Http2PingId ping_id) {
+  session_->EnqueueFrame(absl::make_unique<SpdyPingIR>(ping_id));
+}
+
+void OgHttp2Adapter::SubmitGoAway(Http2StreamId last_accepted_stream_id,
+                                  Http2ErrorCode error_code,
+                                  absl::string_view opaque_data) {
+  session_->EnqueueFrame(absl::make_unique<SpdyGoAwayIR>(
+      last_accepted_stream_id, TranslateErrorCode(error_code),
+      std::string(opaque_data)));
+}
+void OgHttp2Adapter::SubmitWindowUpdate(Http2StreamId stream_id,
+                                        int window_increment) {
+  session_->EnqueueFrame(
+      absl::make_unique<SpdyWindowUpdateIR>(stream_id, window_increment));
+}
+
+void OgHttp2Adapter::SubmitMetadata(Http2StreamId stream_id, bool fin) {
+  SPDY_BUG(oghttp2_submit_metadata) << "Not implemented";
+}
+
+std::string OgHttp2Adapter::GetBytesToWrite(absl::optional<size_t> max_bytes) {
+  return session_->GetBytesToWrite(max_bytes);
+}
+
+int OgHttp2Adapter::GetPeerConnectionWindow() const {
+  return session_->GetRemoteWindowSize();
+}
+
+void OgHttp2Adapter::MarkDataConsumedForStream(Http2StreamId stream_id,
+                                               size_t num_bytes) {
+  session_->Consume(stream_id, num_bytes);
+}
+
+const Http2Session& OgHttp2Adapter::session() const {
+  return *session_;
+}
+
+OgHttp2Adapter::OgHttp2Adapter(Http2VisitorInterface& visitor, Options options)
+    : Http2Adapter(visitor),
+      session_(absl::make_unique<OgHttp2Session>(visitor, std::move(options))) {
+}
+
+}  // namespace adapter
+}  // namespace http2
diff --git a/http2/adapter/oghttp2_adapter.h b/http2/adapter/oghttp2_adapter.h
new file mode 100644
index 0000000..1f32cfc
--- /dev/null
+++ b/http2/adapter/oghttp2_adapter.h
@@ -0,0 +1,53 @@
+#ifndef QUICHE_HTTP2_ADAPTER_OGHTTP2_ADAPTER_H_
+#define QUICHE_HTTP2_ADAPTER_OGHTTP2_ADAPTER_H_
+
+#include <memory>
+
+#include "http2/adapter/http2_adapter.h"
+#include "http2/adapter/http2_session.h"
+
+namespace http2 {
+namespace adapter {
+
+class OgHttp2Adapter : public Http2Adapter {
+ public:
+  struct Options {
+    Perspective context;
+  };
+  static std::unique_ptr<OgHttp2Adapter> Create(Http2VisitorInterface& visitor,
+                                                Options options);
+
+  ~OgHttp2Adapter();
+
+  // From Http2Adapter.
+  ssize_t ProcessBytes(absl::string_view bytes) override;
+  void SubmitSettings(absl::Span<const Http2Setting> settings) override;
+  void SubmitPriorityForStream(Http2StreamId stream_id,
+                               Http2StreamId parent_stream_id,
+                               int weight,
+                               bool exclusive) override;
+  void SubmitPing(Http2PingId ping_id) override;
+  void SubmitGoAway(Http2StreamId last_accepted_stream_id,
+                    Http2ErrorCode error_code,
+                    absl::string_view opaque_data) override;
+  void SubmitWindowUpdate(Http2StreamId stream_id,
+                          int window_increment) override;
+  void SubmitMetadata(Http2StreamId stream_id, bool fin) override;
+  std::string GetBytesToWrite(absl::optional<size_t> max_bytes) override;
+  int GetPeerConnectionWindow() const override;
+  void MarkDataConsumedForStream(Http2StreamId stream_id,
+                                 size_t num_bytes) override;
+
+  const Http2Session& session() const;
+
+ private:
+  OgHttp2Adapter(Http2VisitorInterface& visitor, Options options);
+
+  class OgHttp2Session;
+  std::unique_ptr<OgHttp2Session> session_;
+};
+
+}  // namespace adapter
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_ADAPTER_OGHTTP2_ADAPTER_H_
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
new file mode 100644
index 0000000..3c04c06
--- /dev/null
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -0,0 +1,88 @@
+#include "http2/adapter/oghttp2_adapter.h"
+
+#include "http2/adapter/mock_http2_visitor.h"
+#include "http2/adapter/test_utils.h"
+#include "common/platform/api/quiche_test.h"
+#include "spdy/platform/api/spdy_test_helpers.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+namespace {
+
+class OgHttp2AdapterTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    OgHttp2Adapter::Options options;
+    adapter_ = OgHttp2Adapter::Create(http2_visitor_, options);
+  }
+
+  testing::StrictMock<MockHttp2Visitor> http2_visitor_;
+  std::unique_ptr<OgHttp2Adapter> adapter_;
+};
+
+TEST_F(OgHttp2AdapterTest, ProcessBytes) {
+  EXPECT_SPDY_BUG(adapter_->ProcessBytes("fake data"), "Not implemented");
+}
+
+TEST_F(OgHttp2AdapterTest, SubmitMetadata) {
+  EXPECT_SPDY_BUG(adapter_->SubmitMetadata(3, true), "Not implemented");
+}
+
+TEST_F(OgHttp2AdapterTest, GetPeerConnectionWindow) {
+  int peer_window = 0;
+  EXPECT_SPDY_BUG(peer_window = adapter_->GetPeerConnectionWindow(),
+                  "Not implemented");
+  EXPECT_GT(peer_window, 0);
+}
+
+TEST_F(OgHttp2AdapterTest, MarkDataConsumedForStream) {
+  EXPECT_SPDY_BUG(adapter_->MarkDataConsumedForStream(1, 11),
+                  "Stream 1 not found");
+}
+
+TEST_F(OgHttp2AdapterTest, TestSerialize) {
+  EXPECT_FALSE(adapter_->session().want_read());
+  EXPECT_FALSE(adapter_->session().want_write());
+
+  adapter_->SubmitSettings(
+      {{HEADER_TABLE_SIZE, 128}, {MAX_FRAME_SIZE, 128 << 10}});
+  EXPECT_TRUE(adapter_->session().want_write());
+
+  adapter_->SubmitPriorityForStream(3, 1, 255, true);
+  adapter_->SubmitPing(42);
+  adapter_->SubmitGoAway(13, Http2ErrorCode::NO_ERROR, "");
+  adapter_->SubmitWindowUpdate(3, 127);
+  EXPECT_TRUE(adapter_->session().want_write());
+
+  EXPECT_THAT(adapter_->GetBytesToWrite(absl::nullopt),
+              ContainsFrames(
+                  {spdy::SpdyFrameType::SETTINGS, spdy::SpdyFrameType::PRIORITY,
+                   spdy::SpdyFrameType::PING, spdy::SpdyFrameType::GOAWAY,
+                   spdy::SpdyFrameType::WINDOW_UPDATE}));
+  EXPECT_FALSE(adapter_->session().want_write());
+}
+
+TEST_F(OgHttp2AdapterTest, TestPartialSerialize) {
+  EXPECT_FALSE(adapter_->session().want_write());
+
+  adapter_->SubmitSettings(
+      {{HEADER_TABLE_SIZE, 128}, {MAX_FRAME_SIZE, 128 << 10}});
+  adapter_->SubmitGoAway(13, Http2ErrorCode::NO_ERROR, "And don't come back!");
+  adapter_->SubmitPing(42);
+  EXPECT_TRUE(adapter_->session().want_write());
+
+  const std::string first_part = adapter_->GetBytesToWrite(10);
+  EXPECT_TRUE(adapter_->session().want_write());
+  const std::string second_part = adapter_->GetBytesToWrite(absl::nullopt);
+  EXPECT_FALSE(adapter_->session().want_write());
+  EXPECT_THAT(
+      absl::StrCat(first_part, second_part),
+      ContainsFrames({spdy::SpdyFrameType::SETTINGS,
+                      spdy::SpdyFrameType::GOAWAY, spdy::SpdyFrameType::PING}));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace adapter
+}  // namespace http2