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,