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