package congestion

import (
	"time"

	"github.com/lucas-clemente/quic-go/internal/protocol"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

const initialCongestionWindowPackets protocol.PacketNumber = 10
const defaultWindowTCP = protocol.ByteCount(initialCongestionWindowPackets) * protocol.DefaultTCPMSS

type mockClock time.Time

func (c *mockClock) Now() time.Time {
	return time.Time(*c)
}

func (c *mockClock) Advance(d time.Duration) {
	*c = mockClock(time.Time(*c).Add(d))
}

const MaxCongestionWindow = protocol.PacketNumber(200)

var _ = Describe("Cubic Sender", func() {
	var (
		sender            SendAlgorithmWithDebugInfo
		clock             mockClock
		bytesInFlight     protocol.ByteCount
		packetNumber      protocol.PacketNumber
		ackedPacketNumber protocol.PacketNumber
		rttStats          *RTTStats
	)

	BeforeEach(func() {
		bytesInFlight = 0
		packetNumber = 1
		ackedPacketNumber = 0
		clock = mockClock{}
		rttStats = NewRTTStats()
		sender = NewCubicSender(&clock, rttStats, true /*reno*/, initialCongestionWindowPackets, MaxCongestionWindow)
	})

	SendAvailableSendWindowLen := func(packetLength protocol.ByteCount) int {
		// Send as long as TimeUntilSend returns Zero.
		packets_sent := 0
		can_send := sender.TimeUntilSend(clock.Now(), bytesInFlight) == 0
		for can_send {
			sender.OnPacketSent(clock.Now(), bytesInFlight, packetNumber, packetLength, true)
			packetNumber++
			packets_sent++
			bytesInFlight += packetLength
			can_send = sender.TimeUntilSend(clock.Now(), bytesInFlight) == 0
		}
		return packets_sent
	}

	// Normal is that TCP acks every other segment.
	AckNPacketsLen := func(n int, packetLength protocol.ByteCount) {
		rttStats.UpdateRTT(60*time.Millisecond, 0, clock.Now())
		sender.MaybeExitSlowStart()
		for i := 0; i < n; i++ {
			ackedPacketNumber++
			sender.OnPacketAcked(ackedPacketNumber, packetLength, bytesInFlight)
		}
		bytesInFlight -= protocol.ByteCount(n) * packetLength
		clock.Advance(time.Millisecond)
	}

	LoseNPacketsLen := func(n int, packetLength protocol.ByteCount) {
		for i := 0; i < n; i++ {
			ackedPacketNumber++
			sender.OnPacketLost(ackedPacketNumber, packetLength, bytesInFlight)
		}
		bytesInFlight -= protocol.ByteCount(n) * packetLength
	}

	// Does not increment acked_packet_number_.
	LosePacket := func(number protocol.PacketNumber) {
		sender.OnPacketLost(number, protocol.DefaultTCPMSS, bytesInFlight)
		bytesInFlight -= protocol.DefaultTCPMSS
	}

	SendAvailableSendWindow := func() int { return SendAvailableSendWindowLen(protocol.DefaultTCPMSS) }
	AckNPackets := func(n int) { AckNPacketsLen(n, protocol.DefaultTCPMSS) }
	LoseNPackets := func(n int) { LoseNPacketsLen(n, protocol.DefaultTCPMSS) }

	It("simpler sender", func() {
		// At startup make sure we are at the default.
		Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP))
		// At startup make sure we can send.
		Expect(sender.TimeUntilSend(clock.Now(), 0)).To(BeZero())
		// Make sure we can send.
		Expect(sender.TimeUntilSend(clock.Now(), 0)).To(BeZero())
		// And that window is un-affected.
		Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP))

		// Fill the send window with data, then verify that we can't send.
		SendAvailableSendWindow()
		Expect(sender.TimeUntilSend(clock.Now(), sender.GetCongestionWindow())).ToNot(BeZero())
	})

	It("application limited slow start", func() {
		// Send exactly 10 packets and ensure the CWND ends at 14 packets.
		const kNumberOfAcks = 5
		// At startup make sure we can send.
		Expect(sender.TimeUntilSend(clock.Now(), 0)).To(BeZero())
		// Make sure we can send.
		Expect(sender.TimeUntilSend(clock.Now(), 0)).To(BeZero())

		SendAvailableSendWindow()
		for i := 0; i < kNumberOfAcks; i++ {
			AckNPackets(2)
		}
		bytesToSend := sender.GetCongestionWindow()
		// It's expected 2 acks will arrive when the bytes_in_flight are greater than
		// half the CWND.
		Expect(bytesToSend).To(Equal(defaultWindowTCP + protocol.DefaultTCPMSS*2*2))
	})

	It("exponential slow start", func() {
		const kNumberOfAcks = 20
		// At startup make sure we can send.
		Expect(sender.TimeUntilSend(clock.Now(), 0)).To(BeZero())
		Expect(sender.BandwidthEstimate()).To(BeZero())
		// Make sure we can send.
		Expect(sender.TimeUntilSend(clock.Now(), 0)).To(BeZero())

		for i := 0; i < kNumberOfAcks; i++ {
			// Send our full send window.
			SendAvailableSendWindow()
			AckNPackets(2)
		}
		cwnd := sender.GetCongestionWindow()
		Expect(cwnd).To(Equal(defaultWindowTCP + protocol.DefaultTCPMSS*2*kNumberOfAcks))
		Expect(sender.BandwidthEstimate()).To(Equal(BandwidthFromDelta(cwnd, rttStats.SmoothedRTT())))
	})

	It("slow start packet loss", func() {
		sender.SetNumEmulatedConnections(1)
		const kNumberOfAcks = 10
		for i := 0; i < kNumberOfAcks; i++ {
			// Send our full send window.
			SendAvailableSendWindow()
			AckNPackets(2)
		}
		SendAvailableSendWindow()
		expected_send_window := defaultWindowTCP + (protocol.DefaultTCPMSS * 2 * kNumberOfAcks)
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Lose a packet to exit slow start.
		LoseNPackets(1)
		packets_in_recovery_window := expected_send_window / protocol.DefaultTCPMSS

		// We should now have fallen out of slow start with a reduced window.
		expected_send_window = protocol.ByteCount(float32(expected_send_window) * renoBeta)
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Recovery phase. We need to ack every packet in the recovery window before
		// we exit recovery.
		number_of_packets_in_window := expected_send_window / protocol.DefaultTCPMSS
		AckNPackets(int(packets_in_recovery_window))
		SendAvailableSendWindow()
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// We need to ack an entire window before we increase CWND by 1.
		AckNPackets(int(number_of_packets_in_window) - 2)
		SendAvailableSendWindow()
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Next ack should increase cwnd by 1.
		AckNPackets(1)
		expected_send_window += protocol.DefaultTCPMSS
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Now RTO and ensure slow start gets reset.
		Expect(sender.HybridSlowStart().Started()).To(BeTrue())
		sender.OnRetransmissionTimeout(true)
		Expect(sender.HybridSlowStart().Started()).To(BeFalse())
	})

	It("slow start packet loss with large reduction", func() {
		sender.SetSlowStartLargeReduction(true)

		sender.SetNumEmulatedConnections(1)
		const kNumberOfAcks = 10
		for i := 0; i < kNumberOfAcks; i++ {
			// Send our full send window.
			SendAvailableSendWindow()
			AckNPackets(2)
		}
		SendAvailableSendWindow()
		expected_send_window := defaultWindowTCP + (protocol.DefaultTCPMSS * 2 * kNumberOfAcks)
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Lose a packet to exit slow start. We should now have fallen out of
		// slow start with a window reduced by 1.
		LoseNPackets(1)
		expected_send_window -= protocol.DefaultTCPMSS
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Lose 5 packets in recovery and verify that congestion window is reduced
		// further.
		LoseNPackets(5)
		expected_send_window -= 5 * protocol.DefaultTCPMSS
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		packets_in_recovery_window := expected_send_window / protocol.DefaultTCPMSS

		// Recovery phase. We need to ack every packet in the recovery window before
		// we exit recovery.
		number_of_packets_in_window := expected_send_window / protocol.DefaultTCPMSS
		AckNPackets(int(packets_in_recovery_window))
		SendAvailableSendWindow()
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// We need to ack the rest of the window before cwnd increases by 1.
		AckNPackets(int(number_of_packets_in_window - 1))
		SendAvailableSendWindow()
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Next ack should increase cwnd by 1.
		AckNPackets(1)
		expected_send_window += protocol.DefaultTCPMSS
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Now RTO and ensure slow start gets reset.
		Expect(sender.HybridSlowStart().Started()).To(BeTrue())
		sender.OnRetransmissionTimeout(true)
		Expect(sender.HybridSlowStart().Started()).To(BeFalse())
	})

	It("slow start half packet loss with large reduction", func() {
		sender.SetSlowStartLargeReduction(true)

		sender.SetNumEmulatedConnections(1)
		const kNumberOfAcks = 10
		for i := 0; i < kNumberOfAcks; i++ {
			// Send our full send window in half sized packets.
			SendAvailableSendWindowLen(protocol.DefaultTCPMSS / 2)
			AckNPackets(2)
		}
		SendAvailableSendWindowLen(protocol.DefaultTCPMSS / 2)
		expected_send_window := defaultWindowTCP + (protocol.DefaultTCPMSS * 2 * kNumberOfAcks)
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Lose a packet to exit slow start. We should now have fallen out of
		// slow start with a window reduced by 1.
		LoseNPackets(1)
		expected_send_window -= protocol.DefaultTCPMSS
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Lose 10 packets in recovery and verify that congestion window is reduced
		// by 5 packets.
		LoseNPacketsLen(10, protocol.DefaultTCPMSS/2)
		expected_send_window -= 5 * protocol.DefaultTCPMSS
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))
	})

	It("no PRR when less than one packet in flight", func() {
		SendAvailableSendWindow()
		LoseNPackets(int(initialCongestionWindowPackets) - 1)
		AckNPackets(1)
		// PRR will allow 2 packets for every ack during recovery.
		Expect(SendAvailableSendWindow()).To(Equal(2))
		// Simulate abandoning all packets by supplying a bytes_in_flight of 0.
		// PRR should now allow a packet to be sent, even though prr's state
		// variables believe it has sent enough packets.
		Expect(sender.TimeUntilSend(clock.Now(), 0)).To(BeZero())
	})

	It("slow start packet loss PRR", func() {
		sender.SetNumEmulatedConnections(1)
		// Test based on the first example in RFC6937.
		// Ack 10 packets in 5 acks to raise the CWND to 20, as in the example.
		const kNumberOfAcks = 5
		for i := 0; i < kNumberOfAcks; i++ {
			// Send our full send window.
			SendAvailableSendWindow()
			AckNPackets(2)
		}
		SendAvailableSendWindow()
		expected_send_window := defaultWindowTCP + (protocol.DefaultTCPMSS * 2 * kNumberOfAcks)
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		LoseNPackets(1)

		// We should now have fallen out of slow start with a reduced window.
		send_window_before_loss := expected_send_window
		expected_send_window = protocol.ByteCount(float32(expected_send_window) * renoBeta)
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Testing TCP proportional rate reduction.
		// We should send packets paced over the received acks for the remaining
		// outstanding packets. The number of packets before we exit recovery is the
		// original CWND minus the packet that has been lost and the one which
		// triggered the loss.
		remaining_packets_in_recovery := send_window_before_loss/protocol.DefaultTCPMSS - 2

		for i := protocol.ByteCount(0); i < remaining_packets_in_recovery; i++ {
			AckNPackets(1)
			SendAvailableSendWindow()
			Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))
		}

		// We need to ack another window before we increase CWND by 1.
		number_of_packets_in_window := expected_send_window / protocol.DefaultTCPMSS
		for i := protocol.ByteCount(0); i < number_of_packets_in_window; i++ {
			AckNPackets(1)
			Expect(SendAvailableSendWindow()).To(Equal(1))
			Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))
		}

		AckNPackets(1)
		expected_send_window += protocol.DefaultTCPMSS
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))
	})

	It("slow start burst packet loss PRR", func() {
		sender.SetNumEmulatedConnections(1)
		// Test based on the second example in RFC6937, though we also implement
		// forward acknowledgements, so the first two incoming acks will trigger
		// PRR immediately.
		// Ack 20 packets in 10 acks to raise the CWND to 30.
		const kNumberOfAcks = 10
		for i := 0; i < kNumberOfAcks; i++ {
			// Send our full send window.
			SendAvailableSendWindow()
			AckNPackets(2)
		}
		SendAvailableSendWindow()
		expected_send_window := defaultWindowTCP + (protocol.DefaultTCPMSS * 2 * kNumberOfAcks)
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Lose one more than the congestion window reduction, so that after loss,
		// bytes_in_flight is lesser than the congestion window.
		send_window_after_loss := protocol.ByteCount(renoBeta * float32(expected_send_window))
		num_packets_to_lose := (expected_send_window-send_window_after_loss)/protocol.DefaultTCPMSS + 1
		LoseNPackets(int(num_packets_to_lose))
		// Immediately after the loss, ensure at least one packet can be sent.
		// Losses without subsequent acks can occur with timer based loss detection.
		Expect(sender.TimeUntilSend(clock.Now(), bytesInFlight)).To(BeZero())
		AckNPackets(1)

		// We should now have fallen out of slow start with a reduced window.
		expected_send_window = protocol.ByteCount(float32(expected_send_window) * renoBeta)
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Only 2 packets should be allowed to be sent, per PRR-SSRB
		Expect(SendAvailableSendWindow()).To(Equal(2))

		// Ack the next packet, which triggers another loss.
		LoseNPackets(1)
		AckNPackets(1)

		// Send 2 packets to simulate PRR-SSRB.
		Expect(SendAvailableSendWindow()).To(Equal(2))

		// Ack the next packet, which triggers another loss.
		LoseNPackets(1)
		AckNPackets(1)

		// Send 2 packets to simulate PRR-SSRB.
		Expect(SendAvailableSendWindow()).To(Equal(2))

		// Exit recovery and return to sending at the new rate.
		for i := 0; i < kNumberOfAcks; i++ {
			AckNPackets(1)
			Expect(SendAvailableSendWindow()).To(Equal(1))
		}
	})

	It("RTO congestion window", func() {
		Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP))
		Expect(sender.SlowstartThreshold()).To(Equal(MaxCongestionWindow))

		// Expect the window to decrease to the minimum once the RTO fires
		// and slow start threshold to be set to 1/2 of the CWND.
		sender.OnRetransmissionTimeout(true)
		Expect(sender.GetCongestionWindow()).To(Equal(protocol.ByteCount(2 * protocol.DefaultTCPMSS)))
		Expect(sender.SlowstartThreshold()).To(Equal(protocol.PacketNumber(5)))
	})

	It("RTO congestion window no retransmission", func() {
		Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP))

		// Expect the window to remain unchanged if the RTO fires but no
		// packets are retransmitted.
		sender.OnRetransmissionTimeout(false)
		Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP))
	})

	It("retransmission delay", func() {
		const kRttMs = 10 * time.Millisecond
		const kDeviationMs = 3 * time.Millisecond
		Expect(sender.RetransmissionDelay()).To(BeZero())

		rttStats.UpdateRTT(kRttMs, 0, clock.Now())

		// Initial value is to set the median deviation to half of the initial
		// rtt, the median in then multiplied by a factor of 4 and finally the
		// smoothed rtt is added which is the initial rtt.
		expected_delay := kRttMs + kRttMs/2*4
		Expect(sender.RetransmissionDelay()).To(Equal(expected_delay))

		for i := 0; i < 100; i++ {
			// run to make sure that we converge.
			rttStats.UpdateRTT(kRttMs+kDeviationMs, 0, clock.Now())
			rttStats.UpdateRTT(kRttMs-kDeviationMs, 0, clock.Now())
		}
		expected_delay = kRttMs + kDeviationMs*4

		Expect(rttStats.SmoothedRTT()).To(BeNumerically("~", kRttMs, time.Millisecond))
		Expect(sender.RetransmissionDelay()).To(BeNumerically("~", expected_delay, time.Millisecond))
		Expect(sender.BandwidthEstimate() / BytesPerSecond).To(Equal(Bandwidth(
			sender.GetCongestionWindow() * protocol.ByteCount(time.Second) / protocol.ByteCount(rttStats.SmoothedRTT()),
		)))
	})

	It("slow start max send window", func() {
		const kMaxCongestionWindowTCP = 50
		const kNumberOfAcks = 100
		sender = NewCubicSender(&clock, rttStats, false, initialCongestionWindowPackets, kMaxCongestionWindowTCP)

		for i := 0; i < kNumberOfAcks; i++ {
			// Send our full send window.
			SendAvailableSendWindow()
			AckNPackets(2)
		}
		expected_send_window := kMaxCongestionWindowTCP * protocol.DefaultTCPMSS
		Expect(sender.GetCongestionWindow()).To(Equal(protocol.ByteCount(expected_send_window)))
	})

	It("tcp reno max congestion window", func() {
		const kMaxCongestionWindowTCP = 50
		const kNumberOfAcks = 1000
		sender = NewCubicSender(&clock, rttStats, false, initialCongestionWindowPackets, kMaxCongestionWindowTCP)

		SendAvailableSendWindow()
		AckNPackets(2)
		// Make sure we fall out of slow start.
		LoseNPackets(1)

		for i := 0; i < kNumberOfAcks; i++ {
			// Send our full send window.
			SendAvailableSendWindow()
			AckNPackets(2)
		}

		expected_send_window := kMaxCongestionWindowTCP * protocol.DefaultTCPMSS
		Expect(sender.GetCongestionWindow()).To(Equal(protocol.ByteCount(expected_send_window)))
	})

	It("tcp cubic max congestion window", func() {
		const kMaxCongestionWindowTCP = 50
		// Set to 10000 to compensate for small cubic alpha.
		const kNumberOfAcks = 10000

		sender = NewCubicSender(&clock, rttStats, false, initialCongestionWindowPackets, kMaxCongestionWindowTCP)

		SendAvailableSendWindow()
		AckNPackets(2)
		// Make sure we fall out of slow start.
		LoseNPackets(1)

		for i := 0; i < kNumberOfAcks; i++ {
			// Send our full send window.
			SendAvailableSendWindow()
			AckNPackets(2)
		}

		expected_send_window := kMaxCongestionWindowTCP * protocol.DefaultTCPMSS
		Expect(sender.GetCongestionWindow()).To(Equal(protocol.ByteCount(expected_send_window)))
	})

	It("tcp cubic reset epoch on quiescence", func() {
		const kMaxCongestionWindow = 50
		const kMaxCongestionWindowBytes = kMaxCongestionWindow * protocol.DefaultTCPMSS
		sender = NewCubicSender(&clock, rttStats, false, initialCongestionWindowPackets, kMaxCongestionWindow)

		num_sent := SendAvailableSendWindow()

		// Make sure we fall out of slow start.
		saved_cwnd := sender.GetCongestionWindow()
		LoseNPackets(1)
		Expect(saved_cwnd).To(BeNumerically(">", sender.GetCongestionWindow()))

		// Ack the rest of the outstanding packets to get out of recovery.
		for i := 1; i < num_sent; i++ {
			AckNPackets(1)
		}
		Expect(bytesInFlight).To(BeZero())

		// Send a new window of data and ack all; cubic growth should occur.
		saved_cwnd = sender.GetCongestionWindow()
		num_sent = SendAvailableSendWindow()
		for i := 0; i < num_sent; i++ {
			AckNPackets(1)
		}
		Expect(saved_cwnd).To(BeNumerically("<", sender.GetCongestionWindow()))
		Expect(kMaxCongestionWindowBytes).To(BeNumerically(">", sender.GetCongestionWindow()))
		Expect(bytesInFlight).To(BeZero())

		// Quiescent time of 100 seconds
		clock.Advance(100 * time.Second)

		// Send new window of data and ack one packet. Cubic epoch should have
		// been reset; ensure cwnd increase is not dramatic.
		saved_cwnd = sender.GetCongestionWindow()
		SendAvailableSendWindow()
		AckNPackets(1)
		Expect(saved_cwnd).To(BeNumerically("~", sender.GetCongestionWindow(), protocol.DefaultTCPMSS))
		Expect(kMaxCongestionWindowBytes).To(BeNumerically(">", sender.GetCongestionWindow()))
	})

	It("tcp cubic shifted epoch on quiescence", func() {
		const kMaxCongestionWindow = 50
		const kMaxCongestionWindowBytes = kMaxCongestionWindow * protocol.DefaultTCPMSS
		sender = NewCubicSender(&clock, rttStats, false, initialCongestionWindowPackets, kMaxCongestionWindow)

		num_sent := SendAvailableSendWindow()

		// Make sure we fall out of slow start.
		saved_cwnd := sender.GetCongestionWindow()
		LoseNPackets(1)
		Expect(saved_cwnd).To(BeNumerically(">", sender.GetCongestionWindow()))

		// Ack the rest of the outstanding packets to get out of recovery.
		for i := 1; i < num_sent; i++ {
			AckNPackets(1)
		}
		Expect(bytesInFlight).To(BeZero())

		// Send a new window of data and ack all; cubic growth should occur.
		saved_cwnd = sender.GetCongestionWindow()
		num_sent = SendAvailableSendWindow()
		for i := 0; i < num_sent; i++ {
			AckNPackets(1)
		}
		Expect(saved_cwnd).To(BeNumerically("<", sender.GetCongestionWindow()))
		Expect(kMaxCongestionWindowBytes).To(BeNumerically(">", sender.GetCongestionWindow()))
		Expect(bytesInFlight).To(BeZero())

		// Quiescent time of 100 seconds
		clock.Advance(100 * time.Second)

		// Send new window of data and ack one packet. Cubic epoch should have
		// been reset; ensure cwnd increase is not dramatic.
		saved_cwnd = sender.GetCongestionWindow()
		SendAvailableSendWindow()
		AckNPackets(1)
		Expect(saved_cwnd).To(BeNumerically("~", sender.GetCongestionWindow(), protocol.DefaultTCPMSS))
		Expect(kMaxCongestionWindowBytes).To(BeNumerically(">", sender.GetCongestionWindow()))
	})

	It("multiple losses in one window", func() {
		SendAvailableSendWindow()
		initial_window := sender.GetCongestionWindow()
		LosePacket(ackedPacketNumber + 1)
		post_loss_window := sender.GetCongestionWindow()
		Expect(initial_window).To(BeNumerically(">", post_loss_window))
		LosePacket(ackedPacketNumber + 3)
		Expect(sender.GetCongestionWindow()).To(Equal(post_loss_window))
		LosePacket(packetNumber - 1)
		Expect(sender.GetCongestionWindow()).To(Equal(post_loss_window))

		// Lose a later packet and ensure the window decreases.
		LosePacket(packetNumber)
		Expect(post_loss_window).To(BeNumerically(">", sender.GetCongestionWindow()))
	})

	It("don't track ack packets", func() {
		// Send a packet with no retransmittable data, and ensure it's not tracked.
		Expect(sender.OnPacketSent(clock.Now(), bytesInFlight, packetNumber, protocol.DefaultTCPMSS, false)).To(BeFalse())
		packetNumber++

		// Send a data packet with retransmittable data, and ensure it is tracked.
		Expect(sender.OnPacketSent(clock.Now(), bytesInFlight, packetNumber, protocol.DefaultTCPMSS, true)).To(BeTrue())
	})

	// TEST_F(TcpCubicSenderPacketsTest, ConfigureInitialWindow) {
	//   QuicConfig config;
	//
	//   QuicTagVector options;
	//   options.push_back(kIW03);
	//   QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
	//   sender.SetFromConfig(config, Perspective::IS_SERVER);
	//   Expect( sender.congestion_window()).To(Equal(3u))
	//
	//   options.clear();
	//   options.push_back(kIW10);
	//   QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
	//   sender.SetFromConfig(config, Perspective::IS_SERVER);
	//   Expect( sender.congestion_window()).To(Equal(10u))
	//
	//   options.clear();
	//   options.push_back(kIW20);
	//   QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
	//   sender.SetFromConfig(config, Perspective::IS_SERVER);
	//   Expect( sender.congestion_window()).To(Equal(20u))
	//
	//   options.clear();
	//   options.push_back(kIW50);
	//   QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
	//   sender.SetFromConfig(config, Perspective::IS_SERVER);
	//   Expect( sender.congestion_window()).To(Equal(50u))
	// }
	//
	// TEST_F(TcpCubicSenderPacketsTest, ConfigureMinimumWindow) {
	//   QuicConfig config;
	//
	//   // Verify that kCOPT: kMIN1 forces the min CWND to 1 packet.
	//   QuicTagVector options;
	//   options.push_back(kMIN1);
	//   QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
	//   sender.SetFromConfig(config, Perspective::IS_SERVER);
	//   sender.OnRetransmissionTimeout(true);
	//   Expect( sender.congestion_window()).To(Equal(1u))
	// }

	It("2 connection congestion avoidance at end of recovery", func() {
		sender.SetNumEmulatedConnections(2)
		// Ack 10 packets in 5 acks to raise the CWND to 20.
		const kNumberOfAcks = 5
		for i := 0; i < kNumberOfAcks; i++ {
			// Send our full send window.
			SendAvailableSendWindow()
			AckNPackets(2)
		}
		SendAvailableSendWindow()
		expected_send_window := defaultWindowTCP + (protocol.DefaultTCPMSS * 2 * kNumberOfAcks)
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		LoseNPackets(1)

		// We should now have fallen out of slow start with a reduced window.
		expected_send_window = protocol.ByteCount(float32(expected_send_window) * sender.RenoBeta())
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// No congestion window growth should occur in recovery phase, i.e., until the
		// currently outstanding 20 packets are acked.
		for i := 0; i < 10; i++ {
			// Send our full send window.
			SendAvailableSendWindow()
			Expect(sender.InRecovery()).To(BeTrue())
			AckNPackets(2)
			Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))
		}
		Expect(sender.InRecovery()).To(BeFalse())

		// Out of recovery now. Congestion window should not grow for half an RTT.
		packets_in_send_window := expected_send_window / protocol.DefaultTCPMSS
		SendAvailableSendWindow()
		AckNPackets(int(packets_in_send_window/2 - 2))
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Next ack should increase congestion window by 1MSS.
		SendAvailableSendWindow()
		AckNPackets(2)
		expected_send_window += protocol.DefaultTCPMSS
		packets_in_send_window += 1
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Congestion window should remain steady again for half an RTT.
		SendAvailableSendWindow()
		AckNPackets(int(packets_in_send_window/2 - 1))
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Next ack should cause congestion window to grow by 1MSS.
		SendAvailableSendWindow()
		AckNPackets(2)
		expected_send_window += protocol.DefaultTCPMSS
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))
	})

	It("1 connection congestion avoidance at end of recovery", func() {
		sender.SetNumEmulatedConnections(1)
		// Ack 10 packets in 5 acks to raise the CWND to 20.
		const kNumberOfAcks = 5
		for i := 0; i < kNumberOfAcks; i++ {
			// Send our full send window.
			SendAvailableSendWindow()
			AckNPackets(2)
		}
		SendAvailableSendWindow()
		expected_send_window := defaultWindowTCP + (protocol.DefaultTCPMSS * 2 * kNumberOfAcks)
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		LoseNPackets(1)

		// We should now have fallen out of slow start with a reduced window.
		expected_send_window = protocol.ByteCount(float32(expected_send_window) * renoBeta)
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// No congestion window growth should occur in recovery phase, i.e., until the
		// currently outstanding 20 packets are acked.
		for i := 0; i < 10; i++ {
			// Send our full send window.
			SendAvailableSendWindow()
			Expect(sender.InRecovery()).To(BeTrue())
			AckNPackets(2)
			Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))
		}
		Expect(sender.InRecovery()).To(BeFalse())

		// Out of recovery now. Congestion window should not grow during RTT.
		for i := protocol.ByteCount(0); i < expected_send_window/protocol.DefaultTCPMSS-2; i += 2 {
			// Send our full send window.
			SendAvailableSendWindow()
			AckNPackets(2)
			Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))
		}

		// Next ack should cause congestion window to grow by 1MSS.
		SendAvailableSendWindow()
		AckNPackets(2)
		expected_send_window += protocol.DefaultTCPMSS
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))
	})

	// TEST_F(TcpCubicSenderPacketsTest, BandwidthResumption) {
	//   // Test that when provided with CachedNetworkParameters and opted in to the
	//   // bandwidth resumption experiment, that the TcpCubicSenderPackets sets
	//   // initial CWND appropriately.
	//
	//   // Set some common values.
	//   CachedNetworkParameters cached_network_params;
	//   const QuicPacketCount kNumberOfPackets = 123;
	//   const int kBandwidthEstimateBytesPerSecond =
	//       kNumberOfPackets * protocol.DefaultTCPMSS;
	//   cached_network_params.set_bandwidth_estimate_bytes_per_second(
	//       kBandwidthEstimateBytesPerSecond);
	//   cached_network_params.set_min_rtt_ms(1000);
	//
	//   // Make sure that a bandwidth estimate results in a changed CWND.
	//   cached_network_params.set_timestamp(clock.WallNow().ToUNIXSeconds() -
	//                                       (kNumSecondsPerHour - 1));
	//   sender.ResumeConnectionState(cached_network_params, false);
	//   Expect( sender.congestion_window()).To(Equal(kNumberOfPackets))
	//
	//   // Resumed CWND is limited to be in a sensible range.
	//   cached_network_params.set_bandwidth_estimate_bytes_per_second(
	//       (kMaxCongestionWindow + 1) * protocol.DefaultTCPMSS);
	//   sender.ResumeConnectionState(cached_network_params, false);
	//   Expect( sender.congestion_window()).To(Equal(kMaxCongestionWindow))
	//
	//   cached_network_params.set_bandwidth_estimate_bytes_per_second(
	//       (kMinCongestionWindowForBandwidthResumption - 1) * protocol.DefaultTCPMSS);
	//   sender.ResumeConnectionState(cached_network_params, false);
	//   EXPECT_EQ(kMinCongestionWindowForBandwidthResumption,
	//             sender.congestion_window());
	//
	//   // Resume to the max value.
	//   cached_network_params.set_max_bandwidth_estimate_bytes_per_second(
	//       (kMinCongestionWindowForBandwidthResumption + 10) * protocol.DefaultTCPMSS);
	//   sender.ResumeConnectionState(cached_network_params, true);
	//   EXPECT_EQ((kMinCongestionWindowForBandwidthResumption + 10) * protocol.DefaultTCPMSS,
	//             sender.GetCongestionWindow());
	// }
	//
	// TEST_F(TcpCubicSenderPacketsTest, PaceBelowCWND) {
	//   QuicConfig config;
	//
	//   // Verify that kCOPT: kMIN4 forces the min CWND to 1 packet, but allows up
	//   // to 4 to be sent.
	//   QuicTagVector options;
	//   options.push_back(kMIN4);
	//   QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
	//   sender.SetFromConfig(config, Perspective::IS_SERVER);
	//   sender.OnRetransmissionTimeout(true);
	//   Expect( sender.congestion_window()).To(Equal(1u))
	//   EXPECT_TRUE(
	//       sender.TimeUntilSend(QuicTime::Zero(), protocol.DefaultTCPMSS).IsZero());
	//   EXPECT_TRUE(
	//       sender.TimeUntilSend(QuicTime::Zero(), 2 * protocol.DefaultTCPMSS).IsZero());
	//   EXPECT_TRUE(
	//       sender.TimeUntilSend(QuicTime::Zero(), 3 * protocol.DefaultTCPMSS).IsZero());
	//   EXPECT_FALSE(
	//       sender.TimeUntilSend(QuicTime::Zero(), 4 * protocol.DefaultTCPMSS).IsZero());
	// }

	It("reset after connection migration", func() {
		Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP))
		Expect(sender.SlowstartThreshold()).To(Equal(MaxCongestionWindow))

		// Starts with slow start.
		sender.SetNumEmulatedConnections(1)
		const kNumberOfAcks = 10
		for i := 0; i < kNumberOfAcks; i++ {
			// Send our full send window.
			SendAvailableSendWindow()
			AckNPackets(2)
		}
		SendAvailableSendWindow()
		expected_send_window := defaultWindowTCP + (protocol.DefaultTCPMSS * 2 * kNumberOfAcks)
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))

		// Loses a packet to exit slow start.
		LoseNPackets(1)

		// We should now have fallen out of slow start with a reduced window. Slow
		// start threshold is also updated.
		expected_send_window = protocol.ByteCount(float32(expected_send_window) * renoBeta)
		Expect(sender.GetCongestionWindow()).To(Equal(expected_send_window))
		Expect(sender.SlowstartThreshold()).To(Equal(protocol.PacketNumber(expected_send_window / protocol.DefaultTCPMSS)))

		// Resets cwnd and slow start threshold on connection migrations.
		sender.OnConnectionMigration()
		Expect(sender.GetCongestionWindow()).To(Equal(defaultWindowTCP))
		Expect(sender.SlowstartThreshold()).To(Equal(MaxCongestionWindow))
		Expect(sender.HybridSlowStart().Started()).To(BeFalse())
	})
})
