For loss detection tuning, if user agent is available, save it into quicsession. protected by --gfe2_reloadable_flag_quic_save_user_agent_in_quic_session.

PiperOrigin-RevId: 316688269
Change-Id: I2a25beeb081b38ae204a62c14459427341efa5e0
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index abe9399..1be5ad6 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -83,6 +83,7 @@
 
 const char kFooResponseBody[] = "Artichoke hearts make me happy.";
 const char kBarResponseBody[] = "Palm hearts are pretty delicious, also.";
+const char kTestUserAgentId[] = "quic/core/http/end_to_end_test.cc";
 const float kSessionToStreamRatio = 1.5;
 
 // Run all tests with the cross products of all versions.
@@ -217,6 +218,7 @@
                            client_supported_versions_,
                            crypto_test_utils::ProofVerifierForTesting(),
                            std::make_unique<SimpleSessionCache>());
+    client->SetUserAgentID(kTestUserAgentId);
     client->UseWriter(writer);
     if (!pre_shared_key_client_.empty()) {
       client->client()->SetPreSharedKey(pre_shared_key_client_);
@@ -454,6 +456,13 @@
       EXPECT_EQ(0u, server_stats.packets_lost);
     }
     EXPECT_EQ(0u, server_stats.packets_discarded);
+    if (GetQuicReloadableFlag(quic_save_user_agent_in_quic_session)) {
+      EXPECT_EQ(
+          GetServerSession()->user_agent_id().value_or("MissingUserAgent"),
+          kTestUserAgentId);
+    } else {
+      EXPECT_FALSE(GetServerSession()->user_agent_id().has_value());
+    }
     // TODO(ianswett): Restore the check for packets_dropped equals 0.
     // The expect for packets received is equal to packets processed fails
     // due to version negotiation packets.
@@ -4283,6 +4292,12 @@
 
   server_thread_->Pause();
   QuicConfig server_config = *GetServerSession()->config();
+  if (GetQuicReloadableFlag(quic_save_user_agent_in_quic_session)) {
+    EXPECT_EQ(GetServerSession()->user_agent_id().value_or("MissingUserAgent"),
+              kTestUserAgentId);
+  } else {
+    EXPECT_FALSE(GetServerSession()->user_agent_id().has_value());
+  }
   server_thread_->Resume();
   ASSERT_NE(server_config.received_custom_transport_parameters().find(
                 kCustomParameter),
diff --git a/quic/core/http/http_constants.h b/quic/core/http/http_constants.h
index 17afe1b..285d379 100644
--- a/quic/core/http/http_constants.h
+++ b/quic/core/http/http_constants.h
@@ -42,6 +42,7 @@
 // SETTINGS_QPACK_BLOCKED_STREAMS.
 const uint64_t kDefaultMaximumBlockedStreams = 100;
 
+const char kUserAgentHeaderName[] = "user-agent";
 }  // namespace quic
 
 #endif  // QUICHE_QUIC_CORE_HTTP_HTTP_CONSTANTS_H_
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index 51f7c5c..4c979c1 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -529,6 +529,21 @@
 void QuicSpdyStream::OnStreamHeaderList(bool fin,
                                         size_t frame_len,
                                         const QuicHeaderList& header_list) {
+  if (GetQuicReloadableFlag(quic_save_user_agent_in_quic_session)) {
+    if (!spdy_session()->user_agent_id().has_value()) {
+      QUIC_RELOADABLE_FLAG_COUNT_N(quic_save_user_agent_in_quic_session, 3, 3);
+      std::string uaid;
+      for (const auto& kv : header_list) {
+        if (quiche::QuicheTextUtils::ToLower(kv.first) ==
+            kUserAgentHeaderName) {
+          uaid = kv.second;
+          break;
+        }
+      }
+      spdy_session()->SetUserAgentId(std::move(uaid));
+    }
+  }
+
   // TODO(b/134706391): remove |fin| argument.
   // When using Google QUIC, an empty header list indicates that the size limit
   // has been exceeded.