forked from quic-go/quic-go
* ackhandler: use an interator to track received packet ranges * use slices.Collect * naming convention
305 lines
8.9 KiB
Go
305 lines
8.9 KiB
Go
package ackhandler
|
|
|
|
import (
|
|
"math/rand/v2"
|
|
"slices"
|
|
"testing"
|
|
|
|
"github.com/quic-go/quic-go/internal/protocol"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestReceivedPacketHistorySingleRange(t *testing.T) {
|
|
hist := newReceivedPacketHistory()
|
|
|
|
require.True(t, hist.ReceivedPacket(4))
|
|
require.Equal(t, []interval{{Start: 4, End: 4}}, slices.Collect(hist.Backward()))
|
|
|
|
// add a duplicate packet
|
|
require.False(t, hist.ReceivedPacket(4))
|
|
require.Equal(t, []interval{{Start: 4, End: 4}}, slices.Collect(hist.Backward()))
|
|
|
|
// add a few more packets to extend the range
|
|
require.True(t, hist.ReceivedPacket(5))
|
|
require.True(t, hist.ReceivedPacket(6))
|
|
require.Equal(t, []interval{{Start: 4, End: 6}}, slices.Collect(hist.Backward()))
|
|
|
|
// add a duplicate within this range
|
|
require.False(t, hist.ReceivedPacket(5))
|
|
require.Equal(t, []interval{{Start: 4, End: 6}}, slices.Collect(hist.Backward()))
|
|
|
|
// extend the range at the front
|
|
require.True(t, hist.ReceivedPacket(3))
|
|
require.Equal(t, []interval{{Start: 3, End: 6}}, slices.Collect(hist.Backward()))
|
|
}
|
|
|
|
func TestReceivedPacketHistoryRanges(t *testing.T) {
|
|
hist := newReceivedPacketHistory()
|
|
require.Equal(t, protocol.InvalidPacketNumber, hist.HighestMissingUpTo(1000))
|
|
|
|
require.True(t, hist.ReceivedPacket(4))
|
|
require.Equal(t, protocol.PacketNumber(3), hist.HighestMissingUpTo(1000))
|
|
require.Equal(t, protocol.PacketNumber(3), hist.HighestMissingUpTo(4))
|
|
require.Equal(t, protocol.PacketNumber(3), hist.HighestMissingUpTo(3))
|
|
require.Equal(t, protocol.PacketNumber(2), hist.HighestMissingUpTo(2))
|
|
require.True(t, hist.ReceivedPacket(10))
|
|
require.Equal(t, protocol.PacketNumber(9), hist.HighestMissingUpTo(1000))
|
|
require.Equal(t, []interval{
|
|
{Start: 10, End: 10},
|
|
{Start: 4, End: 4},
|
|
}, slices.Collect(hist.Backward()))
|
|
|
|
// create a new range in the middle
|
|
require.True(t, hist.ReceivedPacket(7))
|
|
require.Equal(t, []interval{
|
|
{Start: 10, End: 10},
|
|
{Start: 7, End: 7},
|
|
{Start: 4, End: 4},
|
|
}, slices.Collect(hist.Backward()))
|
|
|
|
// create a new range at the front
|
|
require.True(t, hist.ReceivedPacket(1))
|
|
require.Equal(t, []interval{
|
|
{Start: 10, End: 10},
|
|
{Start: 7, End: 7},
|
|
{Start: 4, End: 4},
|
|
{Start: 1, End: 1},
|
|
}, slices.Collect(hist.Backward()))
|
|
|
|
// extend an existing range at the end
|
|
require.True(t, hist.ReceivedPacket(8))
|
|
require.Equal(t, []interval{
|
|
{Start: 10, End: 10},
|
|
{Start: 7, End: 8},
|
|
{Start: 4, End: 4},
|
|
{Start: 1, End: 1},
|
|
}, slices.Collect(hist.Backward()))
|
|
|
|
// extend an existing range at the front
|
|
require.True(t, hist.ReceivedPacket(6))
|
|
require.Equal(t, []interval{
|
|
{Start: 10, End: 10},
|
|
{Start: 6, End: 8},
|
|
{Start: 4, End: 4},
|
|
{Start: 1, End: 1},
|
|
}, slices.Collect(hist.Backward()))
|
|
|
|
// close a range
|
|
require.True(t, hist.ReceivedPacket(9))
|
|
require.Equal(t, []interval{
|
|
{Start: 6, End: 10},
|
|
{Start: 4, End: 4},
|
|
{Start: 1, End: 1},
|
|
}, slices.Collect(hist.Backward()))
|
|
}
|
|
|
|
func TestReceivedPacketHistoryMaxNumAckRanges(t *testing.T) {
|
|
hist := newReceivedPacketHistory()
|
|
|
|
for i := range protocol.MaxNumAckRanges {
|
|
require.True(t, hist.ReceivedPacket(protocol.PacketNumber(2*i)))
|
|
}
|
|
require.Len(t, hist.ranges, protocol.MaxNumAckRanges)
|
|
require.Equal(t, interval{Start: 0, End: 0}, hist.ranges[0])
|
|
|
|
hist.ReceivedPacket(2*protocol.MaxNumAckRanges + 1000)
|
|
// check that the oldest ACK range was deleted
|
|
require.Len(t, hist.ranges, protocol.MaxNumAckRanges)
|
|
require.Equal(t, interval{Start: 2, End: 2}, hist.ranges[0])
|
|
}
|
|
|
|
func TestReceivedPacketHistoryDeleteBelow(t *testing.T) {
|
|
hist := newReceivedPacketHistory()
|
|
|
|
hist.DeleteBelow(2)
|
|
require.Empty(t, slices.Collect(hist.Backward()))
|
|
|
|
require.True(t, hist.ReceivedPacket(2))
|
|
require.True(t, hist.ReceivedPacket(4))
|
|
require.True(t, hist.ReceivedPacket(5))
|
|
require.True(t, hist.ReceivedPacket(6))
|
|
require.True(t, hist.ReceivedPacket(10))
|
|
|
|
require.Equal(t, protocol.PacketNumber(3), hist.HighestMissingUpTo(6))
|
|
hist.DeleteBelow(6)
|
|
require.Equal(t, protocol.InvalidPacketNumber, hist.HighestMissingUpTo(6))
|
|
require.Equal(t, protocol.PacketNumber(9), hist.HighestMissingUpTo(10))
|
|
require.Equal(t, []interval{
|
|
{Start: 10, End: 10},
|
|
{Start: 6, End: 6},
|
|
}, slices.Collect(hist.Backward()))
|
|
|
|
// deleting from an existing range
|
|
require.True(t, hist.ReceivedPacket(7))
|
|
require.True(t, hist.ReceivedPacket(8))
|
|
hist.DeleteBelow(7)
|
|
require.Equal(t, []interval{
|
|
{Start: 10, End: 10},
|
|
{Start: 7, End: 8},
|
|
}, slices.Collect(hist.Backward()))
|
|
|
|
// keep a one-packet range
|
|
hist.DeleteBelow(10)
|
|
require.Equal(t, []interval{{Start: 10, End: 10}}, slices.Collect(hist.Backward()))
|
|
|
|
// delayed packets below deleted ranges are ignored
|
|
require.False(t, hist.ReceivedPacket(5))
|
|
require.Equal(t, []interval{{Start: 10, End: 10}}, slices.Collect(hist.Backward()))
|
|
}
|
|
|
|
func TestReceivedPacketHistoryDuplicateDetection(t *testing.T) {
|
|
hist := newReceivedPacketHistory()
|
|
|
|
require.False(t, hist.IsPotentiallyDuplicate(5))
|
|
|
|
require.True(t, hist.ReceivedPacket(4))
|
|
require.True(t, hist.ReceivedPacket(5))
|
|
require.True(t, hist.ReceivedPacket(6))
|
|
require.True(t, hist.ReceivedPacket(8))
|
|
require.True(t, hist.ReceivedPacket(9))
|
|
|
|
require.False(t, hist.IsPotentiallyDuplicate(3))
|
|
require.True(t, hist.IsPotentiallyDuplicate(4))
|
|
require.True(t, hist.IsPotentiallyDuplicate(5))
|
|
require.True(t, hist.IsPotentiallyDuplicate(6))
|
|
require.False(t, hist.IsPotentiallyDuplicate(7))
|
|
require.True(t, hist.IsPotentiallyDuplicate(8))
|
|
require.True(t, hist.IsPotentiallyDuplicate(9))
|
|
require.False(t, hist.IsPotentiallyDuplicate(10))
|
|
|
|
// delete and check for potential duplicates
|
|
hist.DeleteBelow(8)
|
|
require.True(t, hist.IsPotentiallyDuplicate(7))
|
|
require.True(t, hist.IsPotentiallyDuplicate(8))
|
|
require.True(t, hist.IsPotentiallyDuplicate(9))
|
|
require.False(t, hist.IsPotentiallyDuplicate(10))
|
|
}
|
|
|
|
func TestReceivedPacketHistoryRandomized(t *testing.T) {
|
|
hist := newReceivedPacketHistory()
|
|
packets := make(map[protocol.PacketNumber]struct{})
|
|
const num = 2 * protocol.MaxNumAckRanges
|
|
numLostPackets := rand.IntN(protocol.MaxNumAckRanges)
|
|
numRcvdPackets := num - numLostPackets
|
|
|
|
for i := range num {
|
|
packets[protocol.PacketNumber(i)] = struct{}{}
|
|
}
|
|
lostPackets := make([]protocol.PacketNumber, 0, numLostPackets)
|
|
for len(lostPackets) < numLostPackets {
|
|
p := protocol.PacketNumber(rand.IntN(num - 1)) // lose a random packet, but not the last one
|
|
if _, ok := packets[p]; ok {
|
|
lostPackets = append(lostPackets, p)
|
|
delete(packets, p)
|
|
}
|
|
}
|
|
slices.Sort(lostPackets)
|
|
t.Logf("Losing packets: %v", lostPackets)
|
|
|
|
ordered := make([]protocol.PacketNumber, 0, numRcvdPackets)
|
|
for p := range packets {
|
|
ordered = append(ordered, p)
|
|
}
|
|
rand.Shuffle(len(ordered), func(i, j int) { ordered[i], ordered[j] = ordered[j], ordered[i] })
|
|
|
|
t.Logf("Receiving packets: %v", ordered)
|
|
for i, p := range ordered {
|
|
require.True(t, hist.ReceivedPacket(p))
|
|
// sometimes receive a duplicate
|
|
if i > 0 && rand.Int()%5 == 0 {
|
|
require.False(t, hist.ReceivedPacket(ordered[rand.IntN(i)]))
|
|
}
|
|
}
|
|
var counter int
|
|
ackRanges := slices.Collect(hist.Backward())
|
|
t.Logf("ACK ranges: %v", ackRanges)
|
|
require.LessOrEqual(t, len(ackRanges), numLostPackets+1)
|
|
for _, ackRange := range ackRanges {
|
|
for p := ackRange.Start; p <= ackRange.End; p++ {
|
|
counter++
|
|
require.Contains(t, packets, p)
|
|
}
|
|
}
|
|
require.Equal(t, numRcvdPackets, counter)
|
|
|
|
deletedBelow := protocol.PacketNumber(rand.IntN(num * 2 / 3))
|
|
t.Logf("Deleting below %d", deletedBelow)
|
|
hist.DeleteBelow(deletedBelow)
|
|
for pn := range protocol.PacketNumber(num) {
|
|
if pn < deletedBelow {
|
|
require.Equal(t, protocol.InvalidPacketNumber, hist.HighestMissingUpTo(pn))
|
|
continue
|
|
}
|
|
expected := protocol.InvalidPacketNumber
|
|
for _, lost := range lostPackets {
|
|
if lost < deletedBelow {
|
|
continue
|
|
}
|
|
if lost > pn {
|
|
break
|
|
}
|
|
expected = lost
|
|
}
|
|
hm := hist.HighestMissingUpTo(pn)
|
|
require.Equalf(t, expected, hm, "highest missing up to %d: %d", pn, hm)
|
|
}
|
|
}
|
|
|
|
func BenchmarkHistoryReceiveSequentialPackets(b *testing.B) {
|
|
hist := newReceivedPacketHistory()
|
|
var pn protocol.PacketNumber
|
|
for b.Loop() {
|
|
hist.ReceivedPacket(pn)
|
|
pn++
|
|
}
|
|
}
|
|
|
|
// Packets are received sequentially, with occasional gaps
|
|
func BenchmarkHistoryReceiveCommonCase(b *testing.B) {
|
|
hist := newReceivedPacketHistory()
|
|
var pn protocol.PacketNumber
|
|
for b.Loop() {
|
|
hist.ReceivedPacket(pn)
|
|
pn++
|
|
if pn%2000 == 0 {
|
|
pn += 4
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkHistoryReceiveSequentialPacketsWithGaps(b *testing.B) {
|
|
hist := newReceivedPacketHistory()
|
|
var pn protocol.PacketNumber
|
|
for b.Loop() {
|
|
hist.ReceivedPacket(pn)
|
|
pn += 2
|
|
}
|
|
}
|
|
|
|
func BenchmarkHistoryReceiveReversePacketsWithGaps(b *testing.B) {
|
|
hist := newReceivedPacketHistory()
|
|
for i := 0; i < b.N; i++ {
|
|
hist.ReceivedPacket(protocol.PacketNumber(2 * (b.N - i)))
|
|
}
|
|
}
|
|
|
|
func BenchmarkHistoryIsDuplicate(b *testing.B) {
|
|
b.ReportAllocs()
|
|
hist := newReceivedPacketHistory()
|
|
var pn protocol.PacketNumber
|
|
for range protocol.MaxNumAckRanges {
|
|
for range 5 {
|
|
hist.ReceivedPacket(pn)
|
|
pn++
|
|
}
|
|
pn += 5 // create a gap
|
|
}
|
|
|
|
var p protocol.PacketNumber
|
|
for b.Loop() {
|
|
hist.IsPotentiallyDuplicate(p % pn)
|
|
p++
|
|
}
|
|
}
|