|
1 | 1 | package ackhandler |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "encoding/binary" |
4 | 5 | "fmt" |
| 6 | + "math/rand/v2" |
5 | 7 | "slices" |
6 | 8 | "testing" |
7 | 9 | "time" |
@@ -39,6 +41,11 @@ type packetTracker struct { |
39 | 41 | Lost []protocol.PacketNumber |
40 | 42 | } |
41 | 43 |
|
| 44 | +func (t *packetTracker) Reset() { |
| 45 | + t.Acked = nil |
| 46 | + t.Lost = nil |
| 47 | +} |
| 48 | + |
42 | 49 | func (t *packetTracker) NewPingFrame(pn protocol.PacketNumber) Frame { |
43 | 50 | return Frame{ |
44 | 51 | Frame: &wire.PingFrame{}, |
@@ -1227,3 +1234,91 @@ func TestSentPacketHandlerPathProbeAckAndLoss(t *testing.T) { |
1227 | 1234 |
|
1228 | 1235 | require.Equal(t, t2.Add(pathProbePacketLossTimeout), sph.GetLossDetectionTimeout()) |
1229 | 1236 | } |
| 1237 | + |
| 1238 | +// The packet tracking logic is pretty complex. |
| 1239 | +// We test it with a randomized approach, to make sure that it doesn't panic under any circumstances. |
| 1240 | +func TestSentPacketHandlerRandomized(t *testing.T) { |
| 1241 | + seed := uint64(time.Now().UnixNano()) |
| 1242 | + for i := range 5 { |
| 1243 | + t.Run(fmt.Sprintf("run %d (seed %d)", i+1, seed), func(t *testing.T) { |
| 1244 | + testSentPacketHandlerRandomized(t, seed) |
| 1245 | + }) |
| 1246 | + seed++ |
| 1247 | + } |
| 1248 | +} |
| 1249 | + |
| 1250 | +func testSentPacketHandlerRandomized(t *testing.T, seed uint64) { |
| 1251 | + var b [32]byte |
| 1252 | + binary.BigEndian.PutUint64(b[:], seed) |
| 1253 | + r := rand.New(rand.NewChaCha8(b)) |
| 1254 | + |
| 1255 | + var rttStats utils.RTTStats |
| 1256 | + rtt := []time.Duration{10 * time.Millisecond, 100 * time.Millisecond, 1000 * time.Millisecond}[r.IntN(3)] |
| 1257 | + t.Logf("rtt: %dms", rtt.Milliseconds()) |
| 1258 | + rttStats.UpdateRTT(rtt, 0) // RTT of the original path |
| 1259 | + |
| 1260 | + randDuration := func(min, max time.Duration) time.Duration { |
| 1261 | + return time.Duration(rand.Int64N(int64(max-min))) + min |
| 1262 | + } |
| 1263 | + |
| 1264 | + sph := newSentPacketHandler( |
| 1265 | + 0, |
| 1266 | + 1200, |
| 1267 | + &rttStats, |
| 1268 | + true, |
| 1269 | + false, |
| 1270 | + protocol.PerspectiveClient, |
| 1271 | + nil, |
| 1272 | + utils.DefaultLogger, |
| 1273 | + ) |
| 1274 | + sph.DropPackets(protocol.EncryptionInitial, time.Now()) |
| 1275 | + sph.DropPackets(protocol.EncryptionHandshake, time.Now()) |
| 1276 | + |
| 1277 | + var packets packetTracker |
| 1278 | + sendPacket := func(ti time.Time, isPathProbe bool) protocol.PacketNumber { |
| 1279 | + pn := sph.PopPacketNumber(protocol.Encryption1RTT) |
| 1280 | + sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, protocol.Encryption1RTT, protocol.ECNNon, 1200, false, isPathProbe) |
| 1281 | + return pn |
| 1282 | + } |
| 1283 | + |
| 1284 | + now := time.Now() |
| 1285 | + start := now |
| 1286 | + var pns []protocol.PacketNumber |
| 1287 | + for range 4 { |
| 1288 | + isProbe := r.Int()%2 == 0 |
| 1289 | + pn := sendPacket(now, isProbe) |
| 1290 | + t.Logf("t=%dms: sending packet %d (probe packet: %t)", now.Sub(start).Milliseconds(), pn, isProbe) |
| 1291 | + pns = append(pns, pn) |
| 1292 | + now = now.Add(randDuration(0, 500*time.Millisecond)) |
| 1293 | + if r.Int()%3 == 0 { |
| 1294 | + sph.OnLossDetectionTimeout(now) |
| 1295 | + t.Logf("t=%dms: loss detection timeout (lost: %v)", now.Sub(start).Milliseconds(), packets.Lost) |
| 1296 | + packets.Reset() |
| 1297 | + now = now.Add(randDuration(0, 500*time.Millisecond)) |
| 1298 | + } |
| 1299 | + if r.Int()%3 == 0 { |
| 1300 | + // acknowledge up to 2 random packet numbers from the pns slice |
| 1301 | + var ackPns []protocol.PacketNumber |
| 1302 | + if len(pns) > 0 { |
| 1303 | + numToAck := min(1+r.IntN(2), len(pns)) |
| 1304 | + for range numToAck { |
| 1305 | + ackPns = append(ackPns, pns[r.IntN(len(pns))]) |
| 1306 | + } |
| 1307 | + } |
| 1308 | + if len(ackPns) > 1 { |
| 1309 | + slices.Sort(ackPns) |
| 1310 | + ackPns = slices.Compact(ackPns) |
| 1311 | + } |
| 1312 | + sph.ReceivedAck(&wire.AckFrame{AckRanges: ackRanges(ackPns...)}, protocol.Encryption1RTT, now) |
| 1313 | + t.Logf("t=%dms: received ACK for packets %v (acked: %v, lost: %v)", now.Sub(start).Milliseconds(), ackPns, packets.Acked, packets.Lost) |
| 1314 | + packets.Reset() |
| 1315 | + now = now.Add(randDuration(0, 500*time.Millisecond)) |
| 1316 | + } |
| 1317 | + if r.Int()%10 == 0 { |
| 1318 | + sph.MigratedPath(now, 1200) |
| 1319 | + now = now.Add(randDuration(0, 500*time.Millisecond)) |
| 1320 | + } |
| 1321 | + } |
| 1322 | + t.Logf("t=%dms: loss detection timeout (lost: %v)", now.Sub(start).Milliseconds(), packets.Lost) |
| 1323 | + sph.OnLossDetectionTimeout(now) |
| 1324 | +} |
0 commit comments