package ackhandler

import (
	
	
	

	
	
	
	
	
	
	
	
)

const (
	// Maximum reordering in time space before time based loss detection considers a packet lost.
	// Specified as an RTT multiplier.
	timeThreshold = 9.0 / 8
	// Maximum reordering in packets before packet threshold loss detection considers a packet lost.
	packetThreshold = 3
	// Before validating the client's address, the server won't send more than 3x bytes than it received.
	amplificationFactor = 3
	// We use Retry packets to derive an RTT estimate. Make sure we don't set the RTT to a super low value yet.
	minRTTAfterRetry = 5 * time.Millisecond
	// The PTO duration uses exponential backoff, but is truncated to a maximum value, as allowed by RFC 8961, section 4.4.
	maxPTODuration = 60 * time.Second
)

// Path probe packets are declared lost after this time.
const pathProbePacketLossTimeout = time.Second

type packetNumberSpace struct {
	history sentPacketHistory
	pns     packetNumberGenerator

	lossTime                   monotime.Time
	lastAckElicitingPacketTime monotime.Time

	largestAcked protocol.PacketNumber
	largestSent  protocol.PacketNumber
}

func newPacketNumberSpace( protocol.PacketNumber,  bool) *packetNumberSpace {
	var  packetNumberGenerator
	if  {
		 = newSkippingPacketNumberGenerator(, protocol.SkipPacketInitialPeriod, protocol.SkipPacketMaxPeriod)
	} else {
		 = newSequentialPacketNumberGenerator()
	}
	return &packetNumberSpace{
		history:      *newSentPacketHistory(),
		pns:          ,
		largestSent:  protocol.InvalidPacketNumber,
		largestAcked: protocol.InvalidPacketNumber,
	}
}

type alarmTimer struct {
	Time            monotime.Time
	TimerType       qlog.TimerType
	EncryptionLevel protocol.EncryptionLevel
}

type sentPacketHandler struct {
	initialPackets   *packetNumberSpace
	handshakePackets *packetNumberSpace
	appDataPackets   *packetNumberSpace
	lostPackets      lostPacketTracker // only for application-data packet number space
	// send time of the largest acknowledged packet, across all packet number spaces
	largestAckedTime monotime.Time

	// Do we know that the peer completed address validation yet?
	// Always true for the server.
	peerCompletedAddressValidation bool
	bytesReceived                  protocol.ByteCount
	bytesSent                      protocol.ByteCount
	// Have we validated the peer's address yet?
	// Always true for the client.
	peerAddressValidated bool

	handshakeConfirmed bool

	ignorePacketsBelow func(protocol.PacketNumber)

	ackedPackets []packetWithPacketNumber // to avoid allocations in detectAndRemoveAckedPackets

	bytesInFlight protocol.ByteCount

	congestion congestion.SendAlgorithmWithDebugInfos
	rttStats   *utils.RTTStats
	connStats  *utils.ConnectionStats

	// The number of times a PTO has been sent without receiving an ack.
	ptoCount uint32
	ptoMode  SendMode
	// The number of PTO probe packets that should be sent.
	// Only applies to the application-data packet number space.
	numProbesToSend int

	// The alarm timeout
	alarm alarmTimer

	enableECN  bool
	ecnTracker ecnHandler

	perspective protocol.Perspective

	qlogger     qlogwriter.Recorder
	lastMetrics qlog.MetricsUpdated
	logger      utils.Logger
}

var _ SentPacketHandler = &sentPacketHandler{}

// clientAddressValidated indicates whether the address was validated beforehand by an address validation token.
// If the address was validated, the amplification limit doesn't apply. It has no effect for a client.
func (
	 protocol.PacketNumber,
	 protocol.ByteCount,
	 *utils.RTTStats,
	 *utils.ConnectionStats,
	 bool,
	 bool,
	 func(protocol.PacketNumber),
	 protocol.Perspective,
	 qlogwriter.Recorder,
	 utils.Logger,
) SentPacketHandler {
	 := congestion.NewCubicSender(
		congestion.DefaultClock{},
		,
		,
		,
		true, // use Reno
		,
	)

	 := &sentPacketHandler{
		peerCompletedAddressValidation:  == protocol.PerspectiveServer,
		peerAddressValidated:            == protocol.PerspectiveClient || ,
		initialPackets:                 newPacketNumberSpace(, false),
		handshakePackets:               newPacketNumberSpace(0, false),
		appDataPackets:                 newPacketNumberSpace(0, true),
		lostPackets:                    *newLostPacketTracker(64),
		rttStats:                       ,
		connStats:                      ,
		congestion:                     ,
		ignorePacketsBelow:             ,
		perspective:                    ,
		qlogger:                        ,
		logger:                         ,
	}
	if  {
		.enableECN = true
		.ecnTracker = newECNTracker(, )
	}
	return 
}

func ( *sentPacketHandler) ( *packet) {
	if .includedInBytesInFlight {
		if .Length > .bytesInFlight {
			panic("negative bytes_in_flight")
		}
		.bytesInFlight -= .Length
		.includedInBytesInFlight = false
	}
}

func ( *sentPacketHandler) ( protocol.EncryptionLevel,  monotime.Time) {
	// The server won't await address validation after the handshake is confirmed.
	// This applies even if we didn't receive an ACK for a Handshake packet.
	if .perspective == protocol.PerspectiveClient &&  == protocol.EncryptionHandshake {
		.peerCompletedAddressValidation = true
	}
	// remove outstanding packets from bytes_in_flight
	if  == protocol.EncryptionInitial ||  == protocol.EncryptionHandshake {
		 := .getPacketNumberSpace()
		// We might already have dropped this packet number space.
		if  == nil {
			return
		}
		for ,  := range .history.Packets() {
			.removeFromBytesInFlight()
		}
	}
	// drop the packet history
	//nolint:exhaustive // Not every packet number space can be dropped.
	switch  {
	case protocol.EncryptionInitial:
		.initialPackets = nil
	case protocol.EncryptionHandshake:
		// Dropping the handshake packet number space means that the handshake is confirmed,
		// see section 4.9.2 of RFC 9001.
		.handshakeConfirmed = true
		.handshakePackets = nil
	case protocol.Encryption0RTT:
		// This function is only called when 0-RTT is rejected,
		// and not when the client drops 0-RTT keys when the handshake completes.
		// When 0-RTT is rejected, all application data sent so far becomes invalid.
		// Delete the packets from the history and remove them from bytes_in_flight.
		for ,  := range .appDataPackets.history.Packets() {
			if .EncryptionLevel != protocol.Encryption0RTT {
				break
			}
			.removeFromBytesInFlight()
			.appDataPackets.history.Remove()
		}
	default:
		panic(fmt.Sprintf("Cannot drop keys for encryption level %s", ))
	}
	if .qlogger != nil && .ptoCount != 0 {
		.qlogger.RecordEvent(qlog.PTOCountUpdated{PTOCount: 0})
	}
	.ptoCount = 0
	.numProbesToSend = 0
	.ptoMode = SendNone
	.setLossDetectionTimer()
}

func ( *sentPacketHandler) ( protocol.ByteCount,  monotime.Time) {
	.connStats.BytesReceived.Add(uint64())
	 := .isAmplificationLimited()
	.bytesReceived += 
	if  && !.isAmplificationLimited() {
		.setLossDetectionTimer()
	}
}

func ( *sentPacketHandler) ( protocol.EncryptionLevel,  monotime.Time) {
	.connStats.PacketsReceived.Add(1)
	if .perspective == protocol.PerspectiveServer &&  == protocol.EncryptionHandshake && !.peerAddressValidated {
		.peerAddressValidated = true
		.setLossDetectionTimer()
	}
}

func ( *sentPacketHandler) () int {
	 := .appDataPackets.history.NumOutstanding()
	if .handshakePackets != nil {
		 += .handshakePackets.history.NumOutstanding()
	}
	if .initialPackets != nil {
		 += .initialPackets.history.NumOutstanding()
	}
	return 
}

func ( *sentPacketHandler) (
	 monotime.Time,
	,  protocol.PacketNumber,
	 []StreamFrame,
	 []Frame,
	 protocol.EncryptionLevel,
	 protocol.ECN,
	 protocol.ByteCount,
	 bool,
	 bool,
) {
	.bytesSent += 
	.connStats.BytesSent.Add(uint64())
	.connStats.PacketsSent.Add(1)

	 := .getPacketNumberSpace()
	if .logger.Debug() && (.history.HasOutstandingPackets() || .history.HasOutstandingPathProbes()) {
		for  := max(0, .largestSent+1);  < ; ++ {
			.logger.Debugf("Skipping packet number %d", )
		}
	}

	.largestSent = 

	 := getPacket()
	.SendTime = 
	.EncryptionLevel = 
	.Length = 
	.Frames = 
	.LargestAcked = 
	.StreamFrames = 
	.IsPathMTUProbePacket = 
	.isPathProbePacket = 
	 := .IsAckEliciting()

	if  {
		.history.SentPathProbePacket(, )
		.setLossDetectionTimer()
		return
	}
	if  {
		.lastAckElicitingPacketTime = 
		.bytesInFlight += 
		.includedInBytesInFlight = true
		if .numProbesToSend > 0 {
			.numProbesToSend--
		}
	}
	.congestion.OnPacketSent(, .bytesInFlight, , , )

	if  == protocol.Encryption1RTT && .ecnTracker != nil {
		.ecnTracker.SentPacket(, )
	}

	.history.SentPacket(, )
	if ! {
		if !.peerCompletedAddressValidation {
			.setLossDetectionTimer()
		}
		return
	}
	if .qlogger != nil {
		.qlogMetricsUpdated()
	}
	.setLossDetectionTimer()
}

func ( *sentPacketHandler) () {
	var  qlog.MetricsUpdated
	var  bool
	if .rttStats.HasMeasurement() {
		if .lastMetrics.MinRTT != .rttStats.MinRTT() {
			.MinRTT = .rttStats.MinRTT()
			.lastMetrics.MinRTT = .MinRTT
			 = true
		}
		if .lastMetrics.SmoothedRTT != .rttStats.SmoothedRTT() {
			.SmoothedRTT = .rttStats.SmoothedRTT()
			.lastMetrics.SmoothedRTT = .SmoothedRTT
			 = true
		}
		if .lastMetrics.LatestRTT != .rttStats.LatestRTT() {
			.LatestRTT = .rttStats.LatestRTT()
			.lastMetrics.LatestRTT = .LatestRTT
			 = true
		}
		if .lastMetrics.RTTVariance != .rttStats.MeanDeviation() {
			.RTTVariance = .rttStats.MeanDeviation()
			.lastMetrics.RTTVariance = .RTTVariance
			 = true
		}
	}
	if  := .congestion.GetCongestionWindow(); .lastMetrics.CongestionWindow != int() {
		.CongestionWindow = int()
		.lastMetrics.CongestionWindow = .CongestionWindow
		 = true
	}
	if .lastMetrics.BytesInFlight != int(.bytesInFlight) {
		.BytesInFlight = int(.bytesInFlight)
		.lastMetrics.BytesInFlight = .BytesInFlight
		 = true
	}
	 := .packetsInFlight()
	if .lastMetrics.PacketsInFlight !=  {
		.PacketsInFlight = 
		.lastMetrics.PacketsInFlight = .PacketsInFlight
		 = true
	}
	if  {
		.qlogger.RecordEvent()
	}
}

func ( *sentPacketHandler) ( protocol.EncryptionLevel) *packetNumberSpace {
	switch  {
	case protocol.EncryptionInitial:
		return .initialPackets
	case protocol.EncryptionHandshake:
		return .handshakePackets
	case protocol.Encryption0RTT, protocol.Encryption1RTT:
		return .appDataPackets
	default:
		panic("invalid packet number space")
	}
}

func ( *sentPacketHandler) ( *wire.AckFrame,  protocol.EncryptionLevel,  monotime.Time) (bool /* contained 1-RTT packet */, error) {
	 := .getPacketNumberSpace()

	 := .LargestAcked()
	if  > .largestSent {
		return false, &qerr.TransportError{
			ErrorCode:    qerr.ProtocolViolation,
			ErrorMessage: "received ACK for an unsent packet",
		}
	}

	// Servers complete address validation when a protected packet is received.
	if .perspective == protocol.PerspectiveClient && !.peerCompletedAddressValidation &&
		( == protocol.EncryptionHandshake ||  == protocol.Encryption1RTT) {
		.peerCompletedAddressValidation = true
		.logger.Debugf("Peer doesn't await address validation any longer.")
		// Make sure that the timer is reset, even if this ACK doesn't acknowledge any (ack-eliciting) packets.
		.setLossDetectionTimer()
	}

	 := .bytesInFlight
	, ,  := .detectAndRemoveAckedPackets(, )
	if  != nil || len() == 0 {
		return false, 
	}
	// update the RTT, if:
	// * the largest acked is newly acknowledged, AND
	// * at least one new ack-eliciting packet was acknowledged
	if len() > 0 {
		if  := [len()-1]; .PacketNumber == .LargestAcked() && !.isPathProbePacket &&  {
			// don't use the ack delay for Initial and Handshake packets
			var  time.Duration
			if  == protocol.Encryption1RTT {
				 = min(.DelayTime, .rttStats.MaxAckDelay())
			}
			if .largestAckedTime.IsZero() || !.SendTime.Before(.largestAckedTime) {
				.rttStats.UpdateRTT(.Sub(.SendTime), )
				if .logger.Debug() {
					.logger.Debugf("\tupdated RTT: %s (σ: %s)", .rttStats.SmoothedRTT(), .rttStats.MeanDeviation())
				}
				.largestAckedTime = .SendTime
			}
			.congestion.MaybeExitSlowStart()
		}
	}

	// Only inform the ECN tracker about new 1-RTT ACKs if the ACK increases the largest acked.
	if  == protocol.Encryption1RTT && .ecnTracker != nil &&  > .largestAcked {
		 := .ecnTracker.HandleNewlyAcked(, int64(.ECT0), int64(.ECT1), int64(.ECNCE))
		if  {
			.congestion.OnCongestionEvent(, 0, )
		}
	}

	.largestAcked = max(.largestAcked, )

	.detectLostPackets(, )
	if  == protocol.Encryption1RTT {
		.detectLostPathProbes()
	}
	var  bool
	for ,  := range  {
		if .includedInBytesInFlight {
			.congestion.OnPacketAcked(.PacketNumber, .Length, , )
		}
		if .EncryptionLevel == protocol.Encryption1RTT {
			 = true
		}
		.removeFromBytesInFlight(.packet)
		if !.isPathProbePacket {
			putPacket(.packet)
		}
	}

	// detect spurious losses for application data packets, if the ACK was not reordered
	if  == protocol.Encryption1RTT &&  == .largestAcked {
		.detectSpuriousLosses(
			,
			.Add(-min(.DelayTime, .rttStats.MaxAckDelay())),
		)
		// clean up lost packet history
		.lostPackets.DeleteBefore(.Add(-3 * .rttStats.PTO(false)))
	}

	// After this point, we must not use ackedPackets any longer!
	// We've already returned the buffers.
	 = nil    //nolint:ineffassign // This is just to be on the safe side.
	clear(.ackedPackets) // make sure the memory is released
	.ackedPackets = .ackedPackets[:0]

	// Reset the pto_count unless the client is unsure if the server has validated the client's address.
	if .peerCompletedAddressValidation {
		if .qlogger != nil && .ptoCount != 0 {
			.qlogger.RecordEvent(qlog.PTOCountUpdated{PTOCount: 0})
		}
		.ptoCount = 0
	}
	.numProbesToSend = 0

	if .qlogger != nil {
		.qlogMetricsUpdated()
	}

	.setLossDetectionTimer()
	return , nil
}

func ( *sentPacketHandler) ( *wire.AckFrame,  monotime.Time) {
	var  protocol.PacketNumber
	var  time.Duration
	 := len(.AckRanges) - 1
	var  []protocol.PacketNumber
	for ,  := range .lostPackets.All() {
		 := .AckRanges[]
		for  > .Largest {
			// this should never happen, since detectSpuriousLosses is only called for ACKs that increase the largest acked
			if  == 0 {
				break
			}
			--
			 = .AckRanges[]
		}
		if  < .Smallest {
			continue
		}
		if  <= .Largest {
			 := .appDataPackets.history.Difference(.LargestAcked(), )
			 := .Sub()
			 = max(, )
			 = max(, )

			if .qlogger != nil {
				.qlogger.RecordEvent(qlog.SpuriousLoss{
					EncryptionLevel:  protocol.Encryption1RTT,
					PacketNumber:     ,
					PacketReordering: uint64(),
					TimeReordering:   ,
				})
			}
			 = append(, )
		}
	}
	for ,  := range  {
		.lostPackets.Delete()
	}
}

// Packets are returned in ascending packet number order.
func ( *sentPacketHandler) (
	 *wire.AckFrame,
	 protocol.EncryptionLevel,
) ( []packetWithPacketNumber,  bool,  error) {
	if len(.ackedPackets) > 0 {
		return nil, false, errors.New("ackhandler BUG: ackedPackets slice not empty")
	}

	 := .getPacketNumberSpace()

	if  == protocol.Encryption1RTT {
		for  := range .history.SkippedPackets() {
			if .AcksPacket() {
				return nil, false, &qerr.TransportError{
					ErrorCode:    qerr.ProtocolViolation,
					ErrorMessage: fmt.Sprintf("received an ACK for skipped packet number: %d (%s)", , ),
				}
			}
		}
	}

	var  int
	 := .LowestAcked()
	 := .LargestAcked()
	for ,  := range .history.Packets() {
		// ignore packets below the lowest acked
		if  <  {
			continue
		}
		if  >  {
			break
		}

		if .HasMissingRanges() {
			 := .AckRanges[len(.AckRanges)-1-]

			for  > .Largest &&  < len(.AckRanges)-1 {
				++
				 = .AckRanges[len(.AckRanges)-1-]
			}

			if  < .Smallest { // packet not contained in ACK range
				continue
			}
			if  > .Largest {
				return nil, false, fmt.Errorf("BUG: ackhandler would have acked wrong packet %d, while evaluating range %d -> %d", , .Smallest, .Largest)
			}
		}
		if .isPathProbePacket {
			 := .history.RemovePathProbe()
			// the probe packet might already have been declared lost
			if  != nil {
				.ackedPackets = append(.ackedPackets, packetWithPacketNumber{PacketNumber: , packet: })
			}
			continue
		}
		if .IsAckEliciting() {
			 = true
		}
		.ackedPackets = append(.ackedPackets, packetWithPacketNumber{PacketNumber: , packet: })
	}
	if .logger.Debug() && len(.ackedPackets) > 0 {
		 := make([]protocol.PacketNumber, len(.ackedPackets))
		for ,  := range .ackedPackets {
			[] = .PacketNumber
		}
		.logger.Debugf("\tnewly acked packets (%d): %d", len(), )
	}

	for ,  := range .ackedPackets {
		if .LargestAcked != protocol.InvalidPacketNumber &&  == protocol.Encryption1RTT && .ignorePacketsBelow != nil {
			.ignorePacketsBelow(.LargestAcked + 1)
		}

		for ,  := range .Frames {
			if .Handler != nil {
				.Handler.OnAcked(.Frame)
			}
		}
		for ,  := range .StreamFrames {
			if .Handler != nil {
				.Handler.OnAcked(.Frame)
			}
		}
		if  := .history.Remove(.PacketNumber);  != nil {
			return nil, false, 
		}
	}
	// TODO: add support for the transport:packets_acked qlog event
	return .ackedPackets, , nil
}

func ( *sentPacketHandler) () (monotime.Time, protocol.EncryptionLevel) {
	var  protocol.EncryptionLevel
	var  monotime.Time

	if .initialPackets != nil {
		 = .initialPackets.lossTime
		 = protocol.EncryptionInitial
	}
	if .handshakePackets != nil && (.IsZero() || (!.handshakePackets.lossTime.IsZero() && .handshakePackets.lossTime.Before())) {
		 = .handshakePackets.lossTime
		 = protocol.EncryptionHandshake
	}
	if .IsZero() || (!.appDataPackets.lossTime.IsZero() && .appDataPackets.lossTime.Before()) {
		 = .appDataPackets.lossTime
		 = protocol.Encryption1RTT
	}
	return , 
}

func ( *sentPacketHandler) ( bool) time.Duration {
	 := .rttStats.PTO() << .ptoCount
	if  > maxPTODuration ||  <= 0 {
		return maxPTODuration
	}
	return 
}

// same logic as getLossTimeAndSpace, but for lastAckElicitingPacketTime instead of lossTime
func ( *sentPacketHandler) ( monotime.Time) ( monotime.Time,  protocol.EncryptionLevel) {
	// We only send application data probe packets once the handshake is confirmed,
	// because before that, we don't have the keys to decrypt ACKs sent in 1-RTT packets.
	if !.handshakeConfirmed && !.hasOutstandingCryptoPackets() {
		if .peerCompletedAddressValidation {
			return
		}
		 := .Add(.getScaledPTO(false))
		if .initialPackets != nil {
			return , protocol.EncryptionInitial
		}
		return , protocol.EncryptionHandshake
	}

	if .initialPackets != nil && .initialPackets.history.HasOutstandingPackets() &&
		!.initialPackets.lastAckElicitingPacketTime.IsZero() {
		 = protocol.EncryptionInitial
		if  := .initialPackets.lastAckElicitingPacketTime; !.IsZero() {
			 = .Add(.getScaledPTO(false))
		}
	}
	if .handshakePackets != nil && .handshakePackets.history.HasOutstandingPackets() &&
		!.handshakePackets.lastAckElicitingPacketTime.IsZero() {
		 := .handshakePackets.lastAckElicitingPacketTime.Add(.getScaledPTO(false))
		if .IsZero() || (!.IsZero() && .Before()) {
			 = 
			 = protocol.EncryptionHandshake
		}
	}
	if .handshakeConfirmed && .appDataPackets.history.HasOutstandingPackets() &&
		!.appDataPackets.lastAckElicitingPacketTime.IsZero() {
		 := .appDataPackets.lastAckElicitingPacketTime.Add(.getScaledPTO(true))
		if .IsZero() || (!.IsZero() && .Before()) {
			 = 
			 = protocol.Encryption1RTT
		}
	}
	return , 
}

func ( *sentPacketHandler) () bool {
	if .initialPackets != nil && .initialPackets.history.HasOutstandingPackets() {
		return true
	}
	if .handshakePackets != nil && .handshakePackets.history.HasOutstandingPackets() {
		return true
	}
	return false
}

func ( *sentPacketHandler) ( monotime.Time) {
	 := .alarm // only needed in case tracing is enabled
	 := .lossDetectionTime()
	.alarm = 

	 := !.Time.IsZero()
	if ! && !.Time.IsZero() {
		.logger.Debugf("Canceling loss detection timer.")
		if .qlogger != nil {
			.qlogger.RecordEvent(qlog.LossTimerUpdated{
				Type: qlog.LossTimerUpdateTypeCancelled,
			})
		}
	}

	if .qlogger != nil &&  &&  !=  {
		.qlogger.RecordEvent(qlog.LossTimerUpdated{
			Type:      qlog.LossTimerUpdateTypeSet,
			TimerType: .TimerType,
			EncLevel:  .EncryptionLevel,
			Time:      .Time.ToTime(),
		})
	}
}

func ( *sentPacketHandler) ( monotime.Time) alarmTimer {
	// cancel the alarm if no packets are outstanding
	if .peerCompletedAddressValidation && !.hasOutstandingCryptoPackets() &&
		!.appDataPackets.history.HasOutstandingPackets() && !.appDataPackets.history.HasOutstandingPathProbes() {
		return alarmTimer{}
	}

	// cancel the alarm if amplification limited
	if .isAmplificationLimited() {
		return alarmTimer{}
	}

	var  monotime.Time
	if .appDataPackets.history.HasOutstandingPathProbes() {
		if ,  := .appDataPackets.history.FirstOutstandingPathProbe();  != nil {
			 = .SendTime.Add(pathProbePacketLossTimeout)
		}
	}

	// early retransmit timer or time loss detection
	,  := .getLossTimeAndSpace()
	if !.IsZero() && (.IsZero() || .Before()) {
		return alarmTimer{
			Time:            ,
			TimerType:       qlog.TimerTypeACK,
			EncryptionLevel: ,
		}
	}
	,  := .getPTOTimeAndSpace()
	if !.IsZero() && (.IsZero() || .Before()) {
		return alarmTimer{
			Time:            ,
			TimerType:       qlog.TimerTypePTO,
			EncryptionLevel: ,
		}
	}
	if !.IsZero() {
		return alarmTimer{
			Time:            ,
			TimerType:       qlog.TimerTypePathProbe,
			EncryptionLevel: protocol.Encryption1RTT,
		}
	}
	return alarmTimer{}
}

func ( *sentPacketHandler) ( monotime.Time) {
	if !.appDataPackets.history.HasOutstandingPathProbes() {
		return
	}
	 := .Add(-pathProbePacketLossTimeout)
	// RemovePathProbe cannot be called while iterating.
	var  []packetWithPacketNumber
	for ,  := range .appDataPackets.history.PathProbes() {
		if !.SendTime.After() {
			 = append(, packetWithPacketNumber{PacketNumber: , packet: })
		}
	}
	for ,  := range  {
		for ,  := range .Frames {
			.Handler.OnLost(.Frame)
		}
		.appDataPackets.history.RemovePathProbe(.PacketNumber)
	}
}

func ( *sentPacketHandler) ( monotime.Time,  protocol.EncryptionLevel) {
	 := .getPacketNumberSpace()
	.lossTime = 0

	 := float64(max(.rttStats.LatestRTT(), .rttStats.SmoothedRTT()))
	 := time.Duration(timeThreshold * )

	// Minimum time of granularity before packets are deemed lost.
	 = max(, protocol.TimerGranularity)

	// Packets sent before this time are deemed lost.
	 := .Add(-)

	 := .bytesInFlight
	for ,  := range .history.Packets() {
		if  > .largestAcked {
			break
		}

		var  bool
		if !.SendTime.After() {
			 = true
			if !.isPathProbePacket && .IsAckEliciting() {
				if .logger.Debug() {
					.logger.Debugf("\tlost packet %d (time threshold)", )
				}
				if .qlogger != nil {
					.qlogger.RecordEvent(qlog.PacketLost{
						Header: qlog.PacketHeader{
							PacketType:   qlog.EncryptionLevelToPacketType(.EncryptionLevel),
							PacketNumber: ,
						},
						Trigger: qlog.PacketLossTimeThreshold,
					})
				}
			}
		} else if .history.Difference(.largestAcked, ) >= packetThreshold {
			 = true
			if !.isPathProbePacket && .IsAckEliciting() {
				if .logger.Debug() {
					.logger.Debugf("\tlost packet %d (reordering threshold)", )
				}
				if .qlogger != nil {
					.qlogger.RecordEvent(qlog.PacketLost{
						Header: qlog.PacketHeader{
							PacketType:   qlog.EncryptionLevelToPacketType(.EncryptionLevel),
							PacketNumber: ,
						},
						Trigger: qlog.PacketLossReorderingThreshold,
					})
				}
			}
		} else if .lossTime.IsZero() {
			// Note: This conditional is only entered once per call
			 := .SendTime.Add()
			if .logger.Debug() {
				.logger.Debugf("\tsetting loss timer for packet %d (%s) to %s (in %s)", , , , )
			}
			.lossTime = 
		}
		if  {
			if  == protocol.Encryption0RTT ||  == protocol.Encryption1RTT {
				.lostPackets.Add(, .SendTime)
			}
			.history.DeclareLost()
			if !.isPathProbePacket && .IsAckEliciting() {
				// the bytes in flight need to be reduced no matter if the frames in this packet will be retransmitted
				.removeFromBytesInFlight()
				.queueFramesForRetransmission()
				if !.IsPathMTUProbePacket {
					.congestion.OnCongestionEvent(, .Length, )
				}
				if  == protocol.Encryption1RTT && .ecnTracker != nil {
					.ecnTracker.LostPacket()
				}
			}
		}
	}
}

func ( *sentPacketHandler) ( monotime.Time) error {
	defer .setLossDetectionTimer()

	if .handshakeConfirmed {
		.detectLostPathProbes()
	}

	,  := .getLossTimeAndSpace()
	if !.IsZero() {
		if .logger.Debug() {
			.logger.Debugf("Loss detection alarm fired in loss timer mode. Loss time: %s", )
		}
		if .qlogger != nil {
			.qlogger.RecordEvent(qlog.LossTimerUpdated{
				Type:      qlog.LossTimerUpdateTypeExpired,
				TimerType: qlog.TimerTypeACK,
				EncLevel:  ,
			})
		}
		// Early retransmit or time loss detection
		.detectLostPackets(, )
		return nil
	}

	// PTO
	// When all outstanding are acknowledged, the alarm is canceled in setLossDetectionTimer.
	// However, there's no way to reset the timer in the connection.
	// When OnLossDetectionTimeout is called, we therefore need to make sure that there are
	// actually packets outstanding.
	if .bytesInFlight == 0 && !.peerCompletedAddressValidation {
		.ptoCount++
		.numProbesToSend++
		if .initialPackets != nil {
			.ptoMode = SendPTOInitial
		} else if .handshakePackets != nil {
			.ptoMode = SendPTOHandshake
		} else {
			return errors.New("sentPacketHandler BUG: PTO fired, but bytes_in_flight is 0 and Initial and Handshake already dropped")
		}
		return nil
	}

	,  := .getPTOTimeAndSpace()
	if .IsZero() {
		return nil
	}
	 := .getPacketNumberSpace()
	if !.history.HasOutstandingPackets() && !.history.HasOutstandingPathProbes() && !.peerCompletedAddressValidation {
		return nil
	}
	.ptoCount++
	if .logger.Debug() {
		.logger.Debugf("Loss detection alarm for %s fired in PTO mode. PTO count: %d", , .ptoCount)
	}
	if .qlogger != nil {
		.qlogger.RecordEvent(qlog.LossTimerUpdated{
			Type:      qlog.LossTimerUpdateTypeExpired,
			TimerType: qlog.TimerTypePTO,
			EncLevel:  ,
		})
		.qlogger.RecordEvent(qlog.PTOCountUpdated{PTOCount: .ptoCount})
	}
	.numProbesToSend += 2
	//nolint:exhaustive // We never arm a PTO timer for 0-RTT packets.
	switch  {
	case protocol.EncryptionInitial:
		.ptoMode = SendPTOInitial
	case protocol.EncryptionHandshake:
		.ptoMode = SendPTOHandshake
	case protocol.Encryption1RTT:
		// skip a packet number in order to elicit an immediate ACK
		 := .PopPacketNumber(protocol.Encryption1RTT)
		.getPacketNumberSpace(protocol.Encryption1RTT).history.SkippedPacket()
		.ptoMode = SendPTOAppData
	default:
		return fmt.Errorf("PTO timer in unexpected encryption level: %s", )
	}
	return nil
}

func ( *sentPacketHandler) () monotime.Time {
	return .alarm.Time
}

func ( *sentPacketHandler) ( bool) protocol.ECN {
	if !.enableECN {
		return protocol.ECNUnsupported
	}
	if ! {
		return protocol.ECNNon
	}
	return .ecnTracker.Mode()
}

func ( *sentPacketHandler) ( protocol.EncryptionLevel) (protocol.PacketNumber, protocol.PacketNumberLen) {
	 := .getPacketNumberSpace()
	 := .pns.Peek()
	// See section 17.1 of RFC 9000.
	return , protocol.PacketNumberLengthForHeader(, .largestAcked)
}

func ( *sentPacketHandler) ( protocol.EncryptionLevel) protocol.PacketNumber {
	 := .getPacketNumberSpace()
	,  := .pns.Pop()
	if  {
		 :=  - 1
		.history.SkippedPacket()
		if .logger.Debug() {
			.logger.Debugf("Skipping packet number %d", )
		}
	}
	return 
}

func ( *sentPacketHandler) ( monotime.Time) SendMode {
	 := .appDataPackets.history.Len()
	if .initialPackets != nil {
		 += .initialPackets.history.Len()
	}
	if .handshakePackets != nil {
		 += .handshakePackets.history.Len()
	}

	if .isAmplificationLimited() {
		.logger.Debugf("Amplification window limited. Received %d bytes, already sent out %d bytes", .bytesReceived, .bytesSent)
		return SendNone
	}
	// Don't send any packets if we're keeping track of the maximum number of packets.
	// Note that since MaxOutstandingSentPackets is smaller than MaxTrackedSentPackets,
	// we will stop sending out new data when reaching MaxOutstandingSentPackets,
	// but still allow sending of retransmissions and ACKs.
	if  >= protocol.MaxTrackedSentPackets {
		if .logger.Debug() {
			.logger.Debugf("Limited by the number of tracked packets: tracking %d packets, maximum %d", , protocol.MaxTrackedSentPackets)
		}
		return SendNone
	}
	if .numProbesToSend > 0 {
		return .ptoMode
	}
	// Only send ACKs if we're congestion limited.
	if !.congestion.CanSend(.bytesInFlight) {
		if .logger.Debug() {
			.logger.Debugf("Congestion limited: bytes in flight %d, window %d", .bytesInFlight, .congestion.GetCongestionWindow())
		}
		return SendAck
	}
	if  >= protocol.MaxOutstandingSentPackets {
		if .logger.Debug() {
			.logger.Debugf("Max outstanding limited: tracking %d packets, maximum: %d", , protocol.MaxOutstandingSentPackets)
		}
		return SendAck
	}
	if !.congestion.HasPacingBudget() {
		return SendPacingLimited
	}
	return SendAny
}

func ( *sentPacketHandler) () monotime.Time {
	return .congestion.TimeUntilSend(.bytesInFlight)
}

func ( *sentPacketHandler) ( protocol.ByteCount) {
	.congestion.SetMaxDatagramSize()
}

func ( *sentPacketHandler) () bool {
	if .peerAddressValidated {
		return false
	}
	return .bytesSent >= amplificationFactor*.bytesReceived
}

func ( *sentPacketHandler) ( protocol.EncryptionLevel) bool {
	 := .getPacketNumberSpace()
	,  := .history.FirstOutstanding()
	if  == nil {
		return false
	}
	// TODO: don't declare the packet lost here.
	// Keep track of acknowledged frames instead.
	// Call DeclareLost before queueFramesForRetransmission, which clears the packet's frames.
	.history.DeclareLost()
	.removeFromBytesInFlight()
	.queueFramesForRetransmission()
	return true
}

func ( *sentPacketHandler) ( *packet) {
	if len(.Frames) == 0 && len(.StreamFrames) == 0 {
		panic("no frames")
	}
	for ,  := range .Frames {
		if .Handler != nil {
			.Handler.OnLost(.Frame)
		}
	}
	for ,  := range .StreamFrames {
		if .Handler != nil {
			.Handler.OnLost(.Frame)
		}
	}
	.StreamFrames = nil
	.Frames = nil
}

func ( *sentPacketHandler) ( monotime.Time) {
	.bytesInFlight = 0
	var  monotime.Time
	for ,  := range .initialPackets.history.Packets() {
		if .IsZero() {
			 = .SendTime
		}
		if .IsAckEliciting() {
			.queueFramesForRetransmission()
		}
	}
	// All application data packets sent at this point are 0-RTT packets.
	// In the case of a Retry, we can assume that the server dropped all of them.
	for ,  := range .appDataPackets.history.Packets() {
		if .IsAckEliciting() {
			.queueFramesForRetransmission()
		}
	}

	// Only use the Retry to estimate the RTT if we didn't send any retransmission for the Initial.
	// Otherwise, we don't know which Initial the Retry was sent in response to.
	if .ptoCount == 0 {
		// Don't set the RTT to a value lower than 5ms here.
		.rttStats.UpdateRTT(max(minRTTAfterRetry, .Sub()), 0)
		if .logger.Debug() {
			.logger.Debugf("\tupdated RTT: %s (σ: %s)", .rttStats.SmoothedRTT(), .rttStats.MeanDeviation())
		}
		if .qlogger != nil {
			.qlogMetricsUpdated()
		}
	}
	.initialPackets = newPacketNumberSpace(.initialPackets.pns.Peek(), false)
	.appDataPackets = newPacketNumberSpace(.appDataPackets.pns.Peek(), true)
	 := .alarm
	.alarm = alarmTimer{}
	if .qlogger != nil {
		.qlogger.RecordEvent(qlog.PTOCountUpdated{PTOCount: 0})
		if !.Time.IsZero() {
			.qlogger.RecordEvent(qlog.LossTimerUpdated{
				Type: qlog.LossTimerUpdateTypeCancelled,
			})
		}
	}
	.ptoCount = 0
}

func ( *sentPacketHandler) ( monotime.Time,  protocol.ByteCount) {
	.rttStats.ResetForPathMigration()
	for ,  := range .appDataPackets.history.Packets() {
		.appDataPackets.history.DeclareLost()
		if !.isPathProbePacket {
			.removeFromBytesInFlight()
			if .IsAckEliciting() {
				.queueFramesForRetransmission()
			}
		}
	}
	for  := range .appDataPackets.history.PathProbes() {
		.appDataPackets.history.RemovePathProbe()
	}
	.congestion = congestion.NewCubicSender(
		congestion.DefaultClock{},
		.rttStats,
		.connStats,
		,
		true, // use Reno
		.qlogger,
	)
	.setLossDetectionTimer()
}