Add a callback to notify QuicSession whenever a new RTT sample is available.

PiperOrigin-RevId: 933690522
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 26e3eb4..36c2ca0 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -4212,6 +4212,10 @@
   packet_writer_params_.ecn_codepoint = ECN_NOT_ECT;
 }
 
+void QuicConnection::OnRttSampleAvailable(const QuicRttSample& rtt_sample) {
+  visitor_->OnRttSampleAvailable(rtt_sample);
+}
+
 std::unique_ptr<QuicSelfIssuedConnectionIdManager>
 QuicConnection::MakeSelfIssuedConnectionIdManager() {
   QUICHE_DCHECK((perspective_ == Perspective::IS_CLIENT &&
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index c0b27ad..b1816fc 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -186,6 +186,9 @@
   // Called when the connection receives a packet from a migrated client.
   virtual void OnConnectionMigration(AddressChangeType type) = 0;
 
+  // Called when a new RTT sample is available.
+  virtual void OnRttSampleAvailable(const QuicRttSample& rtt_sample) = 0;
+
   // Called when the peer seems unreachable over the current path.
   virtual void OnPathDegrading() = 0;
 
@@ -835,6 +838,7 @@
   void OnPathMtuIncreased(QuicPacketLength packet_size) override;
   void OnInFlightEcnPacketAcked() override;
   void OnInvalidEcnFeedback() override;
+  void OnRttSampleAvailable(const QuicRttSample& rtt_sample) override;
 
   // QuicNetworkBlackholeDetector::Delegate
   void OnPathDegradingDetected() override;
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 0d66c4f..1f28529 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -697,6 +697,7 @@
     EXPECT_CALL(visitor_, ShouldKeepConnectionAlive())
         .WillRepeatedly(Return(false));
     EXPECT_CALL(visitor_, OnCongestionWindowChange(_)).Times(AnyNumber());
+    EXPECT_CALL(visitor_, OnRttSampleAvailable).Times(AnyNumber());
     EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)).Times(AnyNumber());
     EXPECT_CALL(visitor_, MaybeBundleOpportunistically()).Times(AnyNumber());
     EXPECT_CALL(visitor_, GetFlowControlSendWindowSize(_)).Times(AnyNumber());
diff --git a/quiche/quic/core/quic_sent_packet_manager.cc b/quiche/quic/core/quic_sent_packet_manager.cc
index 6b731f0..41ad223 100644
--- a/quiche/quic/core/quic_sent_packet_manager.cc
+++ b/quiche/quic/core/quic_sent_packet_manager.cc
@@ -1065,7 +1065,12 @@
 
   QuicTime::Delta send_delta = ack_receive_time - transmission_info.sent_time;
   const bool min_rtt_available = !rtt_stats_.min_rtt().IsZero();
-  rtt_stats_.UpdateRtt(send_delta, ack_delay_time, ack_receive_time);
+  const bool rtt_updated =
+      rtt_stats_.UpdateRtt(send_delta, ack_delay_time, ack_receive_time);
+  if (rtt_updated && network_change_visitor_ != nullptr) {
+    network_change_visitor_->OnRttSampleAvailable(
+        QuicRttSample{.latest_rtt = rtt_stats_.latest_rtt()});
+  }
 
   if (!min_rtt_available && !rtt_stats_.min_rtt().IsZero()) {
     loss_algorithm_->OnMinRttAvailable();
diff --git a/quiche/quic/core/quic_sent_packet_manager.h b/quiche/quic/core/quic_sent_packet_manager.h
index 42d9e32..a007eba 100644
--- a/quiche/quic/core/quic_sent_packet_manager.h
+++ b/quiche/quic/core/quic_sent_packet_manager.h
@@ -119,6 +119,9 @@
     // Called when an ACK frame with ECN counts has invalid values, or an ACK
     // acknowledges packets with ECN marks and there are no ECN counts.
     virtual void OnInvalidEcnFeedback() = 0;
+
+    // Called when a new RTT sample is available.
+    virtual void OnRttSampleAvailable(const QuicRttSample& rtt_sample) = 0;
   };
 
   // The retransmission timer is a single timer which switches modes depending
diff --git a/quiche/quic/core/quic_sent_packet_manager_test.cc b/quiche/quic/core/quic_sent_packet_manager_test.cc
index d0d3c22..1d04fc3 100644
--- a/quiche/quic/core/quic_sent_packet_manager_test.cc
+++ b/quiche/quic/core/quic_sent_packet_manager_test.cc
@@ -146,6 +146,8 @@
     EXPECT_CALL(*send_algorithm_, OnPacketNeutered(_)).Times(AnyNumber());
     EXPECT_CALL(*network_change_visitor_, OnPathMtuIncreased(1000))
         .Times(AnyNumber());
+    EXPECT_CALL(*network_change_visitor_, OnRttSampleAvailable)
+        .Times(AnyNumber());
     EXPECT_CALL(notifier_, IsFrameOutstanding(_)).WillRepeatedly(Return(true));
     EXPECT_CALL(notifier_, HasUnackedCryptoData())
         .WillRepeatedly(Return(false));
@@ -775,6 +777,22 @@
   EXPECT_EQ(expected_rtt, manager_.GetRttStats()->latest_rtt());
 }
 
+TEST_F(QuicSentPacketManagerTest, OnRttSampleAvailable) {
+  const QuicTime::Delta expected_rtt = QuicTime::Delta::FromMilliseconds(20);
+  SendDataPacket(1);
+  clock_.AdvanceTime(expected_rtt);
+  const QuicTime::Delta ack_delay = QuicTime::Delta::FromMilliseconds(5);
+
+  ExpectAck(1);
+  EXPECT_CALL(*network_change_visitor_,
+              OnRttSampleAvailable(QuicRttSample{expected_rtt}));
+  manager_.OnAckFrameStart(QuicPacketNumber(1), ack_delay, clock_.Now());
+  manager_.OnAckRange(QuicPacketNumber(1), QuicPacketNumber(2));
+  EXPECT_EQ(PACKETS_NEWLY_ACKED,
+            manager_.OnAckFrameEnd(clock_.Now(), QuicPacketNumber(1),
+                                   ENCRYPTION_INITIAL, kEmptyCounts));
+}
+
 TEST_F(QuicSentPacketManagerTest, RttWithInvalidDelta) {
   // Expect that the RTT is equal to the local time elapsed, since the
   // ack_delay_time is larger than the local time elapsed
diff --git a/quiche/quic/core/quic_session.h b/quiche/quic/core/quic_session.h
index 56ac2b0..593bd2d 100644
--- a/quiche/quic/core/quic_session.h
+++ b/quiche/quic/core/quic_session.h
@@ -307,6 +307,7 @@
   void OnCanWrite() override;
   void OnCongestionWindowChange(QuicTime /*now*/) override {}
   void OnConnectionMigration(AddressChangeType /*type*/) override {}
+  void OnRttSampleAvailable(const QuicRttSample& /*rtt_sample*/) override {}
   // Adds a connection level WINDOW_UPDATE frame.
   void OnAckNeedsRetransmittableFrame() override;
   void SendAckFrequency(const QuicAckFrequencyFrame& frame) override;
diff --git a/quiche/quic/core/quic_types.h b/quiche/quic/core/quic_types.h
index 7feba26..1ad2b56 100644
--- a/quiche/quic/core/quic_types.h
+++ b/quiche/quic/core/quic_types.h
@@ -1016,6 +1016,16 @@
 QUICHE_EXPORT std::string QuicPriorityTypeToString(QuicPriorityType type);
 QUICHE_EXPORT std::ostream& operator<<(std::ostream& os, QuicPriorityType type);
 
+// Individual QUIC RTT sample as it is reported to by the application.
+struct QUICHE_EXPORT QuicRttSample {
+  // The time delta between the time that the packet was received, and the time
+  // that the packet was acknowledged, adjusted for the ACK delay (see RFC 9000,
+  // Section 13.2.5).
+  QuicTimeDelta latest_rtt;
+
+  bool operator==(const QuicRttSample& other) const = default;
+};
+
 }  // namespace quic
 
 #endif  // QUICHE_QUIC_CORE_QUIC_TYPES_H_
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h
index 78cfe1f..fd8cbf1 100644
--- a/quiche/quic/test_tools/quic_test_utils.h
+++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -487,6 +487,8 @@
   MOCK_METHOD(void, OnCongestionWindowChange, (QuicTime now), (override));
   MOCK_METHOD(void, OnConnectionMigration, (AddressChangeType type),
               (override));
+  MOCK_METHOD(void, OnRttSampleAvailable, (const QuicRttSample& rtt_sample),
+              (override));
   MOCK_METHOD(void, OnPathDegrading, (), (override));
   MOCK_METHOD(void, OnForwardProgressMadeAfterPathDegrading, (), (override));
   MOCK_METHOD(void, OnForwardProgressMadeAfterFlowLabelChange, (), (override));
@@ -1328,6 +1330,8 @@
   MOCK_METHOD(void, OnPathMtuIncreased, (QuicPacketLength), (override));
   MOCK_METHOD(void, OnInFlightEcnPacketAcked, (), (override));
   MOCK_METHOD(void, OnInvalidEcnFeedback, (), (override));
+  MOCK_METHOD(void, OnRttSampleAvailable, (const QuicRttSample& rtt_sample),
+              (override));
 };
 
 class MockQuicConnectionDebugVisitor : public QuicConnectionDebugVisitor {
diff --git a/quiche/quic/test_tools/simulator/quic_endpoint.h b/quiche/quic/test_tools/simulator/quic_endpoint.h
index fe72ebc..c47a156 100644
--- a/quiche/quic/test_tools/simulator/quic_endpoint.h
+++ b/quiche/quic/test_tools/simulator/quic_endpoint.h
@@ -61,6 +61,7 @@
       const ParsedQuicVersion& /*version*/) override {}
   void OnCongestionWindowChange(QuicTime /*now*/) override {}
   void OnConnectionMigration(AddressChangeType /*type*/) override {}
+  void OnRttSampleAvailable(const QuicRttSample& /*rtt_sample*/) override {}
   void OnPathDegrading() override {}
   void OnForwardProgressMadeAfterPathDegrading() override {}
   void OnForwardProgressMadeAfterFlowLabelChange() override {}