1package quic
2
3import (
4	"time"
5
6	"github.com/lucas-clemente/quic-go/internal/ackhandler"
7	"github.com/lucas-clemente/quic-go/internal/protocol"
8	"github.com/lucas-clemente/quic-go/internal/utils"
9	"github.com/lucas-clemente/quic-go/internal/wire"
10)
11
12type mtuDiscoverer interface {
13	ShouldSendProbe(now time.Time) bool
14	NextProbeTime() time.Time
15	GetPing() (ping ackhandler.Frame, datagramSize protocol.ByteCount)
16}
17
18const (
19	// At some point, we have to stop searching for a higher MTU.
20	// We're happy to send a packet that's 10 bytes smaller than the actual MTU.
21	maxMTUDiff = 20
22	// send a probe packet every mtuProbeDelay RTTs
23	mtuProbeDelay = 5
24)
25
26type mtuFinder struct {
27	lastProbeTime time.Time
28	probeInFlight bool
29	mtuIncreased  func(protocol.ByteCount)
30
31	rttStats *utils.RTTStats
32	current  protocol.ByteCount
33	max      protocol.ByteCount // the maximum value, as advertised by the peer (or our maximum size buffer)
34}
35
36var _ mtuDiscoverer = &mtuFinder{}
37
38func newMTUDiscoverer(rttStats *utils.RTTStats, start, max protocol.ByteCount, mtuIncreased func(protocol.ByteCount)) mtuDiscoverer {
39	return &mtuFinder{
40		current:       start,
41		rttStats:      rttStats,
42		lastProbeTime: time.Now(), // to make sure the first probe packet is not sent immediately
43		mtuIncreased:  mtuIncreased,
44		max:           max,
45	}
46}
47
48func (f *mtuFinder) done() bool {
49	return f.max-f.current <= maxMTUDiff+1
50}
51
52func (f *mtuFinder) ShouldSendProbe(now time.Time) bool {
53	if f.probeInFlight || f.done() {
54		return false
55	}
56	return !now.Before(f.NextProbeTime())
57}
58
59// NextProbeTime returns the time when the next probe packet should be sent.
60// It returns the zero value if no probe packet should be sent.
61func (f *mtuFinder) NextProbeTime() time.Time {
62	if f.probeInFlight || f.done() {
63		return time.Time{}
64	}
65	return f.lastProbeTime.Add(mtuProbeDelay * f.rttStats.SmoothedRTT())
66}
67
68func (f *mtuFinder) GetPing() (ackhandler.Frame, protocol.ByteCount) {
69	size := (f.max + f.current) / 2
70	f.lastProbeTime = time.Now()
71	f.probeInFlight = true
72	return ackhandler.Frame{
73		Frame: &wire.PingFrame{},
74		OnLost: func(wire.Frame) {
75			f.probeInFlight = false
76			f.max = size
77		},
78		OnAcked: func(wire.Frame) {
79			f.probeInFlight = false
80			f.current = size
81			f.mtuIncreased(size)
82		},
83	}, size
84}
85