Adds functionality to TestFrameSequence to serialize CONTINUATION frames.

Also adds tests demonstrating that CONTINUATION frame events are received correctly in the visitor for both adapter implementations.

PiperOrigin-RevId: 387888536
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc
index 84f3939..21346e2 100644
--- a/http2/adapter/nghttp2_adapter_test.cc
+++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -23,6 +23,7 @@
   PING,
   GOAWAY,
   WINDOW_UPDATE,
+  CONTINUATION,
 };
 
 // This send callback assumes |source|'s pointer is a TestDataSource, and
@@ -1382,6 +1383,42 @@
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS}));
 }
 
+TEST(NgHttp2AdapterTest, ClientSendsContinuation) {
+  DataSavingVisitor visitor;
+  auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+  EXPECT_FALSE(adapter->session().want_write());
+
+  const std::string frames = TestFrameSequence()
+                                 .ClientPreface()
+                                 .Headers(1,
+                                          {{":method", "GET"},
+                                           {":scheme", "https"},
+                                           {":authority", "example.com"},
+                                           {":path", "/this/is/request/one"}},
+                                          /*fin=*/true,
+                                          /*add_continuation=*/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());
+  // Stream 1
+  EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 1));
+  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));
+  EXPECT_CALL(visitor, OnEndStream(1));
+
+  const size_t result = adapter->ProcessBytes(frames);
+  EXPECT_EQ(frames.size(), result);
+}
+
 TEST(NgHttp2AdapterTest, ServerSendsInvalidTrailers) {
   DataSavingVisitor visitor;
   auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index b8b2a3d..401fda4 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -23,6 +23,7 @@
   PING,
   GOAWAY,
   WINDOW_UPDATE,
+  CONTINUATION,
 };
 
 using spdy::SpdyFrameType;
@@ -612,6 +613,43 @@
                             SpdyFrameType::PING}));
 }
 
+TEST(OgHttp2AdapterServerTest, ClientSendsContinuation) {
+  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()
+                                 .Headers(1,
+                                          {{":method", "GET"},
+                                           {":scheme", "https"},
+                                           {":authority", "example.com"},
+                                           {":path", "/this/is/request/one"}},
+                                          /*fin=*/true,
+                                          /*add_continuation=*/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());
+  // Stream 1
+  EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 1));
+  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));
+  EXPECT_CALL(visitor, OnEndStream(1));
+
+  const size_t result = adapter->ProcessBytes(frames);
+  EXPECT_EQ(frames.size(), result);
+}
+
 TEST(OgHttp2AdapterServerTest, ServerSendsInvalidTrailers) {
   DataSavingVisitor visitor;
   OgHttp2Adapter::Options options{.perspective = Perspective::kServer};
diff --git a/http2/adapter/test_frame_sequence.cc b/http2/adapter/test_frame_sequence.cc
index 3b2ac9a..c5058a7 100644
--- a/http2/adapter/test_frame_sequence.cc
+++ b/http2/adapter/test_frame_sequence.cc
@@ -90,24 +90,45 @@
 TestFrameSequence& TestFrameSequence::Headers(
     Http2StreamId stream_id,
     absl::Span<const std::pair<absl::string_view, absl::string_view>> headers,
-    bool fin) {
-  return Headers(stream_id, ToHeaders(headers), fin);
+    bool fin, bool add_continuation) {
+  return Headers(stream_id, ToHeaders(headers), fin, add_continuation);
 }
 
 TestFrameSequence& TestFrameSequence::Headers(Http2StreamId stream_id,
                                               spdy::Http2HeaderBlock block,
-                                              bool fin) {
-  auto headers =
-      absl::make_unique<spdy::SpdyHeadersIR>(stream_id, std::move(block));
-  headers->set_fin(fin);
-  frames_.push_back(std::move(headers));
+                                              bool fin, bool add_continuation) {
+  if (add_continuation) {
+    // The normal intermediate representations don't allow you to represent a
+    // nonterminal HEADERS frame explicitly, so we'll need to use
+    // SpdyUnknownIRs. For simplicity, and in order not to mess up HPACK state,
+    // the payload will be uncompressed.
+    std::string encoded_block;
+    spdy::HpackEncoder encoder;
+    encoder.DisableCompression();
+    encoder.EncodeHeaderSet(block, &encoded_block);
+    const size_t pos = encoded_block.size() / 2;
+    const uint8_t flags = fin ? 0x1 : 0x0;
+    frames_.push_back(absl::make_unique<spdy::SpdyUnknownIR>(
+        stream_id, static_cast<uint8_t>(spdy::SpdyFrameType::HEADERS), flags,
+        encoded_block.substr(0, pos)));
+
+    auto continuation = absl::make_unique<spdy::SpdyContinuationIR>(stream_id);
+    continuation->set_end_headers(true);
+    continuation->take_encoding(encoded_block.substr(pos));
+    frames_.push_back(std::move(continuation));
+  } else {
+    auto headers =
+        absl::make_unique<spdy::SpdyHeadersIR>(stream_id, std::move(block));
+    headers->set_fin(fin);
+    frames_.push_back(std::move(headers));
+  }
   return *this;
 }
 
 TestFrameSequence& TestFrameSequence::Headers(Http2StreamId stream_id,
                                               absl::Span<const Header> headers,
-                                              bool fin) {
-  return Headers(stream_id, ToHeaderBlock(headers), fin);
+                                              bool fin, bool add_continuation) {
+  return Headers(stream_id, ToHeaderBlock(headers), fin, add_continuation);
 }
 
 TestFrameSequence& TestFrameSequence::WindowUpdate(Http2StreamId stream_id,
diff --git a/http2/adapter/test_frame_sequence.h b/http2/adapter/test_frame_sequence.h
index 4496614..4f05756 100644
--- a/http2/adapter/test_frame_sequence.h
+++ b/http2/adapter/test_frame_sequence.h
@@ -37,13 +37,13 @@
   TestFrameSequence& Headers(
       Http2StreamId stream_id,
       absl::Span<const std::pair<absl::string_view, absl::string_view>> headers,
-      bool fin = false);
+      bool fin = false, bool add_continuation = false);
   TestFrameSequence& Headers(Http2StreamId stream_id,
-                             spdy::Http2HeaderBlock block,
-                             bool fin = false);
+                             spdy::Http2HeaderBlock block, bool fin = false,
+                             bool add_continuation = false);
   TestFrameSequence& Headers(Http2StreamId stream_id,
-                             absl::Span<const Header> headers,
-                             bool fin = false);
+                             absl::Span<const Header> headers, bool fin = false,
+                             bool add_continuation = false);
   TestFrameSequence& WindowUpdate(Http2StreamId stream_id, int32_t delta);
   TestFrameSequence& Priority(Http2StreamId stream_id,
                               Http2StreamId parent_stream_id,