Enqueues initial SETTINGS in OgHttp2Adapter when any other frame is queued.
PiperOrigin-RevId: 375703266
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index e45502e..f0f5db0 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -76,6 +76,7 @@
}
void OgHttp2Session::Send() {
+ MaybeSetupPreface();
ssize_t result = std::numeric_limits<ssize_t>::max();
// Flush any serialized prefix.
while (result > 0 && !serialized_prefix_.empty()) {
@@ -235,5 +236,22 @@
return true;
}
+void OgHttp2Session::MaybeSetupPreface() {
+ if (!queued_preface_) {
+ if (options_.perspective == Perspective::kClient) {
+ serialized_prefix_.assign(spdy::kHttp2ConnectionHeaderPrefix,
+ spdy::kHttp2ConnectionHeaderPrefixSize);
+ }
+ // First frame must be a non-ack SETTINGS.
+ if (frames_.empty() ||
+ frames_.front()->frame_type() != spdy::SpdyFrameType::SETTINGS ||
+ reinterpret_cast<spdy::SpdySettingsIR*>(frames_.front().get())
+ ->is_ack()) {
+ frames_.push_front(absl::make_unique<spdy::SpdySettingsIR>());
+ }
+ queued_preface_ = true;
+ }
+}
+
} // namespace adapter
} // namespace http2
diff --git a/http2/adapter/oghttp2_session.h b/http2/adapter/oghttp2_session.h
index bb58085..44186be 100644
--- a/http2/adapter/oghttp2_session.h
+++ b/http2/adapter/oghttp2_session.h
@@ -121,6 +121,9 @@
Http2StreamId stream_id_ = 0;
};
+ // Queues the connection preface, if not already done.
+ void MaybeSetupPreface();
+
// Receives events when inbound frames are parsed.
Http2VisitorInterface& visitor_;
@@ -151,6 +154,7 @@
int max_frame_payload_ = 16384;
Options options_;
bool received_goaway_ = false;
+ bool queued_preface_ = false;
};
} // namespace adapter
diff --git a/http2/adapter/oghttp2_session_test.cc b/http2/adapter/oghttp2_session_test.cc
index ab15569..7259b6e 100644
--- a/http2/adapter/oghttp2_session_test.cc
+++ b/http2/adapter/oghttp2_session_test.cc
@@ -2,6 +2,7 @@
#include "http2/adapter/mock_http2_visitor.h"
#include "http2/adapter/test_frame_sequence.h"
+#include "http2/adapter/test_utils.h"
#include "common/platform/api/quiche_test.h"
namespace http2 {
@@ -9,6 +10,7 @@
namespace test {
namespace {
+using spdy::SpdyFrameType;
using testing::_;
enum FrameType {
@@ -95,6 +97,56 @@
EXPECT_EQ(stream_frames.size(), stream_result);
}
+// Verifies that a client session enqueues initial SETTINGS if Send() is called
+// before any frames are explicitly queued.
+TEST(OgHttp2SessionTest, ClientEnqueuesSettingsOnSend) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kClient});
+ EXPECT_FALSE(session.want_write());
+ session.Send();
+ absl::string_view serialized = visitor.data();
+ EXPECT_THAT(serialized,
+ testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(serialized, EqualsFrames({SpdyFrameType::SETTINGS}));
+}
+
+// Verifies that a client session enqueues initial SETTINGS before whatever
+// frame type is passed to the first invocation of EnqueueFrame().
+TEST(OgHttp2SessionTest, ClientEnqueuesSettingsBeforeOtherFrame) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kClient});
+ EXPECT_FALSE(session.want_write());
+ session.EnqueueFrame(absl::make_unique<spdy::SpdyPingIR>(42));
+ EXPECT_TRUE(session.want_write());
+ session.Send();
+ absl::string_view serialized = visitor.data();
+ EXPECT_THAT(serialized,
+ testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(serialized,
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PING}));
+}
+
+// Verifies that if the first call to EnqueueFrame() passes a SETTINGS frame,
+// the client session will not enqueue an additional SETTINGS frame.
+TEST(OgHttp2SessionTest, ClientEnqueuesSettingsOnce) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kClient});
+ EXPECT_FALSE(session.want_write());
+ session.EnqueueFrame(absl::make_unique<spdy::SpdySettingsIR>());
+ EXPECT_TRUE(session.want_write());
+ session.Send();
+ absl::string_view serialized = visitor.data();
+ EXPECT_THAT(serialized,
+ testing::StartsWith(spdy::kHttp2ConnectionHeaderPrefix));
+ serialized.remove_prefix(strlen(spdy::kHttp2ConnectionHeaderPrefix));
+ EXPECT_THAT(serialized, EqualsFrames({SpdyFrameType::SETTINGS}));
+}
+
TEST(OgHttp2SessionTest, ServerConstruction) {
testing::StrictMock<MockHttp2Visitor> visitor;
OgHttp2Session session(
@@ -174,6 +226,33 @@
kDefaultInitialStreamWindowSize + 1000);
}
+// Verifies that a server session enqueues initial SETTINGS before whatever
+// frame type is passed to the first invocation of EnqueueFrame().
+TEST(OgHttp2SessionTest, ServerEnqueuesSettingsBeforeOtherFrame) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kServer});
+ EXPECT_FALSE(session.want_write());
+ session.EnqueueFrame(absl::make_unique<spdy::SpdyPingIR>(42));
+ EXPECT_TRUE(session.want_write());
+ session.Send();
+ EXPECT_THAT(visitor.data(),
+ EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PING}));
+}
+
+// Verifies that if the first call to EnqueueFrame() passes a SETTINGS frame,
+// the server session will not enqueue an additional SETTINGS frame.
+TEST(OgHttp2SessionTest, ServerEnqueuesSettingsOnce) {
+ DataSavingVisitor visitor;
+ OgHttp2Session session(
+ visitor, OgHttp2Session::Options{.perspective = Perspective::kServer});
+ EXPECT_FALSE(session.want_write());
+ session.EnqueueFrame(absl::make_unique<spdy::SpdySettingsIR>());
+ EXPECT_TRUE(session.want_write());
+ session.Send();
+ EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS}));
+}
+
} // namespace test
} // namespace adapter
} // namespace http2