Change `QuicUnackedPacketMap::NotifyFramesAcked` and `QuicUnackedPacketMap::MaybeAggregateAckedStreamFrame` to take `QuicTransmissionInfo*&` as a input-output parameter.

Both functions can potentially invalidate the existing `QuicTransmissionInfo` parameter, if they end up appending element to the unacked map. With this change, both functions will update the `QuicTransmissionInfo` before they return.

Protected by quic_reloadable_flag_quic_update_transmission_info_on_frame_acked.

PiperOrigin-RevId: 695497393
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h
index b4f0c7b..dbdcb4b 100755
--- a/quiche/common/quiche_feature_flags_list.h
+++ b/quiche/common/quiche_feature_flags_list.h
@@ -53,6 +53,7 @@
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_test_peer_addr_change_after_normalize, false, false, "If true, QuicConnection::ProcessValidatedPacket will use normalized address to test peer address changes.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_testonly_default_false, false, false, "A testonly reloadable flag that will always default to false.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_testonly_default_true, true, true, "A testonly reloadable flag that will always default to true.")
+QUICHE_FLAG(bool, quiche_reloadable_flag_quic_update_transmission_info_on_frame_acked, false, true, "If true, QuicUnackedPacketMap will update transmission info after session_notifier_->OnFrameAcked.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_use_alarm_multiplexer, false, false, "Manages all of the connection alarms via QuicAlarmMultiplexer.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_use_received_client_addresses_cache, true, true, "If true, use a LRU cache to record client addresses of packets received on server's original address.")
 QUICHE_FLAG(bool, quiche_restart_flag_quic_support_ect1, false, false, "When true, allows sending of QUIC packets marked ECT(1). A different flag (TBD) will actually utilize this capability to send ECT(1).")
diff --git a/quiche/quic/core/frames/quic_frame.cc b/quiche/quic/core/frames/quic_frame.cc
index 6002ecd..f798703 100644
--- a/quiche/quic/core/frames/quic_frame.cc
+++ b/quiche/quic/core/frames/quic_frame.cc
@@ -177,6 +177,15 @@
   }
 }
 
+bool HasMessageFrame(const QuicFrames& frames) {
+  for (const QuicFrame& frame : frames) {
+    if (frame.type == MESSAGE_FRAME) {
+      return true;
+    }
+  }
+  return false;
+}
+
 bool IsControlFrame(QuicFrameType type) {
   switch (type) {
     case RST_STREAM_FRAME:
diff --git a/quiche/quic/core/frames/quic_frame.h b/quiche/quic/core/frames/quic_frame.h
index dc99fc4..ffb9f77 100644
--- a/quiche/quic/core/frames/quic_frame.h
+++ b/quiche/quic/core/frames/quic_frame.h
@@ -146,6 +146,9 @@
 QUICHE_EXPORT void RemoveFramesForStream(QuicFrames* frames,
                                          QuicStreamId stream_id);
 
+// Returns true if |frames| contains at least one message frame.
+QUICHE_EXPORT bool HasMessageFrame(const QuicFrames& frames);
+
 // Returns true if |type| is a retransmittable control frame.
 QUICHE_EXPORT bool IsControlFrame(QuicFrameType type);
 
diff --git a/quiche/quic/core/frames/quic_frames_test.cc b/quiche/quic/core/frames/quic_frames_test.cc
index 37f45cc..7179d18 100644
--- a/quiche/quic/core/frames/quic_frames_test.cc
+++ b/quiche/quic/core/frames/quic_frames_test.cc
@@ -889,6 +889,19 @@
   EXPECT_EQ(QuicPacketNumber(49u), queue.Max());
 }
 
+TEST_F(QuicFramesTest, HasMessageFrame) {
+  QuicFrames frames;
+
+  frames.push_back(QuicFrame(QuicHandshakeDoneFrame()));
+  EXPECT_FALSE(HasMessageFrame(frames));
+
+  frames.push_back(
+      QuicFrame(new QuicMessageFrame(1, MemSliceFromString("message"))));
+  EXPECT_TRUE(HasMessageFrame(frames));
+
+  DeleteFrames(&frames);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quiche/quic/core/quic_sent_packet_manager.cc b/quiche/quic/core/quic_sent_packet_manager.cc
index 5c4e897..c713350 100644
--- a/quiche/quic/core/quic_sent_packet_manager.cc
+++ b/quiche/quic/core/quic_sent_packet_manager.cc
@@ -573,10 +573,10 @@
 }
 
 void QuicSentPacketManager::MarkPacketHandled(QuicPacketNumber packet_number,
-                                              QuicTransmissionInfo* info,
                                               QuicTime ack_receive_time,
                                               QuicTime::Delta ack_delay_time,
-                                              QuicTime receive_timestamp) {
+                                              QuicTime receive_timestamp,
+                                              QuicTransmissionInfo*& info) {
   if (info->has_ack_frequency) {
     for (const auto& frame : info->retransmittable_frames) {
       if (frame.type == ACK_FREQUENCY_FRAME) {
@@ -587,12 +587,17 @@
   // Try to aggregate acked stream frames if acked packet is not a
   // retransmission.
   if (info->transmission_type == NOT_RETRANSMISSION) {
-    unacked_packets_.MaybeAggregateAckedStreamFrame(*info, ack_delay_time,
-                                                    receive_timestamp);
+    unacked_packets_.MaybeAggregateAckedStreamFrame(
+        packet_number, ack_delay_time, receive_timestamp, info);
   } else {
     unacked_packets_.NotifyAggregatedStreamFrameAcked(ack_delay_time);
+    if (unacked_packets_.update_transmission_info_on_frame_acked()) {
+      QUIC_RELOADABLE_FLAG_COUNT_N(quic_update_transmission_info_on_frame_acked,
+                                   1, 3);
+      info = unacked_packets_.GetMutableTransmissionInfo(packet_number);
+    }
     const bool new_data_acked = unacked_packets_.NotifyFramesAcked(
-        *info, ack_delay_time, receive_timestamp);
+        packet_number, ack_delay_time, receive_timestamp, info);
     if (!new_data_acked && info->transmission_type != NOT_RETRANSMISSION) {
       // Record as a spurious retransmission if this packet is a
       // retransmission and no new data gets acked.
@@ -1451,9 +1456,9 @@
     }
     unacked_packets_.MaybeUpdateLargestAckedOfPacketNumberSpace(
         packet_number_space, acked_packet.packet_number);
-    MarkPacketHandled(acked_packet.packet_number, info, ack_receive_time,
+    MarkPacketHandled(acked_packet.packet_number, ack_receive_time,
                       last_ack_frame_.ack_delay_time,
-                      acked_packet.receive_timestamp);
+                      acked_packet.receive_timestamp, info);
   }
   // Copy raw ECN counts to last_ack_frame_ so it is logged properly. Validated
   // ECN counts are stored in valid_ecn_counts, and the congestion controller
diff --git a/quiche/quic/core/quic_sent_packet_manager.h b/quiche/quic/core/quic_sent_packet_manager.h
index faebd06..43176c3 100644
--- a/quiche/quic/core/quic_sent_packet_manager.h
+++ b/quiche/quic/core/quic_sent_packet_manager.h
@@ -545,10 +545,14 @@
 
   // Removes the retransmittability and in flight properties from the packet at
   // |info| due to receipt by the peer.
+  // Note: The function may cause the QuicTransmissionInfo for |packet_number|
+  // to move, in that case, |info| will be updated to point to the new
+  // QuicTransmissionInfo when the function returns.
   void MarkPacketHandled(QuicPacketNumber packet_number,
-                         QuicTransmissionInfo* info, QuicTime ack_receive_time,
+                         QuicTime ack_receive_time,
                          QuicTime::Delta ack_delay_time,
-                         QuicTime receive_timestamp);
+                         QuicTime receive_timestamp,
+                         QuicTransmissionInfo*& info);
 
   // Request that |packet_number| be retransmitted after the other pending
   // retransmissions.  Does not add it to the retransmissions if it's already
diff --git a/quiche/quic/core/quic_unacked_packet_map.cc b/quiche/quic/core/quic_unacked_packet_map.cc
index 04d184b..d3e3ed3 100644
--- a/quiche/quic/core/quic_unacked_packet_map.cc
+++ b/quiche/quic/core/quic_unacked_packet_map.cc
@@ -9,13 +9,16 @@
 #include <type_traits>
 #include <utility>
 
+#include "absl/cleanup/cleanup.h"
 #include "absl/container/inlined_vector.h"
+#include "quiche/quic/core/frames/quic_frame.h"
 #include "quiche/quic/core/quic_connection_stats.h"
 #include "quiche/quic/core/quic_packet_number.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_utils.h"
 #include "quiche/quic/platform/api/quic_bug_tracker.h"
 #include "quiche/quic/platform/api/quic_flag_utils.h"
+#include "quiche/common/simple_buffer_allocator.h"
 
 namespace quic {
 
@@ -377,8 +380,10 @@
       // Notify session that the data has been delivered (but do not notify
       // send algorithm).
       // TODO(b/148868195): use NotifyFramesNeutered.
-      NotifyFramesAcked(*it, QuicTime::Delta::Zero(), QuicTime::Zero());
-      QUICHE_DCHECK(!HasRetransmittableFrames(*it));
+      QuicTransmissionInfo* info = &(*it);
+      NotifyFramesAcked(packet_number, QuicTime::Delta::Zero(),
+                        QuicTime::Zero(), info);
+      QUICHE_DCHECK(!HasRetransmittableFrames(*info));
     }
   }
   QUICHE_DCHECK(!supports_multiple_packet_number_spaces_ ||
@@ -402,7 +407,9 @@
       it->state = NEUTERED;
       neutered_packets.push_back(packet_number);
       // TODO(b/148868195): use NotifyFramesNeutered.
-      NotifyFramesAcked(*it, QuicTime::Delta::Zero(), QuicTime::Zero());
+      QuicTransmissionInfo* info = &(*it);
+      NotifyFramesAcked(packet_number, QuicTime::Delta::Zero(),
+                        QuicTime::Zero(), info);
     }
   }
   QUICHE_DCHECK(!supports_multiple_packet_number_spaces() ||
@@ -482,14 +489,35 @@
   session_notifier_ = session_notifier;
 }
 
-bool QuicUnackedPacketMap::NotifyFramesAcked(const QuicTransmissionInfo& info,
+bool QuicUnackedPacketMap::NotifyFramesAcked(QuicPacketNumber packet_number,
                                              QuicTime::Delta ack_delay,
-                                             QuicTime receive_timestamp) {
+                                             QuicTime receive_timestamp,
+                                             QuicTransmissionInfo*& info) {
   if (session_notifier_ == nullptr) {
     return false;
   }
   bool new_data_acked = false;
-  for (const QuicFrame& frame : info.retransmittable_frames) {
+  const QuicFrames* frames = &info->retransmittable_frames;
+  quiche::SimpleBufferAllocator allocator;
+  std::optional<QuicFrames> frames_copy;
+  const bool use_copied_frames = update_transmission_info_on_frame_acked_ &&
+                                 !HasMessageFrame(info->retransmittable_frames);
+
+  if (use_copied_frames) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_update_transmission_info_on_frame_acked,
+                                 2, 3);
+    frames = &frames_copy.emplace(
+        CopyQuicFrames(&allocator, info->retransmittable_frames));
+  }
+
+  absl::Cleanup cleanup = [&]() {
+    if (use_copied_frames) {
+      DeleteFrames(&frames_copy.value());
+      info = GetMutableTransmissionInfo(packet_number);
+    }
+  };
+
+  for (const QuicFrame& frame : *frames) {
     if (session_notifier_->OnFrameAcked(frame, ack_delay, receive_timestamp)) {
       new_data_acked = true;
     }
@@ -510,12 +538,32 @@
 }
 
 void QuicUnackedPacketMap::MaybeAggregateAckedStreamFrame(
-    const QuicTransmissionInfo& info, QuicTime::Delta ack_delay,
-    QuicTime receive_timestamp) {
+    QuicPacketNumber packet_number, QuicTime::Delta ack_delay,
+    QuicTime receive_timestamp, QuicTransmissionInfo*& info) {
   if (session_notifier_ == nullptr) {
     return;
   }
-  for (const auto& frame : info.retransmittable_frames) {
+  const QuicFrames* frames = &info->retransmittable_frames;
+  quiche::SimpleBufferAllocator allocator;
+  std::optional<QuicFrames> frames_copy;
+  const bool use_copied_frames = update_transmission_info_on_frame_acked_ &&
+                                 !HasMessageFrame(info->retransmittable_frames);
+
+  if (use_copied_frames) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_update_transmission_info_on_frame_acked,
+                                 3, 3);
+    frames = &frames_copy.emplace(
+        CopyQuicFrames(&allocator, info->retransmittable_frames));
+  }
+
+  absl::Cleanup cleanup = [&]() {
+    if (use_copied_frames) {
+      DeleteFrames(&frames_copy.value());
+      info = GetMutableTransmissionInfo(packet_number);
+    }
+  };
+
+  for (const auto& frame : *frames) {
     // Determine whether acked stream frame can be aggregated.
     const bool can_aggregate =
         frame.type == STREAM_FRAME &&
diff --git a/quiche/quic/core/quic_unacked_packet_map.h b/quiche/quic/core/quic_unacked_packet_map.h
index 74d74af..52c6103 100644
--- a/quiche/quic/core/quic_unacked_packet_map.h
+++ b/quiche/quic/core/quic_unacked_packet_map.h
@@ -51,10 +51,14 @@
   // Returns true if the packet |packet_number| is unacked.
   bool IsUnacked(QuicPacketNumber packet_number) const;
 
-  // Notifies session_notifier that frames have been acked. Returns true if any
-  // new data gets acked, returns false otherwise.
-  bool NotifyFramesAcked(const QuicTransmissionInfo& info,
-                         QuicTime::Delta ack_delay, QuicTime receive_timestamp);
+  // Notifies session_notifier that frames in |packet_number| have been acked.
+  // Returns true if any new data gets acked, returns false otherwise.
+  // Note: The function may cause the QuicTransmissionInfo for |packet_number|
+  // to move, in that case, |info| will be updated to point to the new
+  // QuicTransmissionInfo when the function returns.
+  bool NotifyFramesAcked(QuicPacketNumber packet_number,
+                         QuicTime::Delta ack_delay, QuicTime receive_timestamp,
+                         QuicTransmissionInfo*& info);
 
   // Notifies session_notifier that frames in |info| are considered as lost.
   void NotifyFramesLost(const QuicTransmissionInfo& info,
@@ -186,13 +190,21 @@
   // Try to aggregate acked contiguous stream frames. For noncontiguous stream
   // frames or control frames, notify the session notifier they get acked
   // immediately.
-  void MaybeAggregateAckedStreamFrame(const QuicTransmissionInfo& info,
+  // Note: The function may cause the QuicTransmissionInfo for |packet_number|
+  // to move, in that case, |info| will be updated to point to the new
+  // QuicTransmissionInfo when the function returns.
+  void MaybeAggregateAckedStreamFrame(QuicPacketNumber packet_number,
                                       QuicTime::Delta ack_delay,
-                                      QuicTime receive_timestamp);
+                                      QuicTime receive_timestamp,
+                                      QuicTransmissionInfo*& info);
 
   // Notify the session notifier of any stream data aggregated in
   // aggregated_stream_frame_.  No effect if the stream frame has an invalid
   // stream id.
+  // Note: If the caller holds a reference to a QuicTransmissionInfo stored in
+  // unacked_packets_, the reference may be invalidated by this call, so after
+  // the call, caller should use GetTransmissionInfo() or
+  // GetMutableTransmissionInfo() to get a new reference.
   void NotifyAggregatedStreamFrameAcked(QuicTime::Delta ack_delay);
 
   // Returns packet number space that |packet_number| belongs to. Please use
@@ -259,6 +271,10 @@
         ", packets_in_flight: ", packets_in_flight_, "}");
   }
 
+  bool update_transmission_info_on_frame_acked() const {
+    return update_transmission_info_on_frame_acked_;
+  }
+
  private:
   friend class test::QuicUnackedPacketMapPeer;
 
@@ -332,6 +348,9 @@
 
   // Latched value of the quic_simple_inflight_time flag.
   bool simple_inflight_time_;
+
+  const bool update_transmission_info_on_frame_acked_ =
+      GetQuicReloadableFlag(quic_update_transmission_info_on_frame_acked);
 };
 
 }  // namespace quic
diff --git a/quiche/quic/core/quic_unacked_packet_map_test.cc b/quiche/quic/core/quic_unacked_packet_map_test.cc
index 0a3d8b8..ce2f413 100644
--- a/quiche/quic/core/quic_unacked_packet_map_test.cc
+++ b/quiche/quic/core/quic_unacked_packet_map_test.cc
@@ -4,7 +4,10 @@
 
 #include "quiche/quic/core/quic_unacked_packet_map.h"
 
+#include <stdbool.h>
+
 #include <cstddef>
+#include <cstdint>
 #include <limits>
 #include <vector>
 
@@ -18,6 +21,7 @@
 #include "quiche/quic/test_tools/quic_unacked_packet_map_peer.h"
 
 using testing::_;
+using testing::Invoke;
 using testing::Return;
 using testing::StrictMock;
 
@@ -50,15 +54,31 @@
 
   SerializedPacket CreateRetransmittablePacketForStream(
       uint64_t packet_number, QuicStreamId stream_id) {
+    return CreateRetransmittablePacketForStream(packet_number, stream_id,
+                                                /*fin=*/false, /*offset=*/0,
+                                                /*data_length=*/0);
+  }
+
+  SerializedPacket CreateRetransmittablePacketForStream(
+      uint64_t packet_number, QuicStreamId stream_id, bool fin,
+      QuicStreamOffset offset, QuicPacketLength data_length) {
     SerializedPacket packet(QuicPacketNumber(packet_number),
                             PACKET_1BYTE_PACKET_NUMBER, nullptr, kDefaultLength,
                             false, false);
-    QuicStreamFrame frame;
-    frame.stream_id = stream_id;
+    QuicStreamFrame frame(stream_id, fin, offset, data_length);
     packet.retransmittable_frames.push_back(QuicFrame(frame));
     return packet;
   }
 
+  SerializedPacket CreateRetransmittablePacket(
+      uint64_t packet_number, QuicFrames retransmittable_frames) {
+    SerializedPacket packet(QuicPacketNumber(packet_number),
+                            PACKET_1BYTE_PACKET_NUMBER, nullptr, kDefaultLength,
+                            false, false);
+    packet.retransmittable_frames = std::move(retransmittable_frames);
+    return packet;
+  }
+
   SerializedPacket CreateNonRetransmittablePacket(uint64_t packet_number) {
     return SerializedPacket(QuicPacketNumber(packet_number),
                             PACKET_1BYTE_PACKET_NUMBER, nullptr, kDefaultLength,
@@ -460,37 +480,50 @@
   EXPECT_CALL(notifier_, OnFrameAcked(_, _, _)).Times(0);
   unacked_packets_.NotifyAggregatedStreamFrameAcked(QuicTime::Delta::Zero());
 
-  QuicTransmissionInfo info1;
-  QuicStreamFrame stream_frame1(3, false, 0, 100);
-  info1.retransmittable_frames.push_back(QuicFrame(stream_frame1));
+  SerializedPacket packet1(
+      CreateRetransmittablePacketForStream(1, 3, false, 0, 100));
+  unacked_packets_.AddSentPacket(&packet1, NOT_RETRANSMISSION, now_, true, true,
+                                 ECN_NOT_ECT);
 
-  QuicTransmissionInfo info2;
-  QuicStreamFrame stream_frame2(3, false, 100, 100);
-  info2.retransmittable_frames.push_back(QuicFrame(stream_frame2));
+  SerializedPacket packet2(
+      CreateRetransmittablePacketForStream(2, 3, false, 100, 100));
+  unacked_packets_.AddSentPacket(&packet2, NOT_RETRANSMISSION, now_, true, true,
+                                 ECN_NOT_ECT);
 
-  QuicTransmissionInfo info3;
-  QuicStreamFrame stream_frame3(3, false, 200, 100);
-  info3.retransmittable_frames.push_back(QuicFrame(stream_frame3));
+  SerializedPacket packet3(
+      CreateRetransmittablePacketForStream(3, 3, false, 200, 100));
+  unacked_packets_.AddSentPacket(&packet3, NOT_RETRANSMISSION, now_, true, true,
+                                 ECN_NOT_ECT);
 
-  QuicTransmissionInfo info4;
-  QuicStreamFrame stream_frame4(3, true, 300, 0);
-  info4.retransmittable_frames.push_back(QuicFrame(stream_frame4));
+  SerializedPacket packet4(
+      CreateRetransmittablePacketForStream(4, 3, true, 300, 0));
+  unacked_packets_.AddSentPacket(&packet4, NOT_RETRANSMISSION, now_, true, true,
+                                 ECN_NOT_ECT);
+
+  QuicTransmissionInfo* info1 =
+      unacked_packets_.GetMutableTransmissionInfo(QuicPacketNumber(1));
+  QuicTransmissionInfo* info2 =
+      unacked_packets_.GetMutableTransmissionInfo(QuicPacketNumber(2));
+  QuicTransmissionInfo* info3 =
+      unacked_packets_.GetMutableTransmissionInfo(QuicPacketNumber(3));
+  QuicTransmissionInfo* info4 =
+      unacked_packets_.GetMutableTransmissionInfo(QuicPacketNumber(4));
 
   // Verify stream frames are aggregated.
   EXPECT_CALL(notifier_, OnFrameAcked(_, _, _)).Times(0);
   unacked_packets_.MaybeAggregateAckedStreamFrame(
-      info1, QuicTime::Delta::Zero(), QuicTime::Zero());
+      QuicPacketNumber(1), QuicTime::Delta::Zero(), QuicTime::Zero(), info1);
   EXPECT_CALL(notifier_, OnFrameAcked(_, _, _)).Times(0);
   unacked_packets_.MaybeAggregateAckedStreamFrame(
-      info2, QuicTime::Delta::Zero(), QuicTime::Zero());
+      QuicPacketNumber(2), QuicTime::Delta::Zero(), QuicTime::Zero(), info2);
   EXPECT_CALL(notifier_, OnFrameAcked(_, _, _)).Times(0);
   unacked_packets_.MaybeAggregateAckedStreamFrame(
-      info3, QuicTime::Delta::Zero(), QuicTime::Zero());
+      QuicPacketNumber(3), QuicTime::Delta::Zero(), QuicTime::Zero(), info3);
 
   // Verify aggregated stream frame gets acked since fin is acked.
   EXPECT_CALL(notifier_, OnFrameAcked(_, _, _)).Times(1);
   unacked_packets_.MaybeAggregateAckedStreamFrame(
-      info4, QuicTime::Delta::Zero(), QuicTime::Zero());
+      QuicPacketNumber(4), QuicTime::Delta::Zero(), QuicTime::Zero(), info4);
 }
 
 // Regression test for b/112930090.
@@ -498,6 +531,7 @@
   QuicByteCount kMaxAggregatedDataLength =
       std::numeric_limits<decltype(QuicStreamFrame().data_length)>::max();
   QuicStreamId stream_id = 2;
+  uint64_t next_packet_number = 1;
 
   // acked_stream_length=512 covers the case where a frame will cause the
   // aggregated frame length to be exactly 64K.
@@ -510,10 +544,13 @@
     QuicByteCount aggregated_data_length = 0;
 
     while (offset < 1e6) {
-      QuicTransmissionInfo info;
-      QuicStreamFrame stream_frame(stream_id, false, offset,
-                                   acked_stream_length);
-      info.retransmittable_frames.push_back(QuicFrame(stream_frame));
+      uint64_t packet_number = next_packet_number++;
+      SerializedPacket packet(CreateRetransmittablePacketForStream(
+          packet_number, stream_id, false, offset, acked_stream_length));
+      unacked_packets_.AddSentPacket(&packet, NOT_RETRANSMISSION, now_, true,
+                                     true, ECN_NOT_ECT);
+      QuicTransmissionInfo* info = unacked_packets_.GetMutableTransmissionInfo(
+          QuicPacketNumber(packet_number));
 
       const QuicStreamFrame& aggregated_stream_frame =
           QuicUnackedPacketMapPeer::GetAggregatedStreamFrame(unacked_packets_);
@@ -522,7 +559,8 @@
         // Verify the acked stream frame can be aggregated.
         EXPECT_CALL(notifier_, OnFrameAcked(_, _, _)).Times(0);
         unacked_packets_.MaybeAggregateAckedStreamFrame(
-            info, QuicTime::Delta::Zero(), QuicTime::Zero());
+            QuicPacketNumber(packet_number), QuicTime::Delta::Zero(),
+            QuicTime::Zero(), info);
         aggregated_data_length += acked_stream_length;
         testing::Mock::VerifyAndClearExpectations(&notifier_);
       } else {
@@ -530,7 +568,8 @@
         // data_length is overflow.
         EXPECT_CALL(notifier_, OnFrameAcked(_, _, _)).Times(1);
         unacked_packets_.MaybeAggregateAckedStreamFrame(
-            info, QuicTime::Delta::Zero(), QuicTime::Zero());
+            QuicPacketNumber(packet_number), QuicTime::Delta::Zero(),
+            QuicTime::Zero(), info);
         aggregated_data_length = acked_stream_length;
         testing::Mock::VerifyAndClearExpectations(&notifier_);
       }
@@ -540,12 +579,17 @@
     }
 
     // Ack the last frame of the stream.
-    QuicTransmissionInfo info;
-    QuicStreamFrame stream_frame(stream_id, true, offset, acked_stream_length);
-    info.retransmittable_frames.push_back(QuicFrame(stream_frame));
+    uint64_t packet_number = next_packet_number++;
+    SerializedPacket packet(CreateRetransmittablePacketForStream(
+        packet_number, stream_id, true, offset, acked_stream_length));
+    unacked_packets_.AddSentPacket(&packet, NOT_RETRANSMISSION, now_, true,
+                                   true, ECN_NOT_ECT);
+    QuicTransmissionInfo* info = unacked_packets_.GetMutableTransmissionInfo(
+        QuicPacketNumber(packet_number));
     EXPECT_CALL(notifier_, OnFrameAcked(_, _, _)).Times(1);
     unacked_packets_.MaybeAggregateAckedStreamFrame(
-        info, QuicTime::Delta::Zero(), QuicTime::Zero());
+        QuicPacketNumber(packet_number), QuicTime::Delta::Zero(),
+        QuicTime::Zero(), info);
     testing::Mock::VerifyAndClearExpectations(&notifier_);
   }
 }
@@ -556,30 +600,88 @@
   QuicStreamFrame stream_frame1(3, false, 0, 100);
   QuicStreamFrame stream_frame2(3, false, 100, 100);
   QuicBlockedFrame blocked(2, 5, 0);
-  QuicGoAwayFrame go_away(3, QUIC_PEER_GOING_AWAY, 5, "Going away.");
 
-  QuicTransmissionInfo info1;
-  info1.retransmittable_frames.push_back(QuicFrame(window_update));
-  info1.retransmittable_frames.push_back(QuicFrame(stream_frame1));
-  info1.retransmittable_frames.push_back(QuicFrame(stream_frame2));
+  SerializedPacket packet1(CreateRetransmittablePacket(
+      1, {QuicFrame(window_update), QuicFrame(stream_frame1),
+          QuicFrame(stream_frame2)}));
+  unacked_packets_.AddSentPacket(&packet1, NOT_RETRANSMISSION, now_, true, true,
+                                 ECN_NOT_ECT);
+  QuicTransmissionInfo* info1 =
+      unacked_packets_.GetMutableTransmissionInfo(QuicPacketNumber(1));
 
-  QuicTransmissionInfo info2;
-  info2.retransmittable_frames.push_back(QuicFrame(blocked));
-  info2.retransmittable_frames.push_back(QuicFrame(&go_away));
+  SerializedPacket packet2(CreateRetransmittablePacket(
+      2,
+      {QuicFrame(blocked), QuicFrame(new QuicGoAwayFrame(
+                               3, QUIC_PEER_GOING_AWAY, 5, "Going away."))}));
+  unacked_packets_.AddSentPacket(&packet2, NOT_RETRANSMISSION, now_, true, true,
+                                 ECN_NOT_ECT);
+  QuicTransmissionInfo* info2 =
+      unacked_packets_.GetMutableTransmissionInfo(QuicPacketNumber(2));
 
   // Verify 2 contiguous stream frames are aggregated.
   EXPECT_CALL(notifier_, OnFrameAcked(_, _, _)).Times(1);
   unacked_packets_.MaybeAggregateAckedStreamFrame(
-      info1, QuicTime::Delta::Zero(), QuicTime::Zero());
+      QuicPacketNumber(1), QuicTime::Delta::Zero(), QuicTime::Zero(), info1);
   // Verify aggregated stream frame gets acked.
   EXPECT_CALL(notifier_, OnFrameAcked(_, _, _)).Times(3);
   unacked_packets_.MaybeAggregateAckedStreamFrame(
-      info2, QuicTime::Delta::Zero(), QuicTime::Zero());
+      QuicPacketNumber(2), QuicTime::Delta::Zero(), QuicTime::Zero(), info2);
 
   EXPECT_CALL(notifier_, OnFrameAcked(_, _, _)).Times(0);
   unacked_packets_.NotifyAggregatedStreamFrameAcked(QuicTime::Delta::Zero());
 }
 
+TEST_P(QuicUnackedPacketMapTest, UpdateTransmissionInfoOnFrameAcked) {
+  if (!GetQuicReloadableFlag(quic_update_transmission_info_on_frame_acked)) {
+    return;
+  }
+  uint64_t next_packet_number = 1;
+  do {
+    uint64_t packet_number = next_packet_number++;
+    SerializedPacket packet(CreateRetransmittablePacket(
+        packet_number,
+        {QuicFrame(QuicPaddingFrame(static_cast<int>(packet_number) * 100))}));
+    unacked_packets_.AddSentPacket(&packet, NOT_RETRANSMISSION, now_, true,
+                                   true, ECN_NOT_ECT);
+  } while (QuicUnackedPacketMapPeer::GetCapacity(unacked_packets_) >
+           QuicUnackedPacketMapPeer::GetSize(unacked_packets_));
+  QUIC_DLOG(INFO) << "unacked_packets_ at full capacity: "
+                  << QuicUnackedPacketMapPeer::GetCapacity(unacked_packets_);
+
+  QuicPacketNumber largest_sent_packet_before_acked =
+      unacked_packets_.largest_sent_packet();
+  QuicTransmissionInfo* last_info = unacked_packets_.GetMutableTransmissionInfo(
+      largest_sent_packet_before_acked);
+  int last_padding_bytes =
+      last_info->retransmittable_frames[0].padding_frame.num_padding_bytes;
+
+  EXPECT_CALL(notifier_, OnFrameAcked(_, _, _))
+      .WillOnce(Invoke([&](const QuicFrame& frame, QuicTime::Delta, QuicTime) {
+        EXPECT_EQ(frame.type, PADDING_FRAME);
+        EXPECT_EQ(frame.padding_frame.num_padding_bytes, last_padding_bytes);
+        // Append one more packet to the unacked packet map.
+        SerializedPacket packet(CreateRetransmittablePacket(
+            next_packet_number++, {QuicFrame(QuicBlockedFrame(2, 5, 0))}));
+        unacked_packets_.AddSentPacket(&packet, NOT_RETRANSMISSION, now_, true,
+                                       true, ECN_NOT_ECT);
+        return true;
+      }));
+
+  QuicTransmissionInfo* last_info_updated = last_info;
+  unacked_packets_.NotifyFramesAcked(largest_sent_packet_before_acked,
+                                     QuicTime::Delta::Zero(), QuicTime::Zero(),
+                                     last_info_updated);
+  EXPECT_NE(last_info, last_info_updated);
+  EXPECT_EQ(unacked_packets_.GetMutableTransmissionInfo(
+                largest_sent_packet_before_acked),
+            last_info_updated);
+  ASSERT_EQ(last_info_updated->retransmittable_frames.size(), 1u);
+  EXPECT_EQ(last_info_updated->retransmittable_frames[0].type, PADDING_FRAME);
+  EXPECT_EQ(last_info_updated->retransmittable_frames[0]
+                .padding_frame.num_padding_bytes,
+            last_padding_bytes);
+}
+
 TEST_P(QuicUnackedPacketMapTest, LargestSentPacketMultiplePacketNumberSpaces) {
   unacked_packets_.EnableMultiplePacketNumberSpacesSupport();
   EXPECT_FALSE(
diff --git a/quiche/quic/test_tools/quic_unacked_packet_map_peer.cc b/quiche/quic/test_tools/quic_unacked_packet_map_peer.cc
index 4df89b0..0375c04 100644
--- a/quiche/quic/test_tools/quic_unacked_packet_map_peer.cc
+++ b/quiche/quic/test_tools/quic_unacked_packet_map_peer.cc
@@ -7,23 +7,25 @@
 namespace quic {
 namespace test {
 
-// static
 const QuicStreamFrame& QuicUnackedPacketMapPeer::GetAggregatedStreamFrame(
     const QuicUnackedPacketMap& unacked_packets) {
   return unacked_packets.aggregated_stream_frame_;
 }
 
-// static
 void QuicUnackedPacketMapPeer::SetPerspective(
     QuicUnackedPacketMap* unacked_packets, Perspective perspective) {
   *const_cast<Perspective*>(&unacked_packets->perspective_) = perspective;
 }
 
-// static
 size_t QuicUnackedPacketMapPeer::GetCapacity(
     const QuicUnackedPacketMap& unacked_packets) {
   return unacked_packets.unacked_packets_.capacity();
 }
 
+size_t QuicUnackedPacketMapPeer::GetSize(
+    const QuicUnackedPacketMap& unacked_packets) {
+  return unacked_packets.unacked_packets_.size();
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quiche/quic/test_tools/quic_unacked_packet_map_peer.h b/quiche/quic/test_tools/quic_unacked_packet_map_peer.h
index 5525506..ce4d87e 100644
--- a/quiche/quic/test_tools/quic_unacked_packet_map_peer.h
+++ b/quiche/quic/test_tools/quic_unacked_packet_map_peer.h
@@ -19,6 +19,8 @@
                              Perspective perspective);
 
   static size_t GetCapacity(const QuicUnackedPacketMap& unacked_packets);
+
+  static size_t GetSize(const QuicUnackedPacketMap& unacked_packets);
 };
 
 }  // namespace test