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/congestion_control/general_loss_algorithm.h b/quic/core/congestion_control/general_loss_algorithm.h
index ee8ba64..950831e 100644
--- a/quic/core/congestion_control/general_loss_algorithm.h
+++ b/quic/core/congestion_control/general_loss_algorithm.h
@@ -58,6 +58,11 @@
         << "Unexpected call to GeneralLossAlgorithm::OnMinRttAvailable";
   }
 
+  void OnUserAgentIdKnown() override {
+    DCHECK(false)
+        << "Unexpected call to GeneralLossAlgorithm::OnUserAgentIdKnown";
+  }
+
   void OnConnectionClosed() override {
     DCHECK(false)
         << "Unexpected call to GeneralLossAlgorithm::OnConnectionClosed";
diff --git a/quic/core/congestion_control/loss_detection_interface.h b/quic/core/congestion_control/loss_detection_interface.h
index cd2bd72..d5e0190 100644
--- a/quic/core/congestion_control/loss_detection_interface.h
+++ b/quic/core/congestion_control/loss_detection_interface.h
@@ -57,6 +57,8 @@
 
   virtual void OnMinRttAvailable() = 0;
 
+  virtual void OnUserAgentIdKnown() = 0;
+
   virtual void OnConnectionClosed() = 0;
 };
 
diff --git a/quic/core/congestion_control/uber_loss_algorithm.cc b/quic/core/congestion_control/uber_loss_algorithm.cc
index 28463cb..243f563 100644
--- a/quic/core/congestion_control/uber_loss_algorithm.cc
+++ b/quic/core/congestion_control/uber_loss_algorithm.cc
@@ -98,7 +98,8 @@
 }
 
 void UberLossAlgorithm::MaybeStartTuning() {
-  if (tuner_started_ || !tuning_enabled_ || !min_rtt_available_) {
+  if (tuner_started_ || !tuning_enabled_ || !min_rtt_available_ ||
+      !user_agent_known_) {
     return;
   }
 
@@ -127,6 +128,11 @@
   MaybeStartTuning();
 }
 
+void UberLossAlgorithm::OnUserAgentIdKnown() {
+  user_agent_known_ = true;
+  MaybeStartTuning();
+}
+
 void UberLossAlgorithm::OnConnectionClosed() {
   if (tuner_ != nullptr && tuner_started_) {
     tuner_->Finish(tuned_parameters_);
diff --git a/quic/core/congestion_control/uber_loss_algorithm.h b/quic/core/congestion_control/uber_loss_algorithm.h
index 7dacb66..0d1453c 100644
--- a/quic/core/congestion_control/uber_loss_algorithm.h
+++ b/quic/core/congestion_control/uber_loss_algorithm.h
@@ -7,6 +7,7 @@
 
 #include "net/third_party/quiche/src/quic/core/congestion_control/general_loss_algorithm.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_optional.h"
 
 namespace quic {
@@ -72,6 +73,7 @@
       std::unique_ptr<LossDetectionTunerInterface> tuner);
   void OnConfigNegotiated() override;
   void OnMinRttAvailable() override;
+  void OnUserAgentIdKnown() override;
   void OnConnectionClosed() override;
 
   // Sets reordering_shift for all packet number spaces.
@@ -125,6 +127,10 @@
   LossDetectionParameters tuned_parameters_;
   bool tuner_started_ = false;
   bool min_rtt_available_ = false;
+  // If flag is false, set |user_agent_known_| to true, so loss detection tuner
+  // will start once SetFromConfig is called and min rtt is available.
+  bool user_agent_known_ =
+      !GetQuicReloadableFlag(quic_save_user_agent_in_quic_session);
   bool tuning_enabled_ = false;  // Whether tuning is enabled by config.
 };
 
diff --git a/quic/core/congestion_control/uber_loss_algorithm_test.cc b/quic/core/congestion_control/uber_loss_algorithm_test.cc
index 318e730..e663540 100644
--- a/quic/core/congestion_control/uber_loss_algorithm_test.cc
+++ b/quic/core/congestion_control/uber_loss_algorithm_test.cc
@@ -239,6 +239,8 @@
   const QuicPacketCount old_reordering_threshold =
       loss_algorithm_.GetPacketReorderingThreshold();
 
+  loss_algorithm_.OnUserAgentIdKnown();
+
   // Not owned.
   TestLossTuner* test_tuner = new TestLossTuner(
       /*forced_start_result=*/true,
@@ -275,6 +277,8 @@
   const QuicPacketCount old_reordering_threshold =
       loss_algorithm_.GetPacketReorderingThreshold();
 
+  loss_algorithm_.OnUserAgentIdKnown();
+
   // Not owned.
   TestLossTuner* test_tuner = new TestLossTuner(
       /*forced_start_result=*/true,
@@ -309,6 +313,8 @@
   const QuicPacketCount old_reordering_threshold =
       loss_algorithm_.GetPacketReorderingThreshold();
 
+  loss_algorithm_.OnUserAgentIdKnown();
+
   // Not owned.
   TestLossTuner* test_tuner = new TestLossTuner(
       /*forced_start_result=*/false,
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.
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index 7339b57..5cc257d 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -980,6 +980,8 @@
     return anti_amplification_factor_;
   }
 
+  void OnUserAgentIdKnown() { sent_packet_manager_.OnUserAgentIdKnown(); }
+
  protected:
   // Calls cancel() on all the alarms owned by this connection.
   void CancelAllAlarms();
diff --git a/quic/core/quic_crypto_server_stream.cc b/quic/core/quic_crypto_server_stream.cc
index b21745c..6c84067 100644
--- a/quic/core/quic_crypto_server_stream.cc
+++ b/quic/core/quic_crypto_server_stream.cc
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "third_party/boringssl/src/include/openssl/sha.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_arraysize.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h"
@@ -400,6 +401,17 @@
                  nullptr);
     return;
   }
+
+  if (GetQuicReloadableFlag(quic_save_user_agent_in_quic_session)) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_save_user_agent_in_quic_session, 1, 3);
+    quiche::QuicheStringPiece user_agent_id;
+    message.GetStringPiece(quic::kUAID, &user_agent_id);
+    if (!session()->user_agent_id().has_value()) {
+      std::string uaid = user_agent_id.empty() ? "" : user_agent_id.data();
+      session()->SetUserAgentId(std::move(uaid));
+    }
+  }
+
   if (!result->info.server_nonce.empty()) {
     ++num_handshake_messages_with_server_nonces_;
   }
diff --git a/quic/core/quic_sent_packet_manager.h b/quic/core/quic_sent_packet_manager.h
index ce8353d..b0a8352 100644
--- a/quic/core/quic_sent_packet_manager.h
+++ b/quic/core/quic_sent_packet_manager.h
@@ -408,6 +408,8 @@
 
   bool one_rtt_packet_acked() const { return one_rtt_packet_acked_; }
 
+  void OnUserAgentIdKnown() { loss_algorithm_->OnUserAgentIdKnown(); }
+
  private:
   friend class test::QuicConnectionPeer;
   friend class test::QuicSentPacketManagerPeer;
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index 81ba099..8328782 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -33,6 +33,7 @@
 #include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_optional.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
 
 namespace quic {
@@ -473,6 +474,15 @@
     return true;
   }
 
+  const quiche::QuicheOptional<std::string> user_agent_id() const {
+    return user_agent_id_;
+  }
+
+  void SetUserAgentId(std::string user_agent_id) {
+    user_agent_id_ = std::move(user_agent_id);
+    connection()->OnUserAgentIdKnown();
+  }
+
  protected:
   using StreamMap = QuicSmallMap<QuicStreamId, std::unique_ptr<QuicStream>, 10>;
 
@@ -791,6 +801,8 @@
   // list may be a superset of the connection framer's supported versions.
   ParsedQuicVersionVector supported_versions_;
 
+  quiche::QuicheOptional<std::string> user_agent_id_;
+
   // If true, write_blocked_streams_ uses HTTP2 (tree-style) priority write
   // scheduler.
   bool use_http2_priority_write_scheduler_;
diff --git a/quic/core/tls_server_handshaker.cc b/quic/core/tls_server_handshaker.cc
index 057e86d..84ad6e1 100644
--- a/quic/core/tls_server_handshaker.cc
+++ b/quic/core/tls_server_handshaker.cc
@@ -299,6 +299,20 @@
     return false;
   }
   ProcessAdditionalTransportParameters(client_params);
+  if (GetQuicReloadableFlag(quic_save_user_agent_in_quic_session) &&
+      !session()->user_agent_id().has_value()) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_save_user_agent_in_quic_session, 2, 3);
+
+    if (client_params.user_agent_id.has_value()) {
+      session()->SetUserAgentId(client_params.user_agent_id.value());
+    } else if (client_params.google_quic_params) {
+      quiche::QuicheStringPiece user_agent_id;
+      client_params.google_quic_params->GetStringPiece(kUAID, &user_agent_id);
+      if (!user_agent_id.empty()) {
+        session()->SetUserAgentId(user_agent_id.data());
+      }
+    }
+  }
 
   return true;
 }
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h
index 839bb56..686ea3a 100644
--- a/quic/test_tools/quic_test_utils.h
+++ b/quic/test_tools/quic_test_utils.h
@@ -1292,6 +1292,7 @@
 
   MOCK_METHOD(void, OnConfigNegotiated, (), (override));
   MOCK_METHOD(void, OnMinRttAvailable, (), (override));
+  MOCK_METHOD(void, OnUserAgentIdKnown, (), (override));
   MOCK_METHOD(void, OnConnectionClosed, (), (override));
 };