Moves MetadataVisitor and MetadataSerializer to //third_party/spdy/core for reuse in QUICHE.

PiperOrigin-RevId: 381565468
diff --git a/spdy/core/http2_header_block_hpack_listener.h b/spdy/core/http2_header_block_hpack_listener.h
new file mode 100644
index 0000000..19352e5
--- /dev/null
+++ b/spdy/core/http2_header_block_hpack_listener.h
@@ -0,0 +1,47 @@
+#ifndef QUICHE_SPDY_CORE_HTTP2_HEADER_BLOCK_HPACK_LISTENER_H_
+#define QUICHE_SPDY_CORE_HTTP2_HEADER_BLOCK_HPACK_LISTENER_H_
+
+#include "absl/strings/string_view.h"
+#include "http2/hpack/decoder/hpack_decoder_listener.h"
+#include "common/platform/api/quiche_logging.h"
+#include "spdy/core/spdy_header_block.h"
+
+namespace spdy {
+
+// This class simply gathers the key-value pairs emitted by an HpackDecoder in
+// a SpdyHeaderBlock.
+class Http2HeaderBlockHpackListener : public http2::HpackDecoderListener {
+ public:
+  Http2HeaderBlockHpackListener() {}
+
+  void OnHeaderListStart() override {
+    header_block_.clear();
+    hpack_error_ = false;
+  }
+
+  void OnHeader(const std::string& name, const std::string& value) override {
+    header_block_.AppendValueOrAddHeader(name, value);
+  }
+
+  void OnHeaderListEnd() override {}
+
+  void OnHeaderErrorDetected(absl::string_view error_message) override {
+    QUICHE_VLOG(1) << error_message;
+    hpack_error_ = true;
+  }
+
+  SpdyHeaderBlock release_header_block() {
+    SpdyHeaderBlock block = std::move(header_block_);
+    header_block_ = {};
+    return block;
+  }
+  bool hpack_error() const { return hpack_error_; }
+
+ private:
+  SpdyHeaderBlock header_block_;
+  bool hpack_error_ = false;
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_HTTP2_HEADER_BLOCK_HPACK_LISTENER_H_
diff --git a/spdy/core/metadata_extension.cc b/spdy/core/metadata_extension.cc
new file mode 100644
index 0000000..12f6af1
--- /dev/null
+++ b/spdy/core/metadata_extension.cc
@@ -0,0 +1,195 @@
+#include "spdy/core/metadata_extension.h"
+
+#include <list>
+#include <string>
+
+#include "absl/memory/memory.h"
+#include "absl/strings/str_cat.h"
+#include "http2/decoder/decode_buffer.h"
+#include "http2/hpack/decoder/hpack_decoder.h"
+#include "common/platform/api/quiche_bug_tracker.h"
+#include "common/platform/api/quiche_logging.h"
+#include "spdy/core/hpack/hpack_encoder.h"
+#include "spdy/core/http2_header_block_hpack_listener.h"
+
+namespace spdy {
+
+// Non-standard constants related to METADATA frames.
+const SpdySettingsId MetadataVisitor::kMetadataExtensionId = 0x4d44;
+const uint8_t MetadataVisitor::kMetadataFrameType = 0x4d;
+const uint8_t MetadataVisitor::kEndMetadataFlag = 0x4;
+
+namespace {
+
+const size_t kMaxMetadataBlockSize = 1 << 20;  // 1 MB
+
+// This class uses an HpackEncoder to serialize a METADATA block as a series of
+// METADATA frames.
+class MetadataFrameSequence : public MetadataSerializer::FrameSequence {
+ public:
+  MetadataFrameSequence(SpdyStreamId stream_id, spdy::SpdyHeaderBlock payload)
+      : stream_id_(stream_id), payload_(std::move(payload)) {
+    // Metadata should not use HPACK compression.
+    encoder_.DisableCompression();
+    HpackEncoder::Representations r;
+    for (const auto& kv_pair : payload_) {
+      r.push_back(kv_pair);
+    }
+    progressive_encoder_ = encoder_.EncodeRepresentations(r);
+  }
+
+  // Copies are not allowed.
+  MetadataFrameSequence(const MetadataFrameSequence& other) = delete;
+  MetadataFrameSequence& operator=(const MetadataFrameSequence& other) = delete;
+
+  std::unique_ptr<spdy::SpdyFrameIR> Next() override;
+
+ private:
+  SpdyStreamId stream_id_;
+  SpdyHeaderBlock payload_;
+  HpackEncoder encoder_;
+  std::unique_ptr<HpackEncoder::ProgressiveEncoder> progressive_encoder_;
+};
+
+std::unique_ptr<spdy::SpdyFrameIR> MetadataFrameSequence::Next() {
+  if (!progressive_encoder_->HasNext()) {
+    return nullptr;
+  }
+  std::string payload;
+  // METADATA frames obey the HTTP/2 maximum frame size.
+  progressive_encoder_->Next(spdy::kHttp2DefaultFramePayloadLimit, &payload);
+  const bool end_metadata = (!progressive_encoder_->HasNext());
+  const uint8_t flags = end_metadata ? MetadataVisitor::kEndMetadataFlag : 0;
+  return absl::make_unique<spdy::SpdyUnknownIR>(
+      stream_id_, MetadataVisitor::kMetadataFrameType, flags,
+      std::move(payload));
+}
+
+}  // anonymous namespace
+
+struct MetadataVisitor::MetadataPayloadState {
+  MetadataPayloadState(size_t remaining, bool end)
+      : bytes_remaining(remaining), end_metadata(end) {}
+  std::list<std::string> buffer;
+  size_t bytes_remaining;
+  bool end_metadata;
+};
+
+MetadataVisitor::MetadataVisitor(OnCompletePayload on_payload,
+                                 OnMetadataSupport on_support)
+    : on_payload_(std::move(on_payload)),
+      on_support_(std::move(on_support)),
+      peer_supports_metadata_(MetadataSupportState::UNSPECIFIED) {}
+
+MetadataVisitor::~MetadataVisitor() {}
+
+void MetadataVisitor::OnSetting(SpdySettingsId id, uint32_t value) {
+  QUICHE_VLOG(1) << "MetadataVisitor::OnSetting(" << id << ", " << value << ")";
+  if (id == kMetadataExtensionId) {
+    if (value == 0) {
+      const MetadataSupportState previous_state = peer_supports_metadata_;
+      peer_supports_metadata_ = MetadataSupportState::NOT_SUPPORTED;
+      if (previous_state == MetadataSupportState::UNSPECIFIED ||
+          previous_state == MetadataSupportState::SUPPORTED) {
+        on_support_(false);
+      }
+    } else if (value == 1) {
+      const MetadataSupportState previous_state = peer_supports_metadata_;
+      peer_supports_metadata_ = MetadataSupportState::SUPPORTED;
+      if (previous_state == MetadataSupportState::UNSPECIFIED ||
+          previous_state == MetadataSupportState::NOT_SUPPORTED) {
+        on_support_(true);
+      }
+    } else {
+      LOG_EVERY_N_SEC(WARNING, 1)
+          << "Unrecognized value for setting " << id << ": " << value;
+    }
+  }
+}
+
+bool MetadataVisitor::OnFrameHeader(SpdyStreamId stream_id, size_t length,
+                                    uint8_t type, uint8_t flags) {
+  QUICHE_VLOG(1) << "OnFrameHeader(stream_id=" << stream_id
+                 << ", length=" << length << ", type=" << static_cast<int>(type)
+                 << ", flags=" << static_cast<int>(flags);
+  // TODO(birenroy): Consider disabling METADATA handling until our setting
+  // advertising METADATA support has been acked.
+  if (type != kMetadataFrameType) {
+    return false;
+  }
+  auto it = metadata_map_.find(stream_id);
+  if (it == metadata_map_.end()) {
+    auto state = absl::make_unique<MetadataPayloadState>(
+        length, flags & kEndMetadataFlag);
+    auto result = metadata_map_.insert(std::make_pair(stream_id,
+                                                      std::move(state)));
+    QUICHE_BUG_IF(bug_if_2781_1, !result.second) << "Map insertion failed.";
+    it = result.first;
+  } else {
+    QUICHE_BUG_IF(bug_22051_1, it->second->end_metadata)
+        << "Inconsistent metadata payload state!";
+    QUICHE_BUG_IF(bug_if_2781_2, it->second->bytes_remaining > 0)
+        << "Incomplete metadata block!";
+  }
+
+  if (it->second == nullptr) {
+    QUICHE_BUG(bug_2781_3) << "Null metadata payload state!";
+    return false;
+  }
+  current_stream_ = stream_id;
+  it->second->bytes_remaining = length;
+  it->second->end_metadata = (flags & kEndMetadataFlag);
+  return true;
+}
+
+void MetadataVisitor::OnFramePayload(const char* data, size_t len) {
+  QUICHE_VLOG(1) << "OnFramePayload(stream_id=" << current_stream_
+                 << ", len=" << len << ")";
+  auto it = metadata_map_.find(current_stream_);
+  if (it == metadata_map_.end() || it->second == nullptr) {
+    QUICHE_BUG(bug_2781_4) << "Invalid order of operations on MetadataVisitor.";
+  } else {
+    MetadataPayloadState* state = it->second.get();  // For readability.
+    state->buffer.push_back(std::string(data, len));
+    if (len < state->bytes_remaining) {
+      state->bytes_remaining -= len;
+    } else {
+      QUICHE_BUG_IF(bug_22051_2, len > state->bytes_remaining)
+          << "Metadata payload overflow! len: " << len
+          << " bytes_remaining: " << state->bytes_remaining;
+      state->bytes_remaining = 0;
+      if (state->end_metadata) {
+        // The whole process of decoding the HPACK-encoded metadata block,
+        // below, is more cumbersome than it ought to be.
+        spdy::Http2HeaderBlockHpackListener listener;
+        http2::HpackDecoder decoder(&listener, kMaxMetadataBlockSize);
+
+        // If any operations fail, the decode process should be aborted.
+        bool success = decoder.StartDecodingBlock();
+        for (const std::string& slice : state->buffer) {
+          if (!success) {
+            break;
+          }
+          http2::DecodeBuffer buffer(slice.data(), slice.size());
+          success = success && decoder.DecodeFragment(&buffer);
+        }
+        success =
+            success && decoder.EndDecodingBlock() && !listener.hpack_error();
+        if (success) {
+          on_payload_(current_stream_, listener.release_header_block());
+        }
+        // TODO(birenroy): add varz counting metadata decode successes/failures.
+        metadata_map_.erase(it);
+      }
+    }
+  }
+}
+
+std::unique_ptr<MetadataSerializer::FrameSequence>
+MetadataSerializer::FrameSequenceForPayload(SpdyStreamId stream_id,
+                                            MetadataPayload payload) {
+  return absl::make_unique<MetadataFrameSequence>(stream_id,
+                                                  std::move(payload));
+}
+
+}  // namespace spdy
diff --git a/spdy/core/metadata_extension.h b/spdy/core/metadata_extension.h
new file mode 100644
index 0000000..043d6f0
--- /dev/null
+++ b/spdy/core/metadata_extension.h
@@ -0,0 +1,116 @@
+#ifndef QUICHE_SPDY_CORE_METADATA_EXTENSION_H_
+#define QUICHE_SPDY_CORE_METADATA_EXTENSION_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "spdy/core/http2_frame_decoder_adapter.h"
+#include "spdy/core/spdy_header_block.h"
+#include "spdy/core/spdy_protocol.h"
+#include "spdy/core/zero_copy_output_buffer.h"
+
+namespace spdy {
+
+// An implementation of the ExtensionVisitorInterface that can parse
+// METADATA frames. METADATA is a non-standard HTTP/2 extension developed and
+// used internally at Google. A peer advertises support for METADATA by sending
+// a setting with a setting ID of kMetadataExtensionId and a value of 1.
+//
+// Metadata is represented as a HPACK header block with literal encoding.
+class MetadataVisitor : public spdy::ExtensionVisitorInterface {
+ public:
+  using MetadataPayload = spdy::SpdyHeaderBlock;
+
+  static_assert(!std::is_copy_constructible<MetadataPayload>::value,
+                "MetadataPayload should be a move-only type!");
+
+  using OnMetadataSupport = std::function<void(bool)>;
+  using OnCompletePayload =
+      std::function<void(spdy::SpdyStreamId, MetadataPayload)>;
+
+  // The HTTP/2 SETTINGS ID that is used to indicate support for METADATA
+  // frames.
+  static const spdy::SpdySettingsId kMetadataExtensionId;
+
+  // The 8-bit frame type code for a METADATA frame.
+  static const uint8_t kMetadataFrameType;
+
+  // The flag that indicates the end of a logical metadata block. Due to frame
+  // size limits, a single metadata block may be emitted as several HTTP/2
+  // frames.
+  static const uint8_t kEndMetadataFlag;
+
+  // |on_payload| is invoked whenever a complete metadata payload is received.
+  // |on_support| is invoked whenever the peer's advertised support for metadata
+  // changes.
+  MetadataVisitor(OnCompletePayload on_payload, OnMetadataSupport on_support);
+  ~MetadataVisitor() override;
+
+  MetadataVisitor(const MetadataVisitor&) = delete;
+  MetadataVisitor& operator=(const MetadataVisitor&) = delete;
+
+  // Interprets the non-standard setting indicating support for METADATA.
+  void OnSetting(spdy::SpdySettingsId id, uint32_t value) override;
+
+  // Returns true iff |type| indicates a METADATA frame.
+  bool OnFrameHeader(spdy::SpdyStreamId stream_id, size_t length, uint8_t type,
+                     uint8_t flags) override;
+
+  // Consumes a METADATA frame payload. Invokes the registered callback when a
+  // complete payload has been received.
+  void OnFramePayload(const char* data, size_t len) override;
+
+  // Returns true if the peer has advertised support for METADATA via the
+  // appropriate setting.
+  bool PeerSupportsMetadata() const {
+    return peer_supports_metadata_ == MetadataSupportState::SUPPORTED;
+  }
+
+ private:
+  enum class MetadataSupportState : uint8_t {
+    UNSPECIFIED,
+    SUPPORTED,
+    NOT_SUPPORTED,
+  };
+
+  struct MetadataPayloadState;
+
+  using StreamMetadataMap =
+      absl::flat_hash_map<spdy::SpdyStreamId,
+                          std::unique_ptr<MetadataPayloadState>>;
+
+  OnCompletePayload on_payload_;
+  OnMetadataSupport on_support_;
+  StreamMetadataMap metadata_map_;
+  spdy::SpdyStreamId current_stream_;
+  MetadataSupportState peer_supports_metadata_;
+};
+
+// A class that serializes metadata blocks as sequences of frames.
+class MetadataSerializer {
+ public:
+  using MetadataPayload = spdy::SpdyHeaderBlock;
+
+  class FrameSequence {
+   public:
+    virtual ~FrameSequence() {}
+
+    // Returns nullptr once the sequence has been exhausted.
+    virtual std::unique_ptr<spdy::SpdyFrameIR> Next() = 0;
+  };
+
+  MetadataSerializer() {}
+
+  MetadataSerializer(const MetadataSerializer&) = delete;
+  MetadataSerializer& operator=(const MetadataSerializer&) = delete;
+
+  // Returns nullptr on failure.
+  std::unique_ptr<FrameSequence> FrameSequenceForPayload(
+      spdy::SpdyStreamId stream_id, MetadataPayload payload);
+};
+
+}  // namespace spdy
+
+#endif  // QUICHE_SPDY_CORE_METADATA_EXTENSION_H_
diff --git a/spdy/core/metadata_extension_test.cc b/spdy/core/metadata_extension_test.cc
new file mode 100644
index 0000000..07a5ee1
--- /dev/null
+++ b/spdy/core/metadata_extension_test.cc
@@ -0,0 +1,226 @@
+#include "spdy/core/metadata_extension.h"
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/functional/bind_front.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "common/platform/api/quiche_test.h"
+#include "spdy/core/array_output_buffer.h"
+#include "spdy/core/mock_spdy_framer_visitor.h"
+#include "spdy/core/spdy_framer.h"
+#include "spdy/core/spdy_header_block.h"
+#include "spdy/core/spdy_no_op_visitor.h"
+#include "spdy/core/spdy_protocol.h"
+
+namespace spdy {
+namespace test {
+namespace {
+
+using ::absl::bind_front;
+using ::spdy::SpdyFramer;
+using ::spdy::SpdyHeaderBlock;
+using ::spdy::test::MockSpdyFramerVisitor;
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+
+const size_t kBufferSize = 64 * 1024;
+char kBuffer[kBufferSize];
+
+class MetadataExtensionTest : public QuicheTest {
+ protected:
+  MetadataExtensionTest() : test_buffer_(kBuffer, kBufferSize) {}
+
+  void SetUp() override {
+    extension_ = absl::make_unique<MetadataVisitor>(
+        bind_front(&MetadataExtensionTest::OnCompletePayload, this),
+        bind_front(&MetadataExtensionTest::OnMetadataSupport, this));
+  }
+
+  void OnCompletePayload(spdy::SpdyStreamId stream_id,
+                         MetadataVisitor::MetadataPayload payload) {
+    ++received_count_;
+    received_payload_map_.insert(std::make_pair(stream_id, std::move(payload)));
+  }
+
+  void OnMetadataSupport(bool peer_supports_metadata) {
+    EXPECT_EQ(peer_supports_metadata, extension_->PeerSupportsMetadata());
+    received_metadata_support_.push_back(peer_supports_metadata);
+  }
+
+  MetadataSerializer::MetadataPayload PayloadForData(absl::string_view data) {
+    SpdyHeaderBlock block;
+    block["example-payload"] = data;
+    return block;
+  }
+
+  std::unique_ptr<MetadataVisitor> extension_;
+  absl::flat_hash_map<spdy::SpdyStreamId, SpdyHeaderBlock>
+      received_payload_map_;
+  std::vector<bool> received_metadata_support_;
+  size_t received_count_ = 0;
+  spdy::ArrayOutputBuffer test_buffer_;
+};
+
+// This test verifies that the MetadataVisitor is initialized to a state where
+// it believes the peer does not support metadata.
+TEST_F(MetadataExtensionTest, MetadataNotSupported) {
+  EXPECT_FALSE(extension_->PeerSupportsMetadata());
+  EXPECT_THAT(received_metadata_support_, IsEmpty());
+}
+
+// This test verifies that upon receiving a specific setting, the extension
+// realizes that the peer supports metadata.
+TEST_F(MetadataExtensionTest, MetadataSupported) {
+  EXPECT_FALSE(extension_->PeerSupportsMetadata());
+  // 3 is not an appropriate value for the metadata extension key.
+  extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 3);
+  EXPECT_FALSE(extension_->PeerSupportsMetadata());
+  extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1);
+  ASSERT_TRUE(extension_->PeerSupportsMetadata());
+  extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 0);
+  EXPECT_FALSE(extension_->PeerSupportsMetadata());
+  EXPECT_THAT(received_metadata_support_, ElementsAre(true, false));
+}
+
+TEST_F(MetadataExtensionTest, MetadataIgnoredWithoutExtension) {
+  const char kData[] = "some payload";
+  SpdyHeaderBlock payload = PayloadForData(kData);
+
+  extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1);
+  ASSERT_TRUE(extension_->PeerSupportsMetadata());
+
+  MetadataSerializer serializer;
+  auto sequence = serializer.FrameSequenceForPayload(3, std::move(payload));
+  ASSERT_TRUE(sequence != nullptr);
+
+  http2::Http2DecoderAdapter deframer;
+  ::testing::StrictMock<MockSpdyFramerVisitor> visitor;
+  deframer.set_visitor(&visitor);
+
+  // The Return(true) should not be necessary. http://b/36023792
+  EXPECT_CALL(visitor, OnUnknownFrame(3, MetadataVisitor::kMetadataFrameType))
+      .WillOnce(::testing::Return(true));
+
+  SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION);
+  auto frame = sequence->Next();
+  ASSERT_TRUE(frame != nullptr);
+  while (frame != nullptr) {
+    const size_t frame_size = framer.SerializeFrame(*frame, &test_buffer_);
+    ASSERT_GT(frame_size, 0);
+    ASSERT_FALSE(deframer.HasError());
+    ASSERT_EQ(frame_size, test_buffer_.Size());
+    EXPECT_EQ(frame_size, deframer.ProcessInput(kBuffer, frame_size));
+    test_buffer_.Reset();
+    frame = sequence->Next();
+  }
+  EXPECT_FALSE(deframer.HasError());
+  EXPECT_THAT(received_metadata_support_, ElementsAre(true));
+}
+
+// This test verifies that the METADATA frame emitted by a MetadataExtension
+// can be parsed by another SpdyFramer with a MetadataVisitor.
+TEST_F(MetadataExtensionTest, MetadataPayloadEndToEnd) {
+  SpdyHeaderBlock block1;
+  block1["foo"] = "Some metadata value.";
+  SpdyHeaderBlock block2;
+  block2["bar"] =
+      "The color taupe truly represents a triumph of the human spirit over "
+      "adversity.";
+  block2["baz"] =
+      "Or perhaps it represents abject surrender to the implacable and "
+      "incomprehensible forces of the universe.";
+  const absl::string_view binary_payload{"binary\0payload", 14};
+  block2["qux"] = binary_payload;
+  EXPECT_EQ(binary_payload, block2["qux"]);
+  for (const SpdyHeaderBlock& payload_block :
+       {std::move(block1), std::move(block2)}) {
+    extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1);
+    ASSERT_TRUE(extension_->PeerSupportsMetadata());
+
+    MetadataSerializer serializer;
+    auto sequence =
+        serializer.FrameSequenceForPayload(3, payload_block.Clone());
+    ASSERT_TRUE(sequence != nullptr);
+
+    http2::Http2DecoderAdapter deframer;
+    ::spdy::SpdyNoOpVisitor visitor;
+    deframer.set_visitor(&visitor);
+    deframer.set_extension_visitor(extension_.get());
+    SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION);
+    auto frame = sequence->Next();
+    ASSERT_TRUE(frame != nullptr);
+    while (frame != nullptr) {
+      const size_t frame_size = framer.SerializeFrame(*frame, &test_buffer_);
+      ASSERT_GT(frame_size, 0);
+      ASSERT_FALSE(deframer.HasError());
+      ASSERT_EQ(frame_size, test_buffer_.Size());
+      EXPECT_EQ(frame_size, deframer.ProcessInput(kBuffer, frame_size));
+      test_buffer_.Reset();
+      frame = sequence->Next();
+    }
+    EXPECT_EQ(1, received_count_);
+    auto it = received_payload_map_.find(3);
+    ASSERT_TRUE(it != received_payload_map_.end());
+    EXPECT_EQ(payload_block, it->second);
+
+    received_count_ = 0;
+    received_payload_map_.clear();
+  }
+}
+
+// This test verifies that METADATA frames for two different streams can be
+// interleaved and still successfully parsed by another SpdyFramer with a
+// MetadataVisitor.
+TEST_F(MetadataExtensionTest, MetadataPayloadInterleaved) {
+  const std::string kData1 = std::string(65 * 1024, 'a');
+  const std::string kData2 = std::string(65 * 1024, 'b');
+  const SpdyHeaderBlock payload1 = PayloadForData(kData1);
+  const SpdyHeaderBlock payload2 = PayloadForData(kData2);
+
+  extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1);
+  ASSERT_TRUE(extension_->PeerSupportsMetadata());
+
+  MetadataSerializer serializer;
+  auto sequence1 = serializer.FrameSequenceForPayload(3, payload1.Clone());
+  ASSERT_TRUE(sequence1 != nullptr);
+
+  auto sequence2 = serializer.FrameSequenceForPayload(5, payload2.Clone());
+  ASSERT_TRUE(sequence2 != nullptr);
+
+  http2::Http2DecoderAdapter deframer;
+  ::spdy::SpdyNoOpVisitor visitor;
+  deframer.set_visitor(&visitor);
+  deframer.set_extension_visitor(extension_.get());
+
+  SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION);
+  auto frame1 = sequence1->Next();
+  ASSERT_TRUE(frame1 != nullptr);
+  auto frame2 = sequence2->Next();
+  ASSERT_TRUE(frame2 != nullptr);
+  while (frame1 != nullptr || frame2 != nullptr) {
+    for (auto frame : {frame1.get(), frame2.get()}) {
+      if (frame != nullptr) {
+        const size_t frame_size = framer.SerializeFrame(*frame, &test_buffer_);
+        ASSERT_GT(frame_size, 0);
+        ASSERT_FALSE(deframer.HasError());
+        ASSERT_EQ(frame_size, test_buffer_.Size());
+        EXPECT_EQ(frame_size, deframer.ProcessInput(kBuffer, frame_size));
+        test_buffer_.Reset();
+      }
+    }
+    frame1 = sequence1->Next();
+    frame2 = sequence2->Next();
+  }
+  EXPECT_EQ(2, received_count_);
+  auto it = received_payload_map_.find(3);
+  ASSERT_TRUE(it != received_payload_map_.end());
+  EXPECT_EQ(payload1, it->second);
+
+  it = received_payload_map_.find(5);
+  ASSERT_TRUE(it != received_payload_map_.end());
+  EXPECT_EQ(payload2, it->second);
+}
+
+}  // anonymous namespace
+}  // namespace test
+}  // namespace spdy