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