Adds metadata decoding support in the oghttp2 stack.

Also includes some test utility changes to facilitate the new tests.

PiperOrigin-RevId: 389029208
diff --git a/http2/adapter/callback_visitor.cc b/http2/adapter/callback_visitor.cc
index 3ecc064..4381749 100644
--- a/http2/adapter/callback_visitor.cc
+++ b/http2/adapter/callback_visitor.cc
@@ -388,9 +388,6 @@
                                                size_t payload_length) {
   QUICHE_VLOG(1) << "OnBeginMetadataForStream(stream_id=" << stream_id
                  << ", payload_length=" << payload_length << ")";
-  if (callbacks_->on_frame_recv_callback) {
-    callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
-  }
 }
 
 void CallbackVisitor::OnMetadataForStream(Http2StreamId stream_id,
diff --git a/http2/adapter/http2_protocol.h b/http2/adapter/http2_protocol.h
index e25d177..5e892e0 100644
--- a/http2/adapter/http2_protocol.h
+++ b/http2/adapter/http2_protocol.h
@@ -131,8 +131,9 @@
   kServer,
 };
 
-const uint8_t kMetadataFrameType = 0x4d;
-const uint8_t kMetadataEndFlag = 0x04;
+inline constexpr uint8_t kMetadataFrameType = 0x4d;
+inline constexpr uint8_t kMetadataEndFlag = 0x04;
+inline constexpr uint16_t kMetadataExtensionId = 0x4d44;
 
 }  // namespace adapter
 }  // namespace http2
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc
index e08a683..785e476 100644
--- a/http2/adapter/nghttp2_adapter_test.cc
+++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -381,6 +381,7 @@
   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));
@@ -391,6 +392,7 @@
               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));
@@ -1482,6 +1484,69 @@
   EXPECT_EQ(frames.size(), result);
 }
 
+TEST(NgHttp2AdapterTest, ClientSendsMetadataWithContinuation) {
+  DataSavingVisitor visitor;
+  auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+  EXPECT_FALSE(adapter->session().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 size_t result = adapter->ProcessBytes(frames);
+  EXPECT_EQ(frames.size(), result);
+  EXPECT_EQ(TestFrameSequence::MetadataBlockForPayload(
+                "Example connection metadata in multiple frames"),
+            absl::StrJoin(visitor.GetMetadata(0), ""));
+  EXPECT_EQ(TestFrameSequence::MetadataBlockForPayload(
+                "Some stream metadata that's also sent in multiple frames"),
+            absl::StrJoin(visitor.GetMetadata(1), ""));
+}
+
 TEST(NgHttp2AdapterTest, ServerSendsInvalidTrailers) {
   DataSavingVisitor visitor;
   auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
diff --git a/http2/adapter/nghttp2_callbacks.cc b/http2/adapter/nghttp2_callbacks.cc
index 51b2054..4a2221e 100644
--- a/http2/adapter/nghttp2_callbacks.cc
+++ b/http2/adapter/nghttp2_callbacks.cc
@@ -42,6 +42,8 @@
                          header->flags);
   if (header->type == NGHTTP2_DATA) {
     visitor->OnBeginDataForStream(header->stream_id, header->length);
+  } else if (header->type == kMetadataFrameType) {
+    visitor->OnBeginMetadataForStream(header->stream_id, header->length);
   }
   return 0;
 }
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index 7c038ce..803374e 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -187,8 +187,9 @@
   EXPECT_CALL(visitor, OnSettingsEnd());
 
   EXPECT_CALL(visitor, OnFrameHeader(0, _, kMetadataFrameType, 4));
-  // EXPECT_CALL(visitor, OnMetadataForStream(0, _));
-  // EXPECT_CALL(visitor, OnMetadataEndForStream(0));
+  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"));
@@ -197,8 +198,9 @@
               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, OnMetadataForStream(1, _));
-  // EXPECT_CALL(visitor, OnMetadataEndForStream(1));
+  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."));
@@ -687,6 +689,70 @@
   EXPECT_EQ(frames.size(), result);
 }
 
+TEST(OgHttp2AdapterServerTest, ClientSendsMetadataWithContinuation) {
+  DataSavingVisitor visitor;
+  OgHttp2Adapter::Options options{.perspective = Perspective::kServer};
+  auto adapter = OgHttp2Adapter::Create(visitor, options);
+  EXPECT_FALSE(adapter->session().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 size_t result = adapter->ProcessBytes(frames);
+  EXPECT_EQ(frames.size(), result);
+  EXPECT_EQ(TestFrameSequence::MetadataBlockForPayload(
+                "Example connection metadata in multiple frames"),
+            absl::StrJoin(visitor.GetMetadata(0), ""));
+  EXPECT_EQ(TestFrameSequence::MetadataBlockForPayload(
+                "Some stream metadata that's also sent in multiple frames"),
+            absl::StrJoin(visitor.GetMetadata(1), ""));
+}
+
 TEST(OgHttp2AdapterServerTest, ServerSendsInvalidTrailers) {
   DataSavingVisitor visitor;
   OgHttp2Adapter::Options options{.perspective = Perspective::kServer};
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index c4280bd..3a32b2e 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -144,6 +144,7 @@
                                  }),
       options_(options) {
   decoder_.set_visitor(this);
+  decoder_.set_extension_visitor(this);
   if (options_.perspective == Perspective::kServer) {
     remaining_preface_ = {spdy::kHttp2ConnectionHeaderPrefix,
                           spdy::kHttp2ConnectionHeaderPrefixSize};
@@ -650,6 +651,9 @@
 
 void OgHttp2Session::OnSetting(spdy::SpdySettingsId id, uint32_t value) {
   visitor_.OnSetting({id, value});
+  if (id == kMetadataExtensionId) {
+    peer_supports_metadata_ = (value != 0);
+  }
 }
 
 void OgHttp2Session::OnSettingsEnd() {
@@ -748,6 +752,39 @@
   }
 }
 
+bool OgHttp2Session::OnFrameHeader(spdy::SpdyStreamId stream_id, size_t length,
+                                   uint8_t type, uint8_t flags) {
+  if (type == kMetadataFrameType) {
+    QUICHE_DCHECK_EQ(metadata_length_, 0u);
+    visitor_.OnBeginMetadataForStream(stream_id, length);
+    metadata_stream_id_ = stream_id;
+    metadata_length_ = length;
+    end_metadata_ = flags & kMetadataEndFlag;
+    return true;
+  } else {
+    QUICHE_DLOG(INFO) << "Unexpected frame type " << static_cast<int>(type)
+                      << " received by the extension visitor.";
+    return false;
+  }
+}
+
+void OgHttp2Session::OnFramePayload(const char* data, size_t len) {
+  if (metadata_length_ > 0) {
+    QUICHE_DCHECK_LE(len, metadata_length_);
+    visitor_.OnMetadataForStream(metadata_stream_id_,
+                                 absl::string_view(data, len));
+    metadata_length_ -= len;
+    if (metadata_length_ == 0 && end_metadata_) {
+      visitor_.OnMetadataEndForStream(metadata_stream_id_);
+      metadata_stream_id_ = 0;
+      end_metadata_ = false;
+    }
+  } else {
+    QUICHE_DLOG(INFO) << "Unexpected metadata payload for stream "
+                      << metadata_stream_id_;
+  }
+}
+
 void OgHttp2Session::MaybeSetupPreface() {
   if (!queued_preface_) {
     if (options_.perspective == Perspective::kClient) {
diff --git a/http2/adapter/oghttp2_session.h b/http2/adapter/oghttp2_session.h
index 5188828..77b9da7 100644
--- a/http2/adapter/oghttp2_session.h
+++ b/http2/adapter/oghttp2_session.h
@@ -20,7 +20,8 @@
 // This class manages state associated with a single multiplexed HTTP/2 session.
 class QUICHE_EXPORT_PRIVATE OgHttp2Session
     : public Http2Session,
-      public spdy::SpdyFramerVisitorInterface {
+      public spdy::SpdyFramerVisitorInterface,
+      public spdy::ExtensionVisitorInterface {
  public:
   struct QUICHE_EXPORT_PRIVATE Options {
     Perspective perspective = Perspective::kClient;
@@ -152,6 +153,13 @@
   void OnHeaderStatus(Http2StreamId stream_id,
                       Http2VisitorInterface::OnHeaderResult result);
 
+  // Returns true if a recognized extension frame is received.
+  bool OnFrameHeader(spdy::SpdyStreamId stream_id, size_t length, uint8_t type,
+                     uint8_t flags) override;
+
+  // Handles the payload for a recognized extension frame.
+  void OnFramePayload(const char* data, size_t len) override;
+
  private:
   using MetadataSequence = std::vector<std::unique_ptr<MetadataSource>>;
   struct QUICHE_EXPORT_PRIVATE StreamState {
@@ -256,6 +264,8 @@
 
   Http2StreamId next_stream_id_ = 1;
   Http2StreamId highest_received_stream_id_ = 0;
+  Http2StreamId metadata_stream_id_ = 0;
+  size_t metadata_length_ = 0;
   int connection_send_window_ = kInitialFlowControlWindowSize;
   // The initial flow control receive window size for any newly created streams.
   int stream_receive_window_limit_ = kInitialFlowControlWindowSize;
@@ -263,6 +273,8 @@
   Options options_;
   bool received_goaway_ = false;
   bool queued_preface_ = false;
+  bool peer_supports_metadata_ = false;
+  bool end_metadata_ = false;
 
   // Replace this with a stream ID, for multiple GOAWAY support.
   bool queued_goaway_ = false;
diff --git a/http2/adapter/test_frame_sequence.cc b/http2/adapter/test_frame_sequence.cc
index c5058a7..bb1517d 100644
--- a/http2/adapter/test_frame_sequence.cc
+++ b/http2/adapter/test_frame_sequence.cc
@@ -148,17 +148,21 @@
 }
 
 TestFrameSequence& TestFrameSequence::Metadata(Http2StreamId stream_id,
-                                               absl::string_view payload) {
-  // Encode the payload using a header block.
-  spdy::SpdyHeaderBlock block;
-  block["example-payload"] = payload;
-  spdy::HpackEncoder encoder;
-  encoder.DisableCompression();
-  std::string encoded_payload;
-  encoder.EncodeHeaderSet(block, &encoded_payload);
-  frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>(
-      stream_id, kMetadataFrameType, kMetadataEndFlag,
-      std::move(encoded_payload)));
+                                               absl::string_view payload,
+                                               bool multiple_frames) {
+  const std::string encoded_payload = MetadataBlockForPayload(payload);
+  if (multiple_frames) {
+    const size_t pos = encoded_payload.size() / 2;
+    frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>(
+        stream_id, kMetadataFrameType, 0, encoded_payload.substr(0, pos)));
+    frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>(
+        stream_id, kMetadataFrameType, kMetadataEndFlag,
+        encoded_payload.substr(pos)));
+  } else {
+    frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>(
+        stream_id, kMetadataFrameType, kMetadataEndFlag,
+        std::move(encoded_payload)));
+  }
   return *this;
 }
 
@@ -175,6 +179,18 @@
   return result;
 }
 
+std::string TestFrameSequence::MetadataBlockForPayload(
+    absl::string_view payload) {
+  // Encode the payload using a header block.
+  spdy::SpdyHeaderBlock block;
+  block["example-payload"] = payload;
+  spdy::HpackEncoder encoder;
+  encoder.DisableCompression();
+  std::string encoded_payload;
+  encoder.EncodeHeaderSet(block, &encoded_payload);
+  return encoded_payload;
+}
+
 }  // namespace test
 }  // namespace adapter
 }  // namespace http2
diff --git a/http2/adapter/test_frame_sequence.h b/http2/adapter/test_frame_sequence.h
index 4f05756..99740d3 100644
--- a/http2/adapter/test_frame_sequence.h
+++ b/http2/adapter/test_frame_sequence.h
@@ -50,10 +50,13 @@
                               int weight,
                               bool exclusive);
   TestFrameSequence& Metadata(Http2StreamId stream_id,
-                              absl::string_view payload);
+                              absl::string_view payload,
+                              bool multiple_frames = false);
 
   std::string Serialize();
 
+  static std::string MetadataBlockForPayload(absl::string_view);
+
  private:
   std::string preface_;
   std::vector<std::unique_ptr<spdy::SpdyFrameIR>> frames_;
diff --git a/http2/adapter/test_utils.h b/http2/adapter/test_utils.h
index ff7728d..0c18cdb 100644
--- a/http2/adapter/test_utils.h
+++ b/http2/adapter/test_utils.h
@@ -32,6 +32,24 @@
     return to_accept;
   }
 
+  void OnMetadataForStream(Http2StreamId stream_id,
+                           absl::string_view metadata) override {
+    testing::StrictMock<MockHttp2Visitor>::OnMetadataForStream(stream_id,
+                                                               metadata);
+    auto result =
+        metadata_map_.try_emplace(stream_id, std::vector<std::string>());
+    result.first->second.push_back(std::string(metadata));
+  }
+
+  const std::vector<std::string> GetMetadata(Http2StreamId stream_id) {
+    auto it = metadata_map_.find(stream_id);
+    if (it == metadata_map_.end()) {
+      return {};
+    } else {
+      return it->second;
+    }
+  }
+
   const std::string& data() { return data_; }
   void Clear() { data_.clear(); }
 
@@ -42,6 +60,7 @@
 
  private:
   std::string data_;
+  absl::flat_hash_map<Http2StreamId, std::vector<std::string>> metadata_map_;
   size_t send_limit_ = std::numeric_limits<size_t>::max();
   bool is_write_blocked_ = false;
 };