package hysteria import ( "time" "github.com/sagernet/quic-go/congestion" ) const ( initMaxDatagramSize = 1252 pktInfoSlotCount = 4 minSampleCount = 50 minAckRate = 0.8 ) type BrutalSender struct { rttStats congestion.RTTStatsProvider bps congestion.ByteCount maxDatagramSize congestion.ByteCount pacer *pacer pktInfoSlots [pktInfoSlotCount]pktInfo ackRate float64 } type pktInfo struct { Timestamp int64 AckCount uint64 LossCount uint64 } func NewBrutalSender(bps congestion.ByteCount) *BrutalSender { bs := &BrutalSender{ bps: bps, maxDatagramSize: initMaxDatagramSize, ackRate: 1, } bs.pacer = newPacer(func() congestion.ByteCount { return congestion.ByteCount(float64(bs.bps) / bs.ackRate) }) return bs } func (b *BrutalSender) SetRTTStatsProvider(rttStats congestion.RTTStatsProvider) { b.rttStats = rttStats } func (b *BrutalSender) TimeUntilSend(bytesInFlight congestion.ByteCount) time.Time { return b.pacer.TimeUntilSend() } func (b *BrutalSender) HasPacingBudget() bool { return b.pacer.Budget(time.Now()) >= b.maxDatagramSize } func (b *BrutalSender) CanSend(bytesInFlight congestion.ByteCount) bool { return bytesInFlight < b.GetCongestionWindow() } func (b *BrutalSender) GetCongestionWindow() congestion.ByteCount { rtt := maxDuration(b.rttStats.LatestRTT(), b.rttStats.SmoothedRTT()) if rtt <= 0 { return 10240 } return congestion.ByteCount(float64(b.bps) * rtt.Seconds() * 1.5 / b.ackRate) } func (b *BrutalSender) OnPacketSent(sentTime time.Time, bytesInFlight congestion.ByteCount, packetNumber congestion.PacketNumber, bytes congestion.ByteCount, isRetransmittable bool, ) { b.pacer.SentPacket(sentTime, bytes) } func (b *BrutalSender) OnPacketAcked(number congestion.PacketNumber, ackedBytes congestion.ByteCount, priorInFlight congestion.ByteCount, eventTime time.Time, ) { currentTimestamp := eventTime.Unix() slot := currentTimestamp % pktInfoSlotCount if b.pktInfoSlots[slot].Timestamp == currentTimestamp { b.pktInfoSlots[slot].AckCount++ } else { // uninitialized slot or too old, reset b.pktInfoSlots[slot].Timestamp = currentTimestamp b.pktInfoSlots[slot].AckCount = 1 b.pktInfoSlots[slot].LossCount = 0 } b.updateAckRate(currentTimestamp) } func (b *BrutalSender) OnPacketLost(number congestion.PacketNumber, lostBytes congestion.ByteCount, priorInFlight congestion.ByteCount, ) { currentTimestamp := time.Now().Unix() slot := currentTimestamp % pktInfoSlotCount if b.pktInfoSlots[slot].Timestamp == currentTimestamp { b.pktInfoSlots[slot].LossCount++ } else { // uninitialized slot or too old, reset b.pktInfoSlots[slot].Timestamp = currentTimestamp b.pktInfoSlots[slot].AckCount = 0 b.pktInfoSlots[slot].LossCount = 1 } b.updateAckRate(currentTimestamp) } func (b *BrutalSender) SetMaxDatagramSize(size congestion.ByteCount) { b.maxDatagramSize = size b.pacer.SetMaxDatagramSize(size) } func (b *BrutalSender) updateAckRate(currentTimestamp int64) { minTimestamp := currentTimestamp - pktInfoSlotCount var ackCount, lossCount uint64 for _, info := range b.pktInfoSlots { if info.Timestamp < minTimestamp { continue } ackCount += info.AckCount lossCount += info.LossCount } if ackCount+lossCount < minSampleCount { b.ackRate = 1 } rate := float64(ackCount) / float64(ackCount+lossCount) if rate < minAckRate { b.ackRate = minAckRate } b.ackRate = rate } func (b *BrutalSender) InSlowStart() bool { return false } func (b *BrutalSender) InRecovery() bool { return false } func (b *BrutalSender) MaybeExitSlowStart() {} func (b *BrutalSender) OnRetransmissionTimeout(packetsRetransmitted bool) {} func maxDuration(a, b time.Duration) time.Duration { if a > b { return a } return b }