mirror of
https://github.com/DNSCrypt/dnscrypt-proxy.git
synced 2026-10-12 02:01:53 +00:00
Update quic-go
This commit is contained in:
@@ -24,7 +24,7 @@ require (
|
||||
github.com/lifenjoiner/dhcpdns v0.0.7
|
||||
github.com/miekg/dns v1.1.68
|
||||
github.com/powerman/check v1.9.0
|
||||
github.com/quic-go/quic-go v0.54.1
|
||||
github.com/quic-go/quic-go v0.55.0
|
||||
golang.org/x/crypto v0.42.0
|
||||
golang.org/x/net v0.44.0
|
||||
golang.org/x/sys v0.36.0
|
||||
|
||||
@@ -65,8 +65,8 @@ github.com/powerman/deepequal v0.1.0 h1:sVwtyTsBuYIvdbLR1O2wzRY63YgPqdGZmk/o80l+
|
||||
github.com/powerman/deepequal v0.1.0/go.mod h1:3k7aG/slufBhUANdN67o/UPg8i5YaiJ6FmibWX0cn04=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.54.1 h1:4ZAWm0AhCb6+hE+l5Q1NAL0iRn/ZrMwqHRGQiFwj2eg=
|
||||
github.com/quic-go/quic-go v0.54.1/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
|
||||
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
|
||||
github.com/smarty/assertions v1.16.0 h1:EvHNkdRA4QHMrn75NZSoUQ/mAUXAYWfatfB01yTCzfY=
|
||||
github.com/smarty/assertions v1.16.0/go.mod h1:duaaFdCS0K9dnoM50iyek/eYINOZ64gbh1Xlf6LG7AI=
|
||||
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||
|
||||
+1
@@ -8,6 +8,7 @@ coverage:
|
||||
- internal/mocks/
|
||||
- internal/utils/linkedlist/linkedlist.go
|
||||
- internal/testdata
|
||||
- internal/synctest
|
||||
- logging/connection_tracer_multiplexer.go
|
||||
- logging/tracer_multiplexer.go
|
||||
- testutils/
|
||||
|
||||
+8
-7
@@ -5,6 +5,7 @@ import (
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/qerr"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
@@ -38,7 +39,7 @@ func (cr connRunners) ReplaceWithClosed(ids []protocol.ConnectionID, b []byte, e
|
||||
}
|
||||
|
||||
type connIDToRetire struct {
|
||||
t time.Time
|
||||
t monotime.Time
|
||||
connID protocol.ConnectionID
|
||||
}
|
||||
|
||||
@@ -95,7 +96,7 @@ func (m *connIDGenerator) SetMaxActiveConnIDs(limit uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *connIDGenerator) Retire(seq uint64, sentWithDestConnID protocol.ConnectionID, expiry time.Time) error {
|
||||
func (m *connIDGenerator) Retire(seq uint64, sentWithDestConnID protocol.ConnectionID, expiry monotime.Time) error {
|
||||
if seq > m.highestSeq {
|
||||
return &qerr.TransportError{
|
||||
ErrorCode: qerr.ProtocolViolation,
|
||||
@@ -123,7 +124,7 @@ func (m *connIDGenerator) Retire(seq uint64, sentWithDestConnID protocol.Connect
|
||||
return m.issueNewConnID()
|
||||
}
|
||||
|
||||
func (m *connIDGenerator) queueConnIDForRetiring(connID protocol.ConnectionID, expiry time.Time) {
|
||||
func (m *connIDGenerator) queueConnIDForRetiring(connID protocol.ConnectionID, expiry monotime.Time) {
|
||||
idx := slices.IndexFunc(m.connIDsToRetire, func(c connIDToRetire) bool {
|
||||
return c.t.After(expiry)
|
||||
})
|
||||
@@ -149,21 +150,21 @@ func (m *connIDGenerator) issueNewConnID() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *connIDGenerator) SetHandshakeComplete(connIDExpiry time.Time) {
|
||||
func (m *connIDGenerator) SetHandshakeComplete(connIDExpiry monotime.Time) {
|
||||
if m.initialClientDestConnID != nil {
|
||||
m.queueConnIDForRetiring(*m.initialClientDestConnID, connIDExpiry)
|
||||
m.initialClientDestConnID = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *connIDGenerator) NextRetireTime() time.Time {
|
||||
func (m *connIDGenerator) NextRetireTime() monotime.Time {
|
||||
if len(m.connIDsToRetire) == 0 {
|
||||
return time.Time{}
|
||||
return 0
|
||||
}
|
||||
return m.connIDsToRetire[0].t
|
||||
}
|
||||
|
||||
func (m *connIDGenerator) RemoveRetiredConnIDs(now time.Time) {
|
||||
func (m *connIDGenerator) RemoveRetiredConnIDs(now monotime.Time) {
|
||||
if len(m.connIDsToRetire) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
+194
-67
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/quic-go/quic-go/internal/ackhandler"
|
||||
"github.com/quic-go/quic-go/internal/flowcontrol"
|
||||
"github.com/quic-go/quic-go/internal/handshake"
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/qerr"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
@@ -27,7 +28,7 @@ import (
|
||||
|
||||
type unpacker interface {
|
||||
UnpackLongHeader(hdr *wire.Header, data []byte) (*unpackedPacket, error)
|
||||
UnpackShortHeader(rcvTime time.Time, data []byte) (protocol.PacketNumber, protocol.PacketNumberLen, protocol.KeyPhaseBit, []byte, error)
|
||||
UnpackShortHeader(rcvTime monotime.Time, data []byte) (protocol.PacketNumber, protocol.PacketNumberLen, protocol.KeyPhaseBit, []byte, error)
|
||||
}
|
||||
|
||||
type cryptoStreamHandler interface {
|
||||
@@ -47,7 +48,7 @@ type receivedPacket struct {
|
||||
buffer *packetBuffer
|
||||
|
||||
remoteAddr net.Addr
|
||||
rcvTime time.Time
|
||||
rcvTime monotime.Time
|
||||
data []byte
|
||||
|
||||
ecn protocol.ECN
|
||||
@@ -90,9 +91,26 @@ func (e *errCloseForRecreating) Error() string {
|
||||
return "closing connection in order to recreate it"
|
||||
}
|
||||
|
||||
var deadlineSendImmediately = monotime.Time(42 * time.Millisecond) // any value > time.Time{} and before time.Now() is fine
|
||||
|
||||
var connTracingID atomic.Uint64 // to be accessed atomically
|
||||
func nextConnTracingID() ConnectionTracingID { return ConnectionTracingID(connTracingID.Add(1)) }
|
||||
|
||||
type blockMode uint8
|
||||
|
||||
const (
|
||||
// blockModeNone means that the connection is not blocked.
|
||||
blockModeNone blockMode = iota
|
||||
// blockModeCongestionLimited means that the connection is congestion limited.
|
||||
// In that case, we can still send acknowledgments and PTO probe packets.
|
||||
blockModeCongestionLimited
|
||||
// blockModeHardBlocked means that no packet can be sent, under no circumstances. This can happen when:
|
||||
// * the send queue is full
|
||||
// * the SentPacketHandler returns SendNone, e.g. when we are tracking the maximum number of packets
|
||||
// In that case, the timer will be set to the idle timeout.
|
||||
blockModeHardBlocked
|
||||
)
|
||||
|
||||
// A Conn is a QUIC connection between two peers.
|
||||
// Calls to the connection (and to streams) can return the following types of errors:
|
||||
// - [ApplicationError]: for errors triggered by the application running on top of QUIC
|
||||
@@ -127,7 +145,8 @@ type Conn struct {
|
||||
connIDManager *connIDManager
|
||||
connIDGenerator *connIDGenerator
|
||||
|
||||
rttStats *utils.RTTStats
|
||||
rttStats *utils.RTTStats
|
||||
connStats utils.ConnectionStats
|
||||
|
||||
cryptoStreamManager *cryptoStreamManager
|
||||
sentPacketHandler ackhandler.SentPacketHandler
|
||||
@@ -176,19 +195,21 @@ type Conn struct {
|
||||
versionNegotiated bool
|
||||
receivedFirstPacket bool
|
||||
|
||||
blocked blockMode
|
||||
|
||||
// the minimum of the max_idle_timeout values advertised by both endpoints
|
||||
idleTimeout time.Duration
|
||||
creationTime time.Time
|
||||
creationTime monotime.Time
|
||||
// The idle timeout is set based on the max of the time we received the last packet...
|
||||
lastPacketReceivedTime time.Time
|
||||
lastPacketReceivedTime monotime.Time
|
||||
// ... and the time we sent a new ack-eliciting packet after receiving a packet.
|
||||
firstAckElicitingPacketAfterIdleSentTime time.Time
|
||||
firstAckElicitingPacketAfterIdleSentTime monotime.Time
|
||||
// pacingDeadline is the time when the next packet should be sent
|
||||
pacingDeadline time.Time
|
||||
pacingDeadline monotime.Time
|
||||
|
||||
peerParams *wire.TransportParameters
|
||||
|
||||
timer connectionTimer
|
||||
timer *time.Timer
|
||||
// keepAlivePingSent stores whether a keep alive PING is in flight.
|
||||
// It is reset as soon as we receive a packet from the peer.
|
||||
keepAlivePingSent bool
|
||||
@@ -286,6 +307,7 @@ var newConnection = func(
|
||||
0,
|
||||
protocol.ByteCount(s.config.InitialPacketSize),
|
||||
s.rttStats,
|
||||
&s.connStats,
|
||||
clientAddressValidated,
|
||||
s.conn.capabilities().ECN,
|
||||
s.perspective,
|
||||
@@ -400,6 +422,7 @@ var newClientConnection = func(
|
||||
initialPacketNumber,
|
||||
protocol.ByteCount(s.config.InitialPacketSize),
|
||||
s.rttStats,
|
||||
&s.connStats,
|
||||
false, // has no effect
|
||||
s.conn.capabilities().ECN,
|
||||
s.perspective,
|
||||
@@ -473,6 +496,7 @@ func (c *Conn) preSetup() {
|
||||
c.frameParser = *wire.NewFrameParser(
|
||||
c.config.EnableDatagrams,
|
||||
c.config.EnableStreamResetPartialDelivery,
|
||||
false, // ACK_FREQUENCY is not supported yet
|
||||
)
|
||||
c.rttStats = &utils.RTTStats{}
|
||||
c.connFlowController = flowcontrol.NewConnectionFlowController(
|
||||
@@ -504,7 +528,7 @@ func (c *Conn) preSetup() {
|
||||
c.sendingScheduled = make(chan struct{}, 1)
|
||||
c.handshakeCompleteChan = make(chan struct{})
|
||||
|
||||
now := time.Now()
|
||||
now := monotime.Now()
|
||||
c.lastPacketReceivedTime = now
|
||||
c.creationTime = now
|
||||
|
||||
@@ -528,12 +552,12 @@ func (c *Conn) run() (err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
c.timer = *newTimer()
|
||||
c.timer = time.NewTimer(monotime.Until(c.idleTimeoutStartTime().Add(c.config.HandshakeIdleTimeout)))
|
||||
|
||||
if err := c.cryptoStreamHandler.StartHandshake(c.ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.handleHandshakeEvents(time.Now()); err != nil {
|
||||
if err := c.handleHandshakeEvents(monotime.Now()); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
@@ -609,8 +633,7 @@ runLoop:
|
||||
select {
|
||||
case <-c.closeChan:
|
||||
break runLoop
|
||||
case <-c.timer.Chan():
|
||||
c.timer.SetRead()
|
||||
case <-c.timer.C:
|
||||
case <-c.sendingScheduled:
|
||||
case <-sendQueueAvailable:
|
||||
case <-c.notifyReceivedPacket:
|
||||
@@ -628,7 +651,7 @@ runLoop:
|
||||
|
||||
// Check for loss detection timeout.
|
||||
// This could cause packets to be declared lost, and retransmissions to be enqueued.
|
||||
now := time.Now()
|
||||
now := monotime.Now()
|
||||
if timeout := c.sentPacketHandler.GetLossDetectionTimeout(); !timeout.IsZero() && timeout.Before(now) {
|
||||
if err := c.sentPacketHandler.OnLossDetectionTimeout(now); err != nil {
|
||||
c.setCloseError(&closeError{err: err})
|
||||
@@ -647,7 +670,7 @@ runLoop:
|
||||
} else {
|
||||
idleTimeoutStartTime := c.idleTimeoutStartTime()
|
||||
if (!c.handshakeComplete && now.Sub(idleTimeoutStartTime) >= c.config.HandshakeIdleTimeout) ||
|
||||
(c.handshakeComplete && now.After(c.nextIdleTimeoutTime())) {
|
||||
(c.handshakeComplete && !now.Before(c.nextIdleTimeoutTime())) {
|
||||
c.destroyImpl(qerr.ErrIdleTimeout)
|
||||
break runLoop
|
||||
}
|
||||
@@ -669,7 +692,8 @@ runLoop:
|
||||
// The send queue is still busy sending out packets. Wait until there's space to enqueue new packets.
|
||||
sendQueueAvailable = c.sendQueue.Available()
|
||||
// Cancel the pacing timer, as we can't send any more packets until the send queue is available again.
|
||||
c.pacingDeadline = time.Time{}
|
||||
c.pacingDeadline = 0
|
||||
c.blocked = blockModeHardBlocked
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -677,6 +701,7 @@ runLoop:
|
||||
break runLoop
|
||||
}
|
||||
|
||||
c.blocked = blockModeNone // sending might set it back to true if we're congestion limited
|
||||
if err := c.triggerSending(now); err != nil {
|
||||
c.setCloseError(&closeError{err: err})
|
||||
break runLoop
|
||||
@@ -685,7 +710,8 @@ runLoop:
|
||||
// The send queue is still busy sending out packets. Wait until there's space to enqueue new packets.
|
||||
sendQueueAvailable = c.sendQueue.Available()
|
||||
// Cancel the pacing timer, as we can't send any more packets until the send queue is available again.
|
||||
c.pacingDeadline = time.Time{}
|
||||
c.pacingDeadline = 0
|
||||
c.blocked = blockModeHardBlocked
|
||||
} else {
|
||||
sendQueueAvailable = nil
|
||||
}
|
||||
@@ -732,55 +758,133 @@ func (c *Conn) ConnectionState() ConnectionState {
|
||||
return c.connState
|
||||
}
|
||||
|
||||
// ConnectionStats contains statistics about the QUIC connection
|
||||
type ConnectionStats struct {
|
||||
// MinRTT is the estimate of the minimum RTT observed on the active network
|
||||
// path.
|
||||
MinRTT time.Duration
|
||||
// LatestRTT is the last RTT sample observed on the active network path.
|
||||
LatestRTT time.Duration
|
||||
// SmoothedRTT is an exponentially weighted moving average of an endpoint's
|
||||
// RTT samples. See https://www.rfc-editor.org/rfc/rfc9002#section-5.3
|
||||
SmoothedRTT time.Duration
|
||||
// MeanDeviation estimates the variation in the RTT samples using a mean
|
||||
// variation. See https://www.rfc-editor.org/rfc/rfc9002#section-5.3
|
||||
MeanDeviation time.Duration
|
||||
|
||||
// BytesSent is the number of bytes sent on the underlying connection,
|
||||
// including retransmissions. Does not include UDP or any other outer
|
||||
// framing.
|
||||
BytesSent uint64
|
||||
// PacketsSent is the number of packets sent on the underlying connection,
|
||||
// including those that are determined to have been lost.
|
||||
PacketsSent uint64
|
||||
// BytesReceived is the number of total bytes received on the underlying
|
||||
// connection, including duplicate data for streams. Does not include UDP or
|
||||
// any other outer framing.
|
||||
BytesReceived uint64
|
||||
// PacketsReceived is the number of total packets received on the underlying
|
||||
// connection, including packets that were not processable.
|
||||
PacketsReceived uint64
|
||||
// BytesLost is the number of bytes lost on the underlying connection (does
|
||||
// not monotonically increase, because packets that are declared lost can
|
||||
// subsequently be received). Does not include UDP or any other outer
|
||||
// framing.
|
||||
BytesLost uint64
|
||||
// PacketsLost is the number of packets lost on the underlying connection
|
||||
// (does not monotonically increase, because packets that are declared lost
|
||||
// can subsequently be received).
|
||||
PacketsLost uint64
|
||||
}
|
||||
|
||||
func (c *Conn) ConnectionStats() ConnectionStats {
|
||||
return ConnectionStats{
|
||||
MinRTT: c.rttStats.MinRTT(),
|
||||
LatestRTT: c.rttStats.LatestRTT(),
|
||||
SmoothedRTT: c.rttStats.SmoothedRTT(),
|
||||
MeanDeviation: c.rttStats.MeanDeviation(),
|
||||
|
||||
BytesSent: c.connStats.BytesSent.Load(),
|
||||
PacketsSent: c.connStats.PacketsSent.Load(),
|
||||
BytesReceived: c.connStats.BytesReceived.Load(),
|
||||
PacketsReceived: c.connStats.PacketsReceived.Load(),
|
||||
BytesLost: c.connStats.BytesLost.Load(),
|
||||
PacketsLost: c.connStats.PacketsLost.Load(),
|
||||
}
|
||||
}
|
||||
|
||||
// Time when the connection should time out
|
||||
func (c *Conn) nextIdleTimeoutTime() time.Time {
|
||||
func (c *Conn) nextIdleTimeoutTime() monotime.Time {
|
||||
idleTimeout := max(c.idleTimeout, c.rttStats.PTO(true)*3)
|
||||
return c.idleTimeoutStartTime().Add(idleTimeout)
|
||||
}
|
||||
|
||||
// Time when the next keep-alive packet should be sent.
|
||||
// It returns a zero time if no keep-alive should be sent.
|
||||
func (c *Conn) nextKeepAliveTime() time.Time {
|
||||
func (c *Conn) nextKeepAliveTime() monotime.Time {
|
||||
if c.config.KeepAlivePeriod == 0 || c.keepAlivePingSent {
|
||||
return time.Time{}
|
||||
return 0
|
||||
}
|
||||
keepAliveInterval := max(c.keepAliveInterval, c.rttStats.PTO(true)*3/2)
|
||||
return c.lastPacketReceivedTime.Add(keepAliveInterval)
|
||||
}
|
||||
|
||||
func (c *Conn) maybeResetTimer() {
|
||||
var deadline time.Time
|
||||
var deadline monotime.Time
|
||||
if !c.handshakeComplete {
|
||||
deadline = c.creationTime.Add(c.config.handshakeTimeout())
|
||||
if t := c.idleTimeoutStartTime().Add(c.config.HandshakeIdleTimeout); t.Before(deadline) {
|
||||
deadline = t
|
||||
}
|
||||
} else {
|
||||
if keepAliveTime := c.nextKeepAliveTime(); !keepAliveTime.IsZero() {
|
||||
deadline = keepAliveTime
|
||||
} else {
|
||||
// A keep-alive packet is ack-eliciting, so it can only be sent if the connection is
|
||||
// neither congestion limited nor hard-blocked.
|
||||
if c.blocked != blockModeNone {
|
||||
deadline = c.nextIdleTimeoutTime()
|
||||
} else {
|
||||
if keepAliveTime := c.nextKeepAliveTime(); !keepAliveTime.IsZero() {
|
||||
deadline = keepAliveTime
|
||||
} else {
|
||||
deadline = c.nextIdleTimeoutTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the connection is hard-blocked, we can't even send acknowledgments,
|
||||
// nor can we send PTO probe packets.
|
||||
if c.blocked == blockModeHardBlocked {
|
||||
c.timer.Reset(monotime.Until(deadline))
|
||||
return
|
||||
}
|
||||
|
||||
c.timer.SetTimer(
|
||||
deadline,
|
||||
c.connIDGenerator.NextRetireTime(),
|
||||
c.receivedPacketHandler.GetAlarmTimeout(),
|
||||
c.sentPacketHandler.GetLossDetectionTimeout(),
|
||||
c.pacingDeadline,
|
||||
)
|
||||
if t := c.receivedPacketHandler.GetAlarmTimeout(); !t.IsZero() && t.Before(deadline) {
|
||||
deadline = t
|
||||
}
|
||||
if t := c.sentPacketHandler.GetLossDetectionTimeout(); !t.IsZero() && t.Before(deadline) {
|
||||
deadline = t
|
||||
}
|
||||
if c.blocked == blockModeCongestionLimited {
|
||||
c.timer.Reset(monotime.Until(deadline))
|
||||
return
|
||||
}
|
||||
|
||||
if t := c.connIDGenerator.NextRetireTime(); !t.IsZero() && t.Before(deadline) {
|
||||
deadline = t
|
||||
}
|
||||
if !c.pacingDeadline.IsZero() && c.pacingDeadline.Before(deadline) {
|
||||
deadline = c.pacingDeadline
|
||||
}
|
||||
c.timer.Reset(monotime.Until(deadline))
|
||||
}
|
||||
|
||||
func (c *Conn) idleTimeoutStartTime() time.Time {
|
||||
func (c *Conn) idleTimeoutStartTime() monotime.Time {
|
||||
startTime := c.lastPacketReceivedTime
|
||||
if t := c.firstAckElicitingPacketAfterIdleSentTime; t.After(startTime) {
|
||||
if t := c.firstAckElicitingPacketAfterIdleSentTime; !t.IsZero() && t.After(startTime) {
|
||||
startTime = t
|
||||
}
|
||||
return startTime
|
||||
}
|
||||
|
||||
func (c *Conn) switchToNewPath(tr *Transport, now time.Time) {
|
||||
func (c *Conn) switchToNewPath(tr *Transport, now monotime.Time) {
|
||||
initialPacketSize := protocol.ByteCount(c.config.InitialPacketSize)
|
||||
c.sentPacketHandler.MigratedPath(now, initialPacketSize)
|
||||
maxPacketSize := protocol.ByteCount(protocol.MaxPacketBufferSize)
|
||||
@@ -798,7 +902,7 @@ func (c *Conn) switchToNewPath(tr *Transport, now time.Time) {
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *Conn) handleHandshakeComplete(now time.Time) error {
|
||||
func (c *Conn) handleHandshakeComplete(now monotime.Time) error {
|
||||
defer close(c.handshakeCompleteChan)
|
||||
// Once the handshake completes, we have derived 1-RTT keys.
|
||||
// There's no point in queueing undecryptable packets for later decryption anymore.
|
||||
@@ -844,7 +948,11 @@ func (c *Conn) handleHandshakeComplete(now time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) handleHandshakeConfirmed(now time.Time) error {
|
||||
func (c *Conn) handleHandshakeConfirmed(now monotime.Time) error {
|
||||
// Drop initial keys.
|
||||
// On the client side, this should have happened when sending the first Handshake packet,
|
||||
// but this is not guaranteed if the server misbehaves.
|
||||
// See CVE-2025-59530 for more details.
|
||||
if err := c.dropEncryptionLevel(protocol.EncryptionInitial, now); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -998,6 +1106,7 @@ func (c *Conn) handleOnePacket(rp receivedPacket) (wasProcessed bool, _ error) {
|
||||
}
|
||||
|
||||
p.buffer.MaybeRelease()
|
||||
c.blocked = blockModeNone
|
||||
return wasProcessed, nil
|
||||
}
|
||||
|
||||
@@ -1212,7 +1321,7 @@ func (c *Conn) handleUnpackError(err error, p receivedPacket, pt logging.PacketT
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) handleRetryPacket(hdr *wire.Header, data []byte, rcvTime time.Time) bool /* was this a valid Retry */ {
|
||||
func (c *Conn) handleRetryPacket(hdr *wire.Header, data []byte, rcvTime monotime.Time) bool /* was this a valid Retry */ {
|
||||
if c.perspective == protocol.PerspectiveServer {
|
||||
if c.tracer != nil && c.tracer.DroppedPacket != nil {
|
||||
c.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket)
|
||||
@@ -1328,7 +1437,7 @@ func (c *Conn) handleVersionNegotiationPacket(p receivedPacket) {
|
||||
func (c *Conn) handleUnpackedLongHeaderPacket(
|
||||
packet *unpackedPacket,
|
||||
ecn protocol.ECN,
|
||||
rcvTime time.Time,
|
||||
rcvTime monotime.Time,
|
||||
packetSize protocol.ByteCount, // only for logging
|
||||
) error {
|
||||
if !c.receivedFirstPacket {
|
||||
@@ -1381,7 +1490,7 @@ func (c *Conn) handleUnpackedLongHeaderPacket(
|
||||
}
|
||||
|
||||
c.lastPacketReceivedTime = rcvTime
|
||||
c.firstAckElicitingPacketAfterIdleSentTime = time.Time{}
|
||||
c.firstAckElicitingPacketAfterIdleSentTime = 0
|
||||
c.keepAlivePingSent = false
|
||||
|
||||
if packet.hdr.Type == protocol.PacketType0RTT {
|
||||
@@ -1406,11 +1515,11 @@ func (c *Conn) handleUnpackedShortHeaderPacket(
|
||||
pn protocol.PacketNumber,
|
||||
data []byte,
|
||||
ecn protocol.ECN,
|
||||
rcvTime time.Time,
|
||||
rcvTime monotime.Time,
|
||||
log func([]logging.Frame),
|
||||
) (isNonProbing bool, pathChallenge *wire.PathChallengeFrame, _ error) {
|
||||
c.lastPacketReceivedTime = rcvTime
|
||||
c.firstAckElicitingPacketAfterIdleSentTime = time.Time{}
|
||||
c.firstAckElicitingPacketAfterIdleSentTime = 0
|
||||
c.keepAlivePingSent = false
|
||||
|
||||
isAckEliciting, isNonProbing, pathChallenge, err := c.handleFrames(data, destConnID, protocol.Encryption1RTT, log, rcvTime)
|
||||
@@ -1430,7 +1539,7 @@ func (c *Conn) handleFrames(
|
||||
destConnID protocol.ConnectionID,
|
||||
encLevel protocol.EncryptionLevel,
|
||||
log func([]logging.Frame),
|
||||
rcvTime time.Time,
|
||||
rcvTime monotime.Time,
|
||||
) (isAckEliciting, isNonProbing bool, pathChallenge *wire.PathChallengeFrame, _ error) {
|
||||
// Only used for tracing.
|
||||
// If we're not tracing, this slice will always remain empty.
|
||||
@@ -1477,6 +1586,7 @@ func (c *Conn) handleFrames(
|
||||
if skipHandling {
|
||||
continue
|
||||
}
|
||||
wire.LogFrame(c.logger, streamFrame, false)
|
||||
handleErr = c.streamsMap.HandleStreamFrame(streamFrame, rcvTime)
|
||||
} else if frameType.IsAckFrameType() {
|
||||
ackFrame, l, err := c.frameParser.ParseAckFrame(frameType, data, encLevel, c.version)
|
||||
@@ -1491,6 +1601,7 @@ func (c *Conn) handleFrames(
|
||||
if skipHandling {
|
||||
continue
|
||||
}
|
||||
wire.LogFrame(c.logger, ackFrame, false)
|
||||
handleErr = c.handleAckFrame(ackFrame, encLevel, rcvTime)
|
||||
} else if frameType.IsDatagramFrameType() {
|
||||
datagramFrame, l, err := c.frameParser.ParseDatagramFrame(frameType, data, c.version)
|
||||
@@ -1506,6 +1617,7 @@ func (c *Conn) handleFrames(
|
||||
if skipHandling {
|
||||
continue
|
||||
}
|
||||
wire.LogFrame(c.logger, datagramFrame, false)
|
||||
handleErr = c.handleDatagramFrame(datagramFrame)
|
||||
} else {
|
||||
frame, l, err := c.frameParser.ParseLessCommonFrame(frameType, data, c.version)
|
||||
@@ -1560,7 +1672,7 @@ func (c *Conn) handleFrame(
|
||||
f wire.Frame,
|
||||
encLevel protocol.EncryptionLevel,
|
||||
destConnID protocol.ConnectionID,
|
||||
rcvTime time.Time,
|
||||
rcvTime monotime.Time,
|
||||
) (pathChallenge *wire.PathChallengeFrame, _ error) {
|
||||
var err error
|
||||
wire.LogFrame(c.logger, f, false)
|
||||
@@ -1640,7 +1752,7 @@ func (c *Conn) handleConnectionCloseFrame(frame *wire.ConnectionCloseFrame) erro
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) handleCryptoFrame(frame *wire.CryptoFrame, encLevel protocol.EncryptionLevel, rcvTime time.Time) error {
|
||||
func (c *Conn) handleCryptoFrame(frame *wire.CryptoFrame, encLevel protocol.EncryptionLevel, rcvTime monotime.Time) error {
|
||||
if err := c.cryptoStreamManager.HandleCryptoFrame(frame, encLevel); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1656,7 +1768,7 @@ func (c *Conn) handleCryptoFrame(frame *wire.CryptoFrame, encLevel protocol.Encr
|
||||
return c.handleHandshakeEvents(rcvTime)
|
||||
}
|
||||
|
||||
func (c *Conn) handleHandshakeEvents(now time.Time) error {
|
||||
func (c *Conn) handleHandshakeEvents(now monotime.Time) error {
|
||||
for {
|
||||
ev := c.cryptoStreamHandler.NextEvent()
|
||||
var err error
|
||||
@@ -1743,7 +1855,7 @@ func (c *Conn) handleNewTokenFrame(frame *wire.NewTokenFrame) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) handleHandshakeDoneFrame(rcvTime time.Time) error {
|
||||
func (c *Conn) handleHandshakeDoneFrame(rcvTime monotime.Time) error {
|
||||
if c.perspective == protocol.PerspectiveServer {
|
||||
return &qerr.TransportError{
|
||||
ErrorCode: qerr.ProtocolViolation,
|
||||
@@ -1756,7 +1868,7 @@ func (c *Conn) handleHandshakeDoneFrame(rcvTime time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) handleAckFrame(frame *wire.AckFrame, encLevel protocol.EncryptionLevel, rcvTime time.Time) error {
|
||||
func (c *Conn) handleAckFrame(frame *wire.AckFrame, encLevel protocol.EncryptionLevel, rcvTime monotime.Time) error {
|
||||
acked1RTTPacket, err := c.sentPacketHandler.ReceivedAck(frame, encLevel, c.lastPacketReceivedTime)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1918,7 +2030,7 @@ func (c *Conn) handleCloseError(closeErr *closeError) {
|
||||
c.connIDGenerator.ReplaceWithClosed(connClosePacket, 3*c.rttStats.PTO(false))
|
||||
}
|
||||
|
||||
func (c *Conn) dropEncryptionLevel(encLevel protocol.EncryptionLevel, now time.Time) error {
|
||||
func (c *Conn) dropEncryptionLevel(encLevel protocol.EncryptionLevel, now monotime.Time) error {
|
||||
if c.tracer != nil && c.tracer.DroppedEncryptionLevel != nil {
|
||||
c.tracer.DroppedEncryptionLevel(encLevel)
|
||||
}
|
||||
@@ -1942,6 +2054,9 @@ func (c *Conn) restoreTransportParameters(params *wire.TransportParameters) {
|
||||
if c.logger.Debug() {
|
||||
c.logger.Debugf("Restoring Transport Parameters: %s", params)
|
||||
}
|
||||
if c.tracer != nil && c.tracer.RestoredTransportParameters != nil {
|
||||
c.tracer.RestoredTransportParameters(params)
|
||||
}
|
||||
|
||||
c.peerParams = params
|
||||
c.connIDGenerator.SetMaxActiveConnIDs(params.ActiveConnectionIDLimit)
|
||||
@@ -2050,14 +2165,15 @@ func (c *Conn) applyTransportParameters() {
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Conn) triggerSending(now time.Time) error {
|
||||
c.pacingDeadline = time.Time{}
|
||||
func (c *Conn) triggerSending(now monotime.Time) error {
|
||||
c.pacingDeadline = 0
|
||||
|
||||
sendMode := c.sentPacketHandler.SendMode(now)
|
||||
switch sendMode {
|
||||
case ackhandler.SendAny:
|
||||
return c.sendPackets(now)
|
||||
case ackhandler.SendNone:
|
||||
c.blocked = blockModeHardBlocked
|
||||
return nil
|
||||
case ackhandler.SendPacingLimited:
|
||||
deadline := c.sentPacketHandler.TimeUntilSend()
|
||||
@@ -2068,11 +2184,12 @@ func (c *Conn) triggerSending(now time.Time) error {
|
||||
// Allow sending of an ACK if we're pacing limit.
|
||||
// This makes sure that a peer that is mostly receiving data (and thus has an inaccurate cwnd estimate)
|
||||
// sends enough ACKs to allow its peer to utilize the bandwidth.
|
||||
fallthrough
|
||||
return c.maybeSendAckOnlyPacket(now)
|
||||
case ackhandler.SendAck:
|
||||
// We can at most send a single ACK only packet.
|
||||
// There will only be a new ACK after receiving new packets.
|
||||
// SendAck is only returned when we're congestion limited, so we don't need to set the pacing timer.
|
||||
c.blocked = blockModeCongestionLimited
|
||||
return c.maybeSendAckOnlyPacket(now)
|
||||
case ackhandler.SendPTOInitial, ackhandler.SendPTOHandshake, ackhandler.SendPTOAppData:
|
||||
if err := c.sendProbePacket(sendMode, now); err != nil {
|
||||
@@ -2088,7 +2205,7 @@ func (c *Conn) triggerSending(now time.Time) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) sendPackets(now time.Time) error {
|
||||
func (c *Conn) sendPackets(now monotime.Time) error {
|
||||
if c.perspective == protocol.PerspectiveClient && c.handshakeConfirmed {
|
||||
if pm := c.pathManagerOutgoing.Load(); pm != nil {
|
||||
connID, frame, tr, ok := pm.NextPathToProbe()
|
||||
@@ -2159,7 +2276,7 @@ func (c *Conn) sendPackets(now time.Time) error {
|
||||
return c.sendPacketsWithoutGSO(now)
|
||||
}
|
||||
|
||||
func (c *Conn) sendPacketsWithoutGSO(now time.Time) error {
|
||||
func (c *Conn) sendPacketsWithoutGSO(now monotime.Time) error {
|
||||
for {
|
||||
buf := getPacketBuffer()
|
||||
ecn := c.sentPacketHandler.ECNMode(true)
|
||||
@@ -2195,7 +2312,7 @@ func (c *Conn) sendPacketsWithoutGSO(now time.Time) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) sendPacketsWithGSO(now time.Time) error {
|
||||
func (c *Conn) sendPacketsWithGSO(now monotime.Time) error {
|
||||
buf := getLargePacketBuffer()
|
||||
maxSize := c.maxPacketSize()
|
||||
|
||||
@@ -2267,7 +2384,7 @@ func (c *Conn) resetPacingDeadline() {
|
||||
c.pacingDeadline = deadline
|
||||
}
|
||||
|
||||
func (c *Conn) maybeSendAckOnlyPacket(now time.Time) error {
|
||||
func (c *Conn) maybeSendAckOnlyPacket(now monotime.Time) error {
|
||||
if !c.handshakeConfirmed {
|
||||
ecn := c.sentPacketHandler.ECNMode(false)
|
||||
packet, err := c.packer.PackCoalescedPacket(true, c.maxPacketSize(), now, c.version)
|
||||
@@ -2294,7 +2411,7 @@ func (c *Conn) maybeSendAckOnlyPacket(now time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) sendProbePacket(sendMode ackhandler.SendMode, now time.Time) error {
|
||||
func (c *Conn) sendProbePacket(sendMode ackhandler.SendMode, now monotime.Time) error {
|
||||
var encLevel protocol.EncryptionLevel
|
||||
//nolint:exhaustive // We only need to handle the PTO send modes here.
|
||||
switch sendMode {
|
||||
@@ -2335,7 +2452,7 @@ func (c *Conn) sendProbePacket(sendMode ackhandler.SendMode, now time.Time) erro
|
||||
|
||||
// appendOneShortHeaderPacket appends a new packet to the given packetBuffer.
|
||||
// If there was nothing to pack, the returned size is 0.
|
||||
func (c *Conn) appendOneShortHeaderPacket(buf *packetBuffer, maxSize protocol.ByteCount, ecn protocol.ECN, now time.Time) (protocol.ByteCount, error) {
|
||||
func (c *Conn) appendOneShortHeaderPacket(buf *packetBuffer, maxSize protocol.ByteCount, ecn protocol.ECN, now monotime.Time) (protocol.ByteCount, error) {
|
||||
startLen := buf.Len()
|
||||
p, err := c.packer.AppendPacket(buf, maxSize, now, c.version)
|
||||
if err != nil {
|
||||
@@ -2347,7 +2464,7 @@ func (c *Conn) appendOneShortHeaderPacket(buf *packetBuffer, maxSize protocol.By
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func (c *Conn) registerPackedShortHeaderPacket(p shortHeaderPacket, ecn protocol.ECN, now time.Time) {
|
||||
func (c *Conn) registerPackedShortHeaderPacket(p shortHeaderPacket, ecn protocol.ECN, now monotime.Time) {
|
||||
if p.IsPathProbePacket {
|
||||
c.sentPacketHandler.SentPacket(
|
||||
now,
|
||||
@@ -2386,7 +2503,7 @@ func (c *Conn) registerPackedShortHeaderPacket(p shortHeaderPacket, ecn protocol
|
||||
c.connIDManager.SentPacket()
|
||||
}
|
||||
|
||||
func (c *Conn) sendPackedCoalescedPacket(packet *coalescedPacket, ecn protocol.ECN, now time.Time) error {
|
||||
func (c *Conn) sendPackedCoalescedPacket(packet *coalescedPacket, ecn protocol.ECN, now monotime.Time) error {
|
||||
c.logCoalescedPacket(packet, ecn)
|
||||
for _, p := range packet.longHdrPackets {
|
||||
if c.firstAckElicitingPacketAfterIdleSentTime.IsZero() && p.IsAckEliciting() {
|
||||
@@ -2644,16 +2761,26 @@ func (c *Conn) LocalAddr() net.Addr { return c.conn.LocalAddr() }
|
||||
// RemoteAddr returns the remote address of the QUIC connection.
|
||||
func (c *Conn) RemoteAddr() net.Addr { return c.conn.RemoteAddr() }
|
||||
|
||||
// getPathManager lazily initializes the Conn's pathManagerOutgoing.
|
||||
// May create multiple pathManagerOutgoing objects if called concurrently.
|
||||
func (c *Conn) getPathManager() *pathManagerOutgoing {
|
||||
c.pathManagerOutgoing.CompareAndSwap(nil,
|
||||
func() *pathManagerOutgoing { // this function is only called if a swap is performed
|
||||
return newPathManagerOutgoing(
|
||||
c.connIDManager.GetConnIDForPath,
|
||||
c.connIDManager.RetireConnIDForPath,
|
||||
c.scheduleSending,
|
||||
)
|
||||
}(),
|
||||
old := c.pathManagerOutgoing.Load()
|
||||
if old != nil {
|
||||
// Path manager is already initialized
|
||||
return old
|
||||
}
|
||||
|
||||
// Initialize the path manager
|
||||
new := newPathManagerOutgoing(
|
||||
c.connIDManager.GetConnIDForPath,
|
||||
c.connIDManager.RetireConnIDForPath,
|
||||
c.scheduleSending,
|
||||
)
|
||||
if c.pathManagerOutgoing.CompareAndSwap(old, new) {
|
||||
return new
|
||||
}
|
||||
|
||||
// Swap failed. A concurrent writer wrote first, use their value.
|
||||
return c.pathManagerOutgoing.Load()
|
||||
}
|
||||
|
||||
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
)
|
||||
|
||||
var deadlineSendImmediately = time.Time{}.Add(42 * time.Millisecond) // any value > time.Time{} and before time.Now() is fine
|
||||
|
||||
type connectionTimer struct {
|
||||
timer *utils.Timer
|
||||
last time.Time
|
||||
}
|
||||
|
||||
func newTimer() *connectionTimer {
|
||||
return &connectionTimer{timer: utils.NewTimer()}
|
||||
}
|
||||
|
||||
func (t *connectionTimer) SetRead() {
|
||||
if deadline := t.timer.Deadline(); deadline != deadlineSendImmediately {
|
||||
t.last = deadline
|
||||
}
|
||||
t.timer.SetRead()
|
||||
}
|
||||
|
||||
func (t *connectionTimer) Chan() <-chan time.Time {
|
||||
return t.timer.Chan()
|
||||
}
|
||||
|
||||
// SetTimer resets the timer.
|
||||
// It makes sure that the deadline is strictly increasing.
|
||||
// This prevents busy-looping in cases where the timer fires, but we can't actually send out a packet.
|
||||
// This doesn't apply to the pacing deadline, which can be set multiple times to deadlineSendImmediately.
|
||||
func (t *connectionTimer) SetTimer(idleTimeoutOrKeepAlive, connIDRetirement, ackAlarm, lossTime, pacing time.Time) {
|
||||
deadline := idleTimeoutOrKeepAlive
|
||||
if !connIDRetirement.IsZero() && connIDRetirement.Before(deadline) && connIDRetirement.After(t.last) {
|
||||
deadline = connIDRetirement
|
||||
}
|
||||
if !ackAlarm.IsZero() && ackAlarm.Before(deadline) && ackAlarm.After(t.last) {
|
||||
deadline = ackAlarm
|
||||
}
|
||||
if !lossTime.IsZero() && lossTime.Before(deadline) && lossTime.After(t.last) {
|
||||
deadline = lossTime
|
||||
}
|
||||
if !pacing.IsZero() && pacing.Before(deadline) {
|
||||
deadline = pacing
|
||||
}
|
||||
t.timer.Reset(deadline)
|
||||
}
|
||||
|
||||
func (t *connectionTimer) Stop() {
|
||||
t.timer.Stop()
|
||||
}
|
||||
+4
-4
@@ -3,10 +3,10 @@ package quic
|
||||
import (
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/ackhandler"
|
||||
"github.com/quic-go/quic-go/internal/flowcontrol"
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/utils/ringbuffer"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
@@ -27,7 +27,7 @@ type streamFrameGetter interface {
|
||||
}
|
||||
|
||||
type streamControlFrameGetter interface {
|
||||
getControlFrame(time.Time) (_ ackhandler.Frame, ok, hasMore bool)
|
||||
getControlFrame(monotime.Time) (_ ackhandler.Frame, ok, hasMore bool)
|
||||
}
|
||||
|
||||
type framer struct {
|
||||
@@ -90,7 +90,7 @@ func (f *framer) Append(
|
||||
frames []ackhandler.Frame,
|
||||
streamFrames []ackhandler.StreamFrame,
|
||||
maxLen protocol.ByteCount,
|
||||
now time.Time,
|
||||
now monotime.Time,
|
||||
v protocol.Version,
|
||||
) ([]ackhandler.Frame, []ackhandler.StreamFrame, protocol.ByteCount) {
|
||||
f.controlFrameMutex.Lock()
|
||||
@@ -157,7 +157,7 @@ func (f *framer) Append(
|
||||
func (f *framer) appendControlFrames(
|
||||
frames []ackhandler.Frame,
|
||||
maxLen protocol.ByteCount,
|
||||
now time.Time,
|
||||
now monotime.Time,
|
||||
v protocol.Version,
|
||||
) ([]ackhandler.Frame, protocol.ByteCount) {
|
||||
var length protocol.ByteCount
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ type FrameType uint64
|
||||
|
||||
type unknownFrameHandlerFunc func(FrameType, error) (processed bool, err error)
|
||||
|
||||
type frame interface{}
|
||||
type frame any
|
||||
|
||||
var errHijacked = errors.New("hijacked")
|
||||
|
||||
|
||||
+3
-3
@@ -2,10 +2,10 @@
|
||||
|
||||
package http3
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -mock_names=TestClientConnInterface=MockClientConn -package http3 -destination mock_clientconn_test.go github.com/quic-go/quic-go/http3 TestClientConnInterface"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -mock_names=TestClientConnInterface=MockClientConn -package http3 -destination mock_clientconn_test.go github.com/quic-go/quic-go/http3 TestClientConnInterface"
|
||||
type TestClientConnInterface = clientConn
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -mock_names=DatagramStream=MockDatagramStream -package http3 -destination mock_datagram_stream_test.go github.com/quic-go/quic-go/http3 DatagramStream"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -mock_names=DatagramStream=MockDatagramStream -package http3 -destination mock_datagram_stream_test.go github.com/quic-go/quic-go/http3 DatagramStream"
|
||||
type DatagramStream = datagramStream
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -package http3 -destination mock_quic_listener_test.go github.com/quic-go/quic-go/http3 QUICListener"
|
||||
//go:generate sh -c "go tool mockgen -typed -package http3 -destination mock_quic_listener_test.go github.com/quic-go/quic-go/http3 QUICListener"
|
||||
|
||||
+1
-1
@@ -271,7 +271,7 @@ func (s *Server) ServeQUICConn(conn *quic.Conn) error {
|
||||
// ServeListener serves an existing QUIC listener.
|
||||
// Make sure you use http3.ConfigureTLSConfig to configure a tls.Config
|
||||
// and use it to construct a http3-friendly QUIC listener.
|
||||
// Closing the server does close the listener.
|
||||
// Closing the server does not close the listener. It is the application's responsibility to close them.
|
||||
// ServeListener always returns a non-nil error. After Shutdown or Close, the returned error is http.ErrServerClosed.
|
||||
func (s *Server) ServeListener(ln QUICListener) error {
|
||||
s.mutex.Lock()
|
||||
|
||||
+19
-9
@@ -111,6 +111,7 @@ type Transport struct {
|
||||
|
||||
clients map[string]*roundTripperWithCount
|
||||
transport *quic.Transport
|
||||
closed bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -118,8 +119,12 @@ var (
|
||||
_ io.Closer = &Transport{}
|
||||
)
|
||||
|
||||
// ErrNoCachedConn is returned when Transport.OnlyCachedConn is set
|
||||
var ErrNoCachedConn = errors.New("http3: no cached connection was available")
|
||||
var (
|
||||
// ErrNoCachedConn is returned when Transport.OnlyCachedConn is set
|
||||
ErrNoCachedConn = errors.New("http3: no cached connection was available")
|
||||
// ErrTransportClosed is returned when attempting to use a closed Transport
|
||||
ErrTransportClosed = errors.New("http3: transport is closed")
|
||||
)
|
||||
|
||||
func (t *Transport) init() error {
|
||||
if t.newClientConn == nil {
|
||||
@@ -153,6 +158,13 @@ func (t *Transport) init() error {
|
||||
if t.QUICConfig.MaxIncomingStreams == 0 {
|
||||
t.QUICConfig.MaxIncomingStreams = -1 // don't allow any bidirectional streams
|
||||
}
|
||||
if t.Dial == nil {
|
||||
udpConn, err := net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.transport = &quic.Transport{Conn: udpConn}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -285,6 +297,9 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
func (t *Transport) getClient(ctx context.Context, hostname string, onlyCached bool) (rtc *roundTripperWithCount, isReused bool, err error) {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
if t.closed {
|
||||
return nil, false, ErrTransportClosed
|
||||
}
|
||||
|
||||
if t.clients == nil {
|
||||
t.clients = make(map[string]*roundTripperWithCount)
|
||||
@@ -350,13 +365,6 @@ func (t *Transport) dial(ctx context.Context, hostname string) (*quic.Conn, clie
|
||||
|
||||
dial := t.Dial
|
||||
if dial == nil {
|
||||
if t.transport == nil {
|
||||
udpConn, err := net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
t.transport = &quic.Transport{Conn: udpConn}
|
||||
}
|
||||
dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
|
||||
network := "udp"
|
||||
udpAddr, err := t.resolveUDPAddr(ctx, network, addr)
|
||||
@@ -431,6 +439,7 @@ func (t *Transport) NewClientConn(conn *quic.Conn) *ClientConn {
|
||||
}
|
||||
|
||||
// Close closes the QUIC connections that this Transport has used.
|
||||
// A Transport cannot be used after it has been closed.
|
||||
func (t *Transport) Close() error {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
@@ -449,6 +458,7 @@ func (t *Transport) Close() error {
|
||||
}
|
||||
t.transport = nil
|
||||
}
|
||||
t.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -13,12 +13,13 @@ func NewAckHandler(
|
||||
initialPacketNumber protocol.PacketNumber,
|
||||
initialMaxDatagramSize protocol.ByteCount,
|
||||
rttStats *utils.RTTStats,
|
||||
connStats *utils.ConnectionStats,
|
||||
clientAddressValidated bool,
|
||||
enableECN bool,
|
||||
pers protocol.Perspective,
|
||||
tracer *logging.ConnectionTracer,
|
||||
logger utils.Logger,
|
||||
) (SentPacketHandler, ReceivedPacketHandler) {
|
||||
sph := newSentPacketHandler(initialPacketNumber, initialMaxDatagramSize, rttStats, clientAddressValidated, enableECN, pers, tracer, logger)
|
||||
sph := newSentPacketHandler(initialPacketNumber, initialMaxDatagramSize, rttStats, connStats, clientAddressValidated, enableECN, pers, tracer, logger)
|
||||
return sph, newReceivedPacketHandler(sph, logger)
|
||||
}
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ const numECNTestingPackets = 10
|
||||
type ecnHandler interface {
|
||||
SentPacket(protocol.PacketNumber, protocol.ECN)
|
||||
Mode() protocol.ECN
|
||||
HandleNewlyAcked(packets []*packet, ect0, ect1, ecnce int64) (congested bool)
|
||||
HandleNewlyAcked(packets []packetWithPacketNumber, ect0, ect1, ecnce int64) (congested bool)
|
||||
LostPacket(protocol.PacketNumber)
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ func (e *ecnTracker) LostPacket(pn protocol.PacketNumber) {
|
||||
// HandleNewlyAcked handles the ECN counts on an ACK frame.
|
||||
// It must only be called for ACK frames that increase the largest acknowledged packet number,
|
||||
// see section 13.4.2.1 of RFC 9000.
|
||||
func (e *ecnTracker) HandleNewlyAcked(packets []*packet, ect0, ect1, ecnce int64) (congested bool) {
|
||||
func (e *ecnTracker) HandleNewlyAcked(packets []packetWithPacketNumber, ect0, ect1, ecnce int64) (congested bool) {
|
||||
if e.state == ecnStateFailed {
|
||||
return false
|
||||
}
|
||||
|
||||
+15
-16
@@ -1,8 +1,7 @@
|
||||
package ackhandler
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
)
|
||||
@@ -10,19 +9,19 @@ import (
|
||||
// SentPacketHandler handles ACKs received for outgoing packets
|
||||
type SentPacketHandler interface {
|
||||
// SentPacket may modify the packet
|
||||
SentPacket(t time.Time, pn, largestAcked protocol.PacketNumber, streamFrames []StreamFrame, frames []Frame, encLevel protocol.EncryptionLevel, ecn protocol.ECN, size protocol.ByteCount, isPathMTUProbePacket, isPathProbePacket bool)
|
||||
SentPacket(t monotime.Time, pn, largestAcked protocol.PacketNumber, streamFrames []StreamFrame, frames []Frame, encLevel protocol.EncryptionLevel, ecn protocol.ECN, size protocol.ByteCount, isPathMTUProbePacket, isPathProbePacket bool)
|
||||
// ReceivedAck processes an ACK frame.
|
||||
// It does not store a copy of the frame.
|
||||
ReceivedAck(f *wire.AckFrame, encLevel protocol.EncryptionLevel, rcvTime time.Time) (bool /* 1-RTT packet acked */, error)
|
||||
ReceivedBytes(_ protocol.ByteCount, rcvTime time.Time)
|
||||
DropPackets(_ protocol.EncryptionLevel, rcvTime time.Time)
|
||||
ResetForRetry(rcvTime time.Time)
|
||||
ReceivedAck(f *wire.AckFrame, encLevel protocol.EncryptionLevel, rcvTime monotime.Time) (bool /* 1-RTT packet acked */, error)
|
||||
ReceivedBytes(_ protocol.ByteCount, rcvTime monotime.Time)
|
||||
DropPackets(_ protocol.EncryptionLevel, rcvTime monotime.Time)
|
||||
ResetForRetry(rcvTime monotime.Time)
|
||||
|
||||
// The SendMode determines if and what kind of packets can be sent.
|
||||
SendMode(now time.Time) SendMode
|
||||
SendMode(now monotime.Time) SendMode
|
||||
// TimeUntilSend is the time when the next packet should be sent.
|
||||
// It is used for pacing packets.
|
||||
TimeUntilSend() time.Time
|
||||
TimeUntilSend() monotime.Time
|
||||
SetMaxDatagramSize(count protocol.ByteCount)
|
||||
|
||||
// only to be called once the handshake is complete
|
||||
@@ -32,23 +31,23 @@ type SentPacketHandler interface {
|
||||
PeekPacketNumber(protocol.EncryptionLevel) (protocol.PacketNumber, protocol.PacketNumberLen)
|
||||
PopPacketNumber(protocol.EncryptionLevel) protocol.PacketNumber
|
||||
|
||||
GetLossDetectionTimeout() time.Time
|
||||
OnLossDetectionTimeout(now time.Time) error
|
||||
GetLossDetectionTimeout() monotime.Time
|
||||
OnLossDetectionTimeout(now monotime.Time) error
|
||||
|
||||
MigratedPath(now time.Time, initialMaxPacketSize protocol.ByteCount)
|
||||
MigratedPath(now monotime.Time, initialMaxPacketSize protocol.ByteCount)
|
||||
}
|
||||
|
||||
type sentPacketTracker interface {
|
||||
GetLowestPacketNotConfirmedAcked() protocol.PacketNumber
|
||||
ReceivedPacket(_ protocol.EncryptionLevel, rcvTime time.Time)
|
||||
ReceivedPacket(_ protocol.EncryptionLevel, rcvTime monotime.Time)
|
||||
}
|
||||
|
||||
// ReceivedPacketHandler handles ACKs needed to send for incoming packets
|
||||
type ReceivedPacketHandler interface {
|
||||
IsPotentiallyDuplicate(protocol.PacketNumber, protocol.EncryptionLevel) bool
|
||||
ReceivedPacket(pn protocol.PacketNumber, ecn protocol.ECN, encLevel protocol.EncryptionLevel, rcvTime time.Time, ackEliciting bool) error
|
||||
ReceivedPacket(pn protocol.PacketNumber, ecn protocol.ECN, encLevel protocol.EncryptionLevel, rcvTime monotime.Time, ackEliciting bool) error
|
||||
DropPackets(protocol.EncryptionLevel)
|
||||
|
||||
GetAlarmTimeout() time.Time
|
||||
GetAckFrame(_ protocol.EncryptionLevel, now time.Time, onlyIfQueued bool) *wire.AckFrame
|
||||
GetAlarmTimeout() monotime.Time
|
||||
GetAckFrame(_ protocol.EncryptionLevel, now monotime.Time, onlyIfQueued bool) *wire.AckFrame
|
||||
}
|
||||
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package ackhandler
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"slices"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
)
|
||||
|
||||
type lostPacket struct {
|
||||
PacketNumber protocol.PacketNumber
|
||||
SendTime monotime.Time
|
||||
}
|
||||
|
||||
type lostPacketTracker struct {
|
||||
maxLength int
|
||||
lostPackets []lostPacket
|
||||
}
|
||||
|
||||
func newLostPacketTracker(maxLength int) *lostPacketTracker {
|
||||
return &lostPacketTracker{
|
||||
maxLength: maxLength,
|
||||
// Preallocate a small slice only.
|
||||
// Hopefully we won't lose many packets.
|
||||
lostPackets: make([]lostPacket, 0, 4),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *lostPacketTracker) Add(p protocol.PacketNumber, sendTime monotime.Time) {
|
||||
if len(t.lostPackets) == t.maxLength {
|
||||
t.lostPackets = t.lostPackets[1:]
|
||||
}
|
||||
t.lostPackets = append(t.lostPackets, lostPacket{
|
||||
PacketNumber: p,
|
||||
SendTime: sendTime,
|
||||
})
|
||||
}
|
||||
|
||||
// Delete deletes a packet from the lost packet tracker.
|
||||
// This function is not optimized for performance if many packets are lost,
|
||||
// but it is only used when a spurious loss is detected, which is rare.
|
||||
func (t *lostPacketTracker) Delete(pn protocol.PacketNumber) {
|
||||
t.lostPackets = slices.DeleteFunc(t.lostPackets, func(p lostPacket) bool {
|
||||
return p.PacketNumber == pn
|
||||
})
|
||||
}
|
||||
|
||||
func (t *lostPacketTracker) All() iter.Seq2[protocol.PacketNumber, monotime.Time] {
|
||||
return func(yield func(protocol.PacketNumber, monotime.Time) bool) {
|
||||
for _, p := range t.lostPackets {
|
||||
if !yield(p.PacketNumber, p.SendTime) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *lostPacketTracker) DeleteBefore(ti monotime.Time) {
|
||||
if len(t.lostPackets) == 0 {
|
||||
return
|
||||
}
|
||||
if !t.lostPackets[0].SendTime.Before(ti) {
|
||||
return
|
||||
}
|
||||
var idx int
|
||||
for ; idx < len(t.lostPackets); idx++ {
|
||||
if !t.lostPackets[idx].SendTime.Before(ti) {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.lostPackets = slices.Delete(t.lostPackets, 0, idx)
|
||||
}
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
|
||||
package ackhandler
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package ackhandler -destination mock_sent_packet_tracker_test.go github.com/quic-go/quic-go/internal/ackhandler SentPacketTracker"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package ackhandler -destination mock_sent_packet_tracker_test.go github.com/quic-go/quic-go/internal/ackhandler SentPacketTracker"
|
||||
type SentPacketTracker = sentPacketTracker
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -build_flags=\"-tags=gomock\" -package ackhandler -destination mock_ecn_handler_test.go github.com/quic-go/quic-go/internal/ackhandler ECNHandler"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package ackhandler -destination mock_ecn_handler_test.go github.com/quic-go/quic-go/internal/ackhandler ECNHandler"
|
||||
type ECNHandler = ecnHandler
|
||||
|
||||
+10
-8
@@ -2,15 +2,19 @@ package ackhandler
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
)
|
||||
|
||||
type packetWithPacketNumber struct {
|
||||
PacketNumber protocol.PacketNumber
|
||||
*packet
|
||||
}
|
||||
|
||||
// A Packet is a packet
|
||||
type packet struct {
|
||||
SendTime time.Time
|
||||
PacketNumber protocol.PacketNumber
|
||||
SendTime monotime.Time
|
||||
StreamFrames []StreamFrame
|
||||
Frames []Frame
|
||||
LargestAcked protocol.PacketNumber // InvalidPacketNumber if the packet doesn't contain an ACK
|
||||
@@ -21,29 +25,27 @@ type packet struct {
|
||||
|
||||
includedInBytesInFlight bool
|
||||
declaredLost bool
|
||||
skippedPacket bool
|
||||
isPathProbePacket bool
|
||||
}
|
||||
|
||||
func (p *packet) outstanding() bool {
|
||||
return !p.declaredLost && !p.skippedPacket && !p.IsPathMTUProbePacket && !p.isPathProbePacket
|
||||
return !p.declaredLost && !p.IsPathMTUProbePacket && !p.isPathProbePacket
|
||||
}
|
||||
|
||||
var packetPool = sync.Pool{New: func() any { return &packet{} }}
|
||||
|
||||
func getPacket() *packet {
|
||||
p := packetPool.Get().(*packet)
|
||||
p.PacketNumber = 0
|
||||
p.StreamFrames = nil
|
||||
p.Frames = nil
|
||||
p.LargestAcked = 0
|
||||
p.Length = 0
|
||||
p.EncryptionLevel = protocol.EncryptionLevel(0)
|
||||
p.SendTime = time.Time{}
|
||||
p.SendTime = 0
|
||||
p.IsPathMTUProbePacket = false
|
||||
p.includedInBytesInFlight = false
|
||||
p.declaredLost = false
|
||||
p.skippedPacket = false
|
||||
p.isPathProbePacket = false
|
||||
return p
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -2,8 +2,8 @@ package ackhandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
@@ -35,20 +35,20 @@ func (h *receivedPacketHandler) ReceivedPacket(
|
||||
pn protocol.PacketNumber,
|
||||
ecn protocol.ECN,
|
||||
encLevel protocol.EncryptionLevel,
|
||||
rcvTime time.Time,
|
||||
rcvTime monotime.Time,
|
||||
ackEliciting bool,
|
||||
) error {
|
||||
h.sentPackets.ReceivedPacket(encLevel, rcvTime)
|
||||
switch encLevel {
|
||||
case protocol.EncryptionInitial:
|
||||
return h.initialPackets.ReceivedPacket(pn, ecn, rcvTime, ackEliciting)
|
||||
return h.initialPackets.ReceivedPacket(pn, ecn, ackEliciting)
|
||||
case protocol.EncryptionHandshake:
|
||||
// The Handshake packet number space might already have been dropped as a result
|
||||
// of processing the CRYPTO frame that was contained in this packet.
|
||||
if h.handshakePackets == nil {
|
||||
return nil
|
||||
}
|
||||
return h.handshakePackets.ReceivedPacket(pn, ecn, rcvTime, ackEliciting)
|
||||
return h.handshakePackets.ReceivedPacket(pn, ecn, ackEliciting)
|
||||
case protocol.Encryption0RTT:
|
||||
if h.lowest1RTTPacket != protocol.InvalidPacketNumber && pn > h.lowest1RTTPacket {
|
||||
return fmt.Errorf("received packet number %d on a 0-RTT packet after receiving %d on a 1-RTT packet", pn, h.lowest1RTTPacket)
|
||||
@@ -83,11 +83,11 @@ func (h *receivedPacketHandler) DropPackets(encLevel protocol.EncryptionLevel) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *receivedPacketHandler) GetAlarmTimeout() time.Time {
|
||||
func (h *receivedPacketHandler) GetAlarmTimeout() monotime.Time {
|
||||
return h.appDataPackets.GetAlarmTimeout()
|
||||
}
|
||||
|
||||
func (h *receivedPacketHandler) GetAckFrame(encLevel protocol.EncryptionLevel, now time.Time, onlyIfQueued bool) *wire.AckFrame {
|
||||
func (h *receivedPacketHandler) GetAckFrame(encLevel protocol.EncryptionLevel, now monotime.Time, onlyIfQueued bool) *wire.AckFrame {
|
||||
//nolint:exhaustive // 0-RTT packets can't contain ACK frames.
|
||||
switch encLevel {
|
||||
case protocol.EncryptionInitial:
|
||||
|
||||
+31
-13
@@ -1,10 +1,10 @@
|
||||
package ackhandler
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"slices"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
)
|
||||
|
||||
// interval is an interval from one PacketNumber to the other
|
||||
@@ -23,7 +23,9 @@ type receivedPacketHistory struct {
|
||||
}
|
||||
|
||||
func newReceivedPacketHistory() *receivedPacketHistory {
|
||||
return &receivedPacketHistory{}
|
||||
return &receivedPacketHistory{
|
||||
deletedBelow: protocol.InvalidPacketNumber,
|
||||
}
|
||||
}
|
||||
|
||||
// ReceivedPacket registers a packet with PacketNumber p and updates the ranges
|
||||
@@ -107,21 +109,37 @@ func (h *receivedPacketHistory) DeleteBelow(p protocol.PacketNumber) {
|
||||
}
|
||||
}
|
||||
|
||||
// AppendAckRanges appends to a slice of all AckRanges that can be used in an AckFrame
|
||||
func (h *receivedPacketHistory) AppendAckRanges(ackRanges []wire.AckRange) []wire.AckRange {
|
||||
for i := len(h.ranges) - 1; i >= 0; i-- {
|
||||
ackRanges = append(ackRanges, wire.AckRange{Smallest: h.ranges[i].Start, Largest: h.ranges[i].End})
|
||||
// Backward returns an iterator over the ranges in reverse order
|
||||
func (h *receivedPacketHistory) Backward() iter.Seq[interval] {
|
||||
return func(yield func(interval) bool) {
|
||||
for i := len(h.ranges) - 1; i >= 0; i-- {
|
||||
if !yield(h.ranges[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return ackRanges
|
||||
}
|
||||
|
||||
func (h *receivedPacketHistory) GetHighestAckRange() wire.AckRange {
|
||||
ackRange := wire.AckRange{}
|
||||
if len(h.ranges) > 0 {
|
||||
ackRange.Smallest = h.ranges[len(h.ranges)-1].Start
|
||||
ackRange.Largest = h.ranges[len(h.ranges)-1].End
|
||||
func (h *receivedPacketHistory) HighestMissingUpTo(p protocol.PacketNumber) protocol.PacketNumber {
|
||||
if len(h.ranges) == 0 || (h.deletedBelow != protocol.InvalidPacketNumber && p < h.deletedBelow) {
|
||||
return protocol.InvalidPacketNumber
|
||||
}
|
||||
return ackRange
|
||||
p = min(h.ranges[len(h.ranges)-1].End, p)
|
||||
for i := len(h.ranges) - 1; i >= 0; i-- {
|
||||
r := h.ranges[i]
|
||||
if p >= r.Start && p <= r.End { // p is contained in this range
|
||||
highest := r.Start - 1 // highest packet in the gap before this range
|
||||
if h.deletedBelow != protocol.InvalidPacketNumber && highest < h.deletedBelow {
|
||||
return protocol.InvalidPacketNumber
|
||||
}
|
||||
return highest
|
||||
}
|
||||
if i >= 1 && p > h.ranges[i-1].End && p <= r.Start {
|
||||
// p is in the gap between the previous range and this range
|
||||
return p
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (h *receivedPacketHistory) IsPotentiallyDuplicate(p protocol.PacketNumber) bool {
|
||||
|
||||
+26
-14
@@ -4,11 +4,14 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
)
|
||||
|
||||
const reorderingThreshold = 1
|
||||
|
||||
// The receivedPacketTracker tracks packets for the Initial and Handshake packet number space.
|
||||
// Every received packet is acknowledged immediately.
|
||||
type receivedPacketTracker struct {
|
||||
@@ -24,9 +27,9 @@ func newReceivedPacketTracker() *receivedPacketTracker {
|
||||
return &receivedPacketTracker{packetHistory: *newReceivedPacketHistory()}
|
||||
}
|
||||
|
||||
func (h *receivedPacketTracker) ReceivedPacket(pn protocol.PacketNumber, ecn protocol.ECN, rcvTime time.Time, ackEliciting bool) error {
|
||||
func (h *receivedPacketTracker) ReceivedPacket(pn protocol.PacketNumber, ecn protocol.ECN, ackEliciting bool) error {
|
||||
if isNew := h.packetHistory.ReceivedPacket(pn); !isNew {
|
||||
return fmt.Errorf("recevedPacketTracker BUG: ReceivedPacket called for old / duplicate packet %d", pn)
|
||||
return fmt.Errorf("receivedPacketTracker BUG: ReceivedPacket called for old / duplicate packet %d", pn)
|
||||
}
|
||||
|
||||
//nolint:exhaustive // Only need to count ECT(0), ECT(1) and ECN-CE.
|
||||
@@ -59,7 +62,9 @@ func (h *receivedPacketTracker) GetAckFrame() *wire.AckFrame {
|
||||
ack.ECT0 = h.ect0
|
||||
ack.ECT1 = h.ect1
|
||||
ack.ECNCE = h.ecnce
|
||||
ack.AckRanges = h.packetHistory.AppendAckRanges(ack.AckRanges)
|
||||
for r := range h.packetHistory.Backward() {
|
||||
ack.AckRanges = append(ack.AckRanges, wire.AckRange{Smallest: r.Start, Largest: r.End})
|
||||
}
|
||||
|
||||
h.lastAck = ack
|
||||
h.hasNewAck = false
|
||||
@@ -78,7 +83,7 @@ const packetsBeforeAck = 2
|
||||
type appDataReceivedPacketTracker struct {
|
||||
receivedPacketTracker
|
||||
|
||||
largestObservedRcvdTime time.Time
|
||||
largestObservedRcvdTime monotime.Time
|
||||
|
||||
largestObserved protocol.PacketNumber
|
||||
ignoreBelow protocol.PacketNumber
|
||||
@@ -87,7 +92,7 @@ type appDataReceivedPacketTracker struct {
|
||||
ackQueued bool // true if we need send a new ACK
|
||||
|
||||
ackElicitingPacketsReceivedSinceLastAck int
|
||||
ackAlarm time.Time
|
||||
ackAlarm monotime.Time
|
||||
|
||||
logger utils.Logger
|
||||
}
|
||||
@@ -101,8 +106,8 @@ func newAppDataReceivedPacketTracker(logger utils.Logger) *appDataReceivedPacket
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *appDataReceivedPacketTracker) ReceivedPacket(pn protocol.PacketNumber, ecn protocol.ECN, rcvTime time.Time, ackEliciting bool) error {
|
||||
if err := h.receivedPacketTracker.ReceivedPacket(pn, ecn, rcvTime, ackEliciting); err != nil {
|
||||
func (h *appDataReceivedPacketTracker) ReceivedPacket(pn protocol.PacketNumber, ecn protocol.ECN, rcvTime monotime.Time, ackEliciting bool) error {
|
||||
if err := h.receivedPacketTracker.ReceivedPacket(pn, ecn, ackEliciting); err != nil {
|
||||
return err
|
||||
}
|
||||
if pn >= h.largestObserved {
|
||||
@@ -116,7 +121,7 @@ func (h *appDataReceivedPacketTracker) ReceivedPacket(pn protocol.PacketNumber,
|
||||
isMissing := h.isMissing(pn)
|
||||
if !h.ackQueued && h.shouldQueueACK(pn, ecn, isMissing) {
|
||||
h.ackQueued = true
|
||||
h.ackAlarm = time.Time{} // cancel the ack alarm
|
||||
h.ackAlarm = 0 // cancel the ack alarm
|
||||
}
|
||||
if !h.ackQueued {
|
||||
// No ACK queued, but we'll need to acknowledge the packet after max_ack_delay.
|
||||
@@ -150,11 +155,18 @@ func (h *appDataReceivedPacketTracker) isMissing(p protocol.PacketNumber) bool {
|
||||
}
|
||||
|
||||
func (h *appDataReceivedPacketTracker) hasNewMissingPackets() bool {
|
||||
if h.lastAck == nil {
|
||||
if h.largestObserved < reorderingThreshold {
|
||||
return false
|
||||
}
|
||||
highestRange := h.packetHistory.GetHighestAckRange()
|
||||
return highestRange.Smallest > h.lastAck.LargestAcked()+1 && highestRange.Len() == 1
|
||||
highestMissing := h.packetHistory.HighestMissingUpTo(h.largestObserved - reorderingThreshold)
|
||||
if highestMissing == protocol.InvalidPacketNumber {
|
||||
return false
|
||||
}
|
||||
if highestMissing < h.lastAck.LargestAcked() {
|
||||
// the packet was already reported missing in the last ACK
|
||||
return false
|
||||
}
|
||||
return highestMissing > h.lastAck.LargestAcked()-reorderingThreshold
|
||||
}
|
||||
|
||||
func (h *appDataReceivedPacketTracker) shouldQueueACK(pn protocol.PacketNumber, ecn protocol.ECN, wasMissing bool) bool {
|
||||
@@ -196,7 +208,7 @@ func (h *appDataReceivedPacketTracker) shouldQueueACK(pn protocol.PacketNumber,
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *appDataReceivedPacketTracker) GetAckFrame(now time.Time, onlyIfQueued bool) *wire.AckFrame {
|
||||
func (h *appDataReceivedPacketTracker) GetAckFrame(now monotime.Time, onlyIfQueued bool) *wire.AckFrame {
|
||||
if onlyIfQueued && !h.ackQueued {
|
||||
if h.ackAlarm.IsZero() || h.ackAlarm.After(now) {
|
||||
return nil
|
||||
@@ -211,9 +223,9 @@ func (h *appDataReceivedPacketTracker) GetAckFrame(now time.Time, onlyIfQueued b
|
||||
}
|
||||
ack.DelayTime = max(0, now.Sub(h.largestObservedRcvdTime))
|
||||
h.ackQueued = false
|
||||
h.ackAlarm = time.Time{}
|
||||
h.ackAlarm = 0
|
||||
h.ackElicitingPacketsReceivedSinceLastAck = 0
|
||||
return ack
|
||||
}
|
||||
|
||||
func (h *appDataReceivedPacketTracker) GetAlarmTimeout() time.Time { return h.ackAlarm }
|
||||
func (h *appDataReceivedPacketTracker) GetAlarmTimeout() monotime.Time { return h.ackAlarm }
|
||||
|
||||
+155
-86
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/congestion"
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/qerr"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
@@ -34,8 +35,8 @@ type packetNumberSpace struct {
|
||||
history sentPacketHistory
|
||||
pns packetNumberGenerator
|
||||
|
||||
lossTime time.Time
|
||||
lastAckElicitingPacketTime time.Time
|
||||
lossTime monotime.Time
|
||||
lastAckElicitingPacketTime monotime.Time
|
||||
|
||||
largestAcked protocol.PacketNumber
|
||||
largestSent protocol.PacketNumber
|
||||
@@ -57,7 +58,7 @@ func newPacketNumberSpace(initialPN protocol.PacketNumber, isAppData bool) *pack
|
||||
}
|
||||
|
||||
type alarmTimer struct {
|
||||
Time time.Time
|
||||
Time monotime.Time
|
||||
TimerType logging.TimerType
|
||||
EncryptionLevel protocol.EncryptionLevel
|
||||
}
|
||||
@@ -66,6 +67,7 @@ type sentPacketHandler struct {
|
||||
initialPackets *packetNumberSpace
|
||||
handshakePackets *packetNumberSpace
|
||||
appDataPackets *packetNumberSpace
|
||||
lostPackets lostPacketTracker // only for application-data packet number space
|
||||
|
||||
// Do we know that the peer completed address validation yet?
|
||||
// Always true for the server.
|
||||
@@ -84,12 +86,13 @@ type sentPacketHandler struct {
|
||||
// Only applies to the application-data packet number space.
|
||||
lowestNotConfirmedAcked protocol.PacketNumber
|
||||
|
||||
ackedPackets []*packet // to avoid allocations in detectAndRemoveAckedPackets
|
||||
ackedPackets []packetWithPacketNumber // to avoid allocations in detectAndRemoveAckedPackets
|
||||
|
||||
bytesInFlight protocol.ByteCount
|
||||
|
||||
congestion congestion.SendAlgorithmWithDebugInfos
|
||||
rttStats *utils.RTTStats
|
||||
connStats *utils.ConnectionStats
|
||||
|
||||
// The number of times a PTO has been sent without receiving an ack.
|
||||
ptoCount uint32
|
||||
@@ -121,6 +124,7 @@ func newSentPacketHandler(
|
||||
initialPN protocol.PacketNumber,
|
||||
initialMaxDatagramSize protocol.ByteCount,
|
||||
rttStats *utils.RTTStats,
|
||||
connStats *utils.ConnectionStats,
|
||||
clientAddressValidated bool,
|
||||
enableECN bool,
|
||||
pers protocol.Perspective,
|
||||
@@ -130,6 +134,7 @@ func newSentPacketHandler(
|
||||
congestion := congestion.NewCubicSender(
|
||||
congestion.DefaultClock{},
|
||||
rttStats,
|
||||
connStats,
|
||||
initialMaxDatagramSize,
|
||||
true, // use Reno
|
||||
tracer,
|
||||
@@ -141,7 +146,9 @@ func newSentPacketHandler(
|
||||
initialPackets: newPacketNumberSpace(initialPN, false),
|
||||
handshakePackets: newPacketNumberSpace(0, false),
|
||||
appDataPackets: newPacketNumberSpace(0, true),
|
||||
lostPackets: *newLostPacketTracker(64),
|
||||
rttStats: rttStats,
|
||||
connStats: connStats,
|
||||
congestion: congestion,
|
||||
perspective: pers,
|
||||
tracer: tracer,
|
||||
@@ -164,7 +171,7 @@ func (h *sentPacketHandler) removeFromBytesInFlight(p *packet) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) DropPackets(encLevel protocol.EncryptionLevel, now time.Time) {
|
||||
func (h *sentPacketHandler) DropPackets(encLevel protocol.EncryptionLevel, now monotime.Time) {
|
||||
// The server won't await address validation after the handshake is confirmed.
|
||||
// This applies even if we didn't receive an ACK for a Handshake packet.
|
||||
if h.perspective == protocol.PerspectiveClient && encLevel == protocol.EncryptionHandshake {
|
||||
@@ -177,7 +184,7 @@ func (h *sentPacketHandler) DropPackets(encLevel protocol.EncryptionLevel, now t
|
||||
if pnSpace == nil {
|
||||
return
|
||||
}
|
||||
for p := range pnSpace.history.Packets() {
|
||||
for _, p := range pnSpace.history.Packets() {
|
||||
h.removeFromBytesInFlight(p)
|
||||
}
|
||||
}
|
||||
@@ -196,12 +203,12 @@ func (h *sentPacketHandler) DropPackets(encLevel protocol.EncryptionLevel, now t
|
||||
// and not when the client drops 0-RTT keys when the handshake completes.
|
||||
// When 0-RTT is rejected, all application data sent so far becomes invalid.
|
||||
// Delete the packets from the history and remove them from bytes_in_flight.
|
||||
for p := range h.appDataPackets.history.Packets() {
|
||||
if p.EncryptionLevel != protocol.Encryption0RTT && !p.skippedPacket {
|
||||
for pn, p := range h.appDataPackets.history.Packets() {
|
||||
if p.EncryptionLevel != protocol.Encryption0RTT {
|
||||
break
|
||||
}
|
||||
h.removeFromBytesInFlight(p)
|
||||
h.appDataPackets.history.Remove(p.PacketNumber)
|
||||
h.appDataPackets.history.Remove(pn)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Cannot drop keys for encryption level %s", encLevel))
|
||||
@@ -215,7 +222,8 @@ func (h *sentPacketHandler) DropPackets(encLevel protocol.EncryptionLevel, now t
|
||||
h.setLossDetectionTimer(now)
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) ReceivedBytes(n protocol.ByteCount, t time.Time) {
|
||||
func (h *sentPacketHandler) ReceivedBytes(n protocol.ByteCount, t monotime.Time) {
|
||||
h.connStats.BytesReceived.Add(uint64(n))
|
||||
wasAmplificationLimit := h.isAmplificationLimited()
|
||||
h.bytesReceived += n
|
||||
if wasAmplificationLimit && !h.isAmplificationLimited() {
|
||||
@@ -223,7 +231,8 @@ func (h *sentPacketHandler) ReceivedBytes(n protocol.ByteCount, t time.Time) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) ReceivedPacket(l protocol.EncryptionLevel, t time.Time) {
|
||||
func (h *sentPacketHandler) ReceivedPacket(l protocol.EncryptionLevel, t monotime.Time) {
|
||||
h.connStats.PacketsReceived.Add(1)
|
||||
if h.perspective == protocol.PerspectiveServer && l == protocol.EncryptionHandshake && !h.peerAddressValidated {
|
||||
h.peerAddressValidated = true
|
||||
h.setLossDetectionTimer(t)
|
||||
@@ -242,7 +251,7 @@ func (h *sentPacketHandler) packetsInFlight() int {
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) SentPacket(
|
||||
t time.Time,
|
||||
t monotime.Time,
|
||||
pn, largestAcked protocol.PacketNumber,
|
||||
streamFrames []StreamFrame,
|
||||
frames []Frame,
|
||||
@@ -253,6 +262,8 @@ func (h *sentPacketHandler) SentPacket(
|
||||
isPathProbePacket bool,
|
||||
) {
|
||||
h.bytesSent += size
|
||||
h.connStats.BytesSent.Add(uint64(size))
|
||||
h.connStats.PacketsSent.Add(1)
|
||||
|
||||
pnSpace := h.getPacketNumberSpace(encLevel)
|
||||
if h.logger.Debug() && (pnSpace.history.HasOutstandingPackets() || pnSpace.history.HasOutstandingPathProbes()) {
|
||||
@@ -267,12 +278,11 @@ func (h *sentPacketHandler) SentPacket(
|
||||
if isPathProbePacket {
|
||||
p := getPacket()
|
||||
p.SendTime = t
|
||||
p.PacketNumber = pn
|
||||
p.EncryptionLevel = encLevel
|
||||
p.Length = size
|
||||
p.Frames = frames
|
||||
p.isPathProbePacket = true
|
||||
pnSpace.history.SentPathProbePacket(p)
|
||||
pnSpace.history.SentPathProbePacket(pn, p)
|
||||
h.setLossDetectionTimer(t)
|
||||
return
|
||||
}
|
||||
@@ -299,7 +309,6 @@ func (h *sentPacketHandler) SentPacket(
|
||||
|
||||
p := getPacket()
|
||||
p.SendTime = t
|
||||
p.PacketNumber = pn
|
||||
p.EncryptionLevel = encLevel
|
||||
p.Length = size
|
||||
p.LargestAcked = largestAcked
|
||||
@@ -308,7 +317,7 @@ func (h *sentPacketHandler) SentPacket(
|
||||
p.IsPathMTUProbePacket = isPathMTUProbePacket
|
||||
p.includedInBytesInFlight = true
|
||||
|
||||
pnSpace.history.SentAckElicitingPacket(p)
|
||||
pnSpace.history.SentAckElicitingPacket(pn, p)
|
||||
if h.tracer != nil && h.tracer.UpdatedMetrics != nil {
|
||||
h.tracer.UpdatedMetrics(h.rttStats, h.congestion.GetCongestionWindow(), h.bytesInFlight, h.packetsInFlight())
|
||||
}
|
||||
@@ -328,7 +337,7 @@ func (h *sentPacketHandler) getPacketNumberSpace(encLevel protocol.EncryptionLev
|
||||
}
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.EncryptionLevel, rcvTime time.Time) (bool /* contained 1-RTT packet */, error) {
|
||||
func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.EncryptionLevel, rcvTime monotime.Time) (bool /* contained 1-RTT packet */, error) {
|
||||
pnSpace := h.getPacketNumberSpace(encLevel)
|
||||
|
||||
largestAcked := ack.LargestAcked()
|
||||
@@ -391,14 +400,27 @@ func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.En
|
||||
if p.EncryptionLevel == protocol.Encryption1RTT {
|
||||
acked1RTTPacket = true
|
||||
}
|
||||
h.removeFromBytesInFlight(p)
|
||||
h.removeFromBytesInFlight(p.packet)
|
||||
if !p.isPathProbePacket {
|
||||
putPacket(p)
|
||||
putPacket(p.packet)
|
||||
}
|
||||
}
|
||||
|
||||
// detect spurious losses for application data packets, if the ACK was not reordered
|
||||
if encLevel == protocol.Encryption1RTT && largestAcked == pnSpace.largestAcked {
|
||||
h.detectSpuriousLosses(
|
||||
ack,
|
||||
rcvTime.Add(-min(ack.DelayTime, h.rttStats.MaxAckDelay())),
|
||||
)
|
||||
// clean up lost packet history
|
||||
h.lostPackets.DeleteBefore(rcvTime.Add(-3 * h.rttStats.PTO(false)))
|
||||
}
|
||||
|
||||
// After this point, we must not use ackedPackets any longer!
|
||||
// We've already returned the buffers.
|
||||
ackedPackets = nil //nolint:ineffassign // This is just to be on the safe side.
|
||||
ackedPackets = nil //nolint:ineffassign // This is just to be on the safe side.
|
||||
clear(h.ackedPackets) // make sure the memory is released
|
||||
h.ackedPackets = h.ackedPackets[:0]
|
||||
|
||||
// Reset the pto_count unless the client is unsure if the server has validated the client's address.
|
||||
if h.peerCompletedAddressValidation {
|
||||
@@ -417,56 +439,100 @@ func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.En
|
||||
return acked1RTTPacket, nil
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) detectSpuriousLosses(ack *wire.AckFrame, ackTime monotime.Time) {
|
||||
var maxPacketReordering protocol.PacketNumber
|
||||
var maxTimeReordering time.Duration
|
||||
ackRangeIdx := len(ack.AckRanges) - 1
|
||||
var spuriousLosses []protocol.PacketNumber
|
||||
for pn, sendTime := range h.lostPackets.All() {
|
||||
ackRange := ack.AckRanges[ackRangeIdx]
|
||||
for pn > ackRange.Largest {
|
||||
// this should never happen, since detectSpuriousLosses is only called for ACKs that increase the largest acked
|
||||
if ackRangeIdx == 0 {
|
||||
break
|
||||
}
|
||||
ackRangeIdx--
|
||||
ackRange = ack.AckRanges[ackRangeIdx]
|
||||
}
|
||||
if pn < ackRange.Smallest {
|
||||
continue
|
||||
}
|
||||
if pn <= ackRange.Largest {
|
||||
packetReordering := h.appDataPackets.history.Difference(ack.LargestAcked(), pn)
|
||||
timeReordering := ackTime.Sub(sendTime)
|
||||
maxPacketReordering = max(maxPacketReordering, packetReordering)
|
||||
maxTimeReordering = max(maxTimeReordering, timeReordering)
|
||||
|
||||
if h.tracer != nil && h.tracer.DetectedSpuriousLoss != nil {
|
||||
h.tracer.DetectedSpuriousLoss(protocol.Encryption1RTT, pn, uint64(packetReordering), timeReordering)
|
||||
}
|
||||
spuriousLosses = append(spuriousLosses, pn)
|
||||
}
|
||||
}
|
||||
for _, pn := range spuriousLosses {
|
||||
h.lostPackets.Delete(pn)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) GetLowestPacketNotConfirmedAcked() protocol.PacketNumber {
|
||||
return h.lowestNotConfirmedAcked
|
||||
}
|
||||
|
||||
// Packets are returned in ascending packet number order.
|
||||
func (h *sentPacketHandler) detectAndRemoveAckedPackets(ack *wire.AckFrame, encLevel protocol.EncryptionLevel) ([]*packet, error) {
|
||||
func (h *sentPacketHandler) detectAndRemoveAckedPackets(ack *wire.AckFrame, encLevel protocol.EncryptionLevel) ([]packetWithPacketNumber, error) {
|
||||
if len(h.ackedPackets) > 0 {
|
||||
return nil, errors.New("ackhandler BUG: ackedPackets slice not empty")
|
||||
}
|
||||
|
||||
pnSpace := h.getPacketNumberSpace(encLevel)
|
||||
h.ackedPackets = h.ackedPackets[:0]
|
||||
ackRangeIndex := 0
|
||||
|
||||
if encLevel == protocol.Encryption1RTT {
|
||||
for p := range pnSpace.history.SkippedPackets() {
|
||||
if ack.AcksPacket(p) {
|
||||
return nil, &qerr.TransportError{
|
||||
ErrorCode: qerr.ProtocolViolation,
|
||||
ErrorMessage: fmt.Sprintf("received an ACK for skipped packet number: %d (%s)", p, encLevel),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ackRangeIndex int
|
||||
lowestAcked := ack.LowestAcked()
|
||||
largestAcked := ack.LargestAcked()
|
||||
for p := range pnSpace.history.Packets() {
|
||||
for pn, p := range pnSpace.history.Packets() {
|
||||
// ignore packets below the lowest acked
|
||||
if p.PacketNumber < lowestAcked {
|
||||
if pn < lowestAcked {
|
||||
continue
|
||||
}
|
||||
if p.PacketNumber > largestAcked {
|
||||
if pn > largestAcked {
|
||||
break
|
||||
}
|
||||
|
||||
if ack.HasMissingRanges() {
|
||||
ackRange := ack.AckRanges[len(ack.AckRanges)-1-ackRangeIndex]
|
||||
|
||||
for p.PacketNumber > ackRange.Largest && ackRangeIndex < len(ack.AckRanges)-1 {
|
||||
for pn > ackRange.Largest && ackRangeIndex < len(ack.AckRanges)-1 {
|
||||
ackRangeIndex++
|
||||
ackRange = ack.AckRanges[len(ack.AckRanges)-1-ackRangeIndex]
|
||||
}
|
||||
|
||||
if p.PacketNumber < ackRange.Smallest { // packet not contained in ACK range
|
||||
if pn < ackRange.Smallest { // packet not contained in ACK range
|
||||
continue
|
||||
}
|
||||
if p.PacketNumber > ackRange.Largest {
|
||||
return nil, fmt.Errorf("BUG: ackhandler would have acked wrong packet %d, while evaluating range %d -> %d", p.PacketNumber, ackRange.Smallest, ackRange.Largest)
|
||||
}
|
||||
}
|
||||
if p.skippedPacket {
|
||||
return nil, &qerr.TransportError{
|
||||
ErrorCode: qerr.ProtocolViolation,
|
||||
ErrorMessage: fmt.Sprintf("received an ACK for skipped packet number: %d (%s)", p.PacketNumber, encLevel),
|
||||
if pn > ackRange.Largest {
|
||||
return nil, fmt.Errorf("BUG: ackhandler would have acked wrong packet %d, while evaluating range %d -> %d", pn, ackRange.Smallest, ackRange.Largest)
|
||||
}
|
||||
}
|
||||
if p.isPathProbePacket {
|
||||
probePacket := pnSpace.history.RemovePathProbe(p.PacketNumber)
|
||||
probePacket := pnSpace.history.RemovePathProbe(pn)
|
||||
// the probe packet might already have been declared lost
|
||||
if probePacket != nil {
|
||||
h.ackedPackets = append(h.ackedPackets, probePacket)
|
||||
h.ackedPackets = append(h.ackedPackets, packetWithPacketNumber{PacketNumber: pn, packet: probePacket})
|
||||
}
|
||||
continue
|
||||
}
|
||||
h.ackedPackets = append(h.ackedPackets, p)
|
||||
h.ackedPackets = append(h.ackedPackets, packetWithPacketNumber{PacketNumber: pn, packet: p})
|
||||
}
|
||||
if h.logger.Debug() && len(h.ackedPackets) > 0 {
|
||||
pns := make([]protocol.PacketNumber, len(h.ackedPackets))
|
||||
@@ -501,9 +567,9 @@ func (h *sentPacketHandler) detectAndRemoveAckedPackets(ack *wire.AckFrame, encL
|
||||
return h.ackedPackets, nil
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) getLossTimeAndSpace() (time.Time, protocol.EncryptionLevel) {
|
||||
func (h *sentPacketHandler) getLossTimeAndSpace() (monotime.Time, protocol.EncryptionLevel) {
|
||||
var encLevel protocol.EncryptionLevel
|
||||
var lossTime time.Time
|
||||
var lossTime monotime.Time
|
||||
|
||||
if h.initialPackets != nil {
|
||||
lossTime = h.initialPackets.lossTime
|
||||
@@ -529,7 +595,7 @@ func (h *sentPacketHandler) getScaledPTO(includeMaxAckDelay bool) time.Duration
|
||||
}
|
||||
|
||||
// same logic as getLossTimeAndSpace, but for lastAckElicitingPacketTime instead of lossTime
|
||||
func (h *sentPacketHandler) getPTOTimeAndSpace(now time.Time) (pto time.Time, encLevel protocol.EncryptionLevel) {
|
||||
func (h *sentPacketHandler) getPTOTimeAndSpace(now monotime.Time) (pto monotime.Time, encLevel protocol.EncryptionLevel) {
|
||||
// We only send application data probe packets once the handshake is confirmed,
|
||||
// because before that, we don't have the keys to decrypt ACKs sent in 1-RTT packets.
|
||||
if !h.handshakeConfirmed && !h.hasOutstandingCryptoPackets() {
|
||||
@@ -579,7 +645,7 @@ func (h *sentPacketHandler) hasOutstandingCryptoPackets() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) setLossDetectionTimer(now time.Time) {
|
||||
func (h *sentPacketHandler) setLossDetectionTimer(now monotime.Time) {
|
||||
oldAlarm := h.alarm // only needed in case tracing is enabled
|
||||
newAlarm := h.lossDetectionTime(now)
|
||||
h.alarm = newAlarm
|
||||
@@ -593,11 +659,11 @@ func (h *sentPacketHandler) setLossDetectionTimer(now time.Time) {
|
||||
}
|
||||
|
||||
if hasAlarm && h.tracer != nil && h.tracer.SetLossTimer != nil && newAlarm != oldAlarm {
|
||||
h.tracer.SetLossTimer(newAlarm.TimerType, newAlarm.EncryptionLevel, newAlarm.Time)
|
||||
h.tracer.SetLossTimer(newAlarm.TimerType, newAlarm.EncryptionLevel, newAlarm.Time.ToTime())
|
||||
}
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) lossDetectionTime(now time.Time) alarmTimer {
|
||||
func (h *sentPacketHandler) lossDetectionTime(now monotime.Time) alarmTimer {
|
||||
// cancel the alarm if no packets are outstanding
|
||||
if h.peerCompletedAddressValidation && !h.hasOutstandingCryptoPackets() &&
|
||||
!h.appDataPackets.history.HasOutstandingPackets() && !h.appDataPackets.history.HasOutstandingPathProbes() {
|
||||
@@ -609,9 +675,9 @@ func (h *sentPacketHandler) lossDetectionTime(now time.Time) alarmTimer {
|
||||
return alarmTimer{}
|
||||
}
|
||||
|
||||
var pathProbeLossTime time.Time
|
||||
var pathProbeLossTime monotime.Time
|
||||
if h.appDataPackets.history.HasOutstandingPathProbes() {
|
||||
if p := h.appDataPackets.history.FirstOutstandingPathProbe(); p != nil {
|
||||
if _, p := h.appDataPackets.history.FirstOutstandingPathProbe(); p != nil {
|
||||
pathProbeLossTime = p.SendTime.Add(pathProbePacketLossTimeout)
|
||||
}
|
||||
}
|
||||
@@ -643,16 +709,16 @@ func (h *sentPacketHandler) lossDetectionTime(now time.Time) alarmTimer {
|
||||
return alarmTimer{}
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) detectLostPathProbes(now time.Time) {
|
||||
func (h *sentPacketHandler) detectLostPathProbes(now monotime.Time) {
|
||||
if !h.appDataPackets.history.HasOutstandingPathProbes() {
|
||||
return
|
||||
}
|
||||
lossTime := now.Add(-pathProbePacketLossTimeout)
|
||||
// RemovePathProbe cannot be called while iterating.
|
||||
var lostPathProbes []*packet
|
||||
for p := range h.appDataPackets.history.PathProbes() {
|
||||
var lostPathProbes []packetWithPacketNumber
|
||||
for pn, p := range h.appDataPackets.history.PathProbes() {
|
||||
if !p.SendTime.After(lossTime) {
|
||||
lostPathProbes = append(lostPathProbes, p)
|
||||
lostPathProbes = append(lostPathProbes, packetWithPacketNumber{PacketNumber: pn, packet: p})
|
||||
}
|
||||
}
|
||||
for _, p := range lostPathProbes {
|
||||
@@ -663,9 +729,9 @@ func (h *sentPacketHandler) detectLostPathProbes(now time.Time) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) detectLostPackets(now time.Time, encLevel protocol.EncryptionLevel) {
|
||||
func (h *sentPacketHandler) detectLostPackets(now monotime.Time, encLevel protocol.EncryptionLevel) {
|
||||
pnSpace := h.getPacketNumberSpace(encLevel)
|
||||
pnSpace.lossTime = time.Time{}
|
||||
pnSpace.lossTime = 0
|
||||
|
||||
maxRTT := float64(max(h.rttStats.LatestRTT(), h.rttStats.SmoothedRTT()))
|
||||
lossDelay := time.Duration(timeThreshold * maxRTT)
|
||||
@@ -677,59 +743,61 @@ func (h *sentPacketHandler) detectLostPackets(now time.Time, encLevel protocol.E
|
||||
lostSendTime := now.Add(-lossDelay)
|
||||
|
||||
priorInFlight := h.bytesInFlight
|
||||
for p := range pnSpace.history.Packets() {
|
||||
if p.PacketNumber > pnSpace.largestAcked {
|
||||
for pn, p := range pnSpace.history.Packets() {
|
||||
if pn > pnSpace.largestAcked {
|
||||
break
|
||||
}
|
||||
|
||||
isRegularPacket := !p.skippedPacket && !p.isPathProbePacket
|
||||
var packetLost bool
|
||||
if !p.SendTime.After(lostSendTime) {
|
||||
packetLost = true
|
||||
if isRegularPacket {
|
||||
if !p.isPathProbePacket {
|
||||
if h.logger.Debug() {
|
||||
h.logger.Debugf("\tlost packet %d (time threshold)", p.PacketNumber)
|
||||
h.logger.Debugf("\tlost packet %d (time threshold)", pn)
|
||||
}
|
||||
if h.tracer != nil && h.tracer.LostPacket != nil {
|
||||
h.tracer.LostPacket(p.EncryptionLevel, p.PacketNumber, logging.PacketLossTimeThreshold)
|
||||
h.tracer.LostPacket(p.EncryptionLevel, pn, logging.PacketLossTimeThreshold)
|
||||
}
|
||||
}
|
||||
} else if pnSpace.largestAcked >= p.PacketNumber+packetThreshold {
|
||||
} else if pnSpace.history.Difference(pnSpace.largestAcked, pn) >= packetThreshold {
|
||||
packetLost = true
|
||||
if isRegularPacket {
|
||||
if !p.isPathProbePacket {
|
||||
if h.logger.Debug() {
|
||||
h.logger.Debugf("\tlost packet %d (reordering threshold)", p.PacketNumber)
|
||||
h.logger.Debugf("\tlost packet %d (reordering threshold)", pn)
|
||||
}
|
||||
if h.tracer != nil && h.tracer.LostPacket != nil {
|
||||
h.tracer.LostPacket(p.EncryptionLevel, p.PacketNumber, logging.PacketLossReorderingThreshold)
|
||||
h.tracer.LostPacket(p.EncryptionLevel, pn, logging.PacketLossReorderingThreshold)
|
||||
}
|
||||
}
|
||||
} else if pnSpace.lossTime.IsZero() {
|
||||
// Note: This conditional is only entered once per call
|
||||
lossTime := p.SendTime.Add(lossDelay)
|
||||
if h.logger.Debug() {
|
||||
h.logger.Debugf("\tsetting loss timer for packet %d (%s) to %s (in %s)", p.PacketNumber, encLevel, lossDelay, lossTime)
|
||||
h.logger.Debugf("\tsetting loss timer for packet %d (%s) to %s (in %s)", pn, encLevel, lossDelay, lossTime)
|
||||
}
|
||||
pnSpace.lossTime = lossTime
|
||||
}
|
||||
if packetLost {
|
||||
pnSpace.history.DeclareLost(p.PacketNumber)
|
||||
if isRegularPacket {
|
||||
if encLevel == protocol.Encryption0RTT || encLevel == protocol.Encryption1RTT {
|
||||
h.lostPackets.Add(pn, p.SendTime)
|
||||
}
|
||||
pnSpace.history.DeclareLost(pn)
|
||||
if !p.isPathProbePacket {
|
||||
// the bytes in flight need to be reduced no matter if the frames in this packet will be retransmitted
|
||||
h.removeFromBytesInFlight(p)
|
||||
h.queueFramesForRetransmission(p)
|
||||
if !p.IsPathMTUProbePacket {
|
||||
h.congestion.OnCongestionEvent(p.PacketNumber, p.Length, priorInFlight)
|
||||
h.congestion.OnCongestionEvent(pn, p.Length, priorInFlight)
|
||||
}
|
||||
if encLevel == protocol.Encryption1RTT && h.ecnTracker != nil {
|
||||
h.ecnTracker.LostPacket(p.PacketNumber)
|
||||
h.ecnTracker.LostPacket(pn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) OnLossDetectionTimeout(now time.Time) error {
|
||||
func (h *sentPacketHandler) OnLossDetectionTimeout(now monotime.Time) error {
|
||||
defer h.setLossDetectionTimer(now)
|
||||
|
||||
if h.handshakeConfirmed {
|
||||
@@ -805,7 +873,7 @@ func (h *sentPacketHandler) OnLossDetectionTimeout(now time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) GetLossDetectionTimeout() time.Time {
|
||||
func (h *sentPacketHandler) GetLossDetectionTimeout() monotime.Time {
|
||||
return h.alarm.Time
|
||||
}
|
||||
|
||||
@@ -839,7 +907,7 @@ func (h *sentPacketHandler) PopPacketNumber(encLevel protocol.EncryptionLevel) p
|
||||
return pn
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) SendMode(now time.Time) SendMode {
|
||||
func (h *sentPacketHandler) SendMode(now monotime.Time) SendMode {
|
||||
numTrackedPackets := h.appDataPackets.history.Len()
|
||||
if h.initialPackets != nil {
|
||||
numTrackedPackets += h.initialPackets.history.Len()
|
||||
@@ -884,7 +952,7 @@ func (h *sentPacketHandler) SendMode(now time.Time) SendMode {
|
||||
return SendAny
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) TimeUntilSend() time.Time {
|
||||
func (h *sentPacketHandler) TimeUntilSend() monotime.Time {
|
||||
return h.congestion.TimeUntilSend(h.bytesInFlight)
|
||||
}
|
||||
|
||||
@@ -901,7 +969,7 @@ func (h *sentPacketHandler) isAmplificationLimited() bool {
|
||||
|
||||
func (h *sentPacketHandler) QueueProbePacket(encLevel protocol.EncryptionLevel) bool {
|
||||
pnSpace := h.getPacketNumberSpace(encLevel)
|
||||
p := pnSpace.history.FirstOutstanding()
|
||||
pn, p := pnSpace.history.FirstOutstanding()
|
||||
if p == nil {
|
||||
return false
|
||||
}
|
||||
@@ -909,7 +977,7 @@ func (h *sentPacketHandler) QueueProbePacket(encLevel protocol.EncryptionLevel)
|
||||
// TODO: don't declare the packet lost here.
|
||||
// Keep track of acknowledged frames instead.
|
||||
h.removeFromBytesInFlight(p)
|
||||
pnSpace.history.DeclareLost(p.PacketNumber)
|
||||
pnSpace.history.DeclareLost(pn)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -931,21 +999,21 @@ func (h *sentPacketHandler) queueFramesForRetransmission(p *packet) {
|
||||
p.Frames = nil
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) ResetForRetry(now time.Time) {
|
||||
func (h *sentPacketHandler) ResetForRetry(now monotime.Time) {
|
||||
h.bytesInFlight = 0
|
||||
var firstPacketSendTime time.Time
|
||||
for p := range h.initialPackets.history.Packets() {
|
||||
var firstPacketSendTime monotime.Time
|
||||
for _, p := range h.initialPackets.history.Packets() {
|
||||
if firstPacketSendTime.IsZero() {
|
||||
firstPacketSendTime = p.SendTime
|
||||
}
|
||||
if !p.declaredLost && !p.skippedPacket {
|
||||
if !p.declaredLost {
|
||||
h.queueFramesForRetransmission(p)
|
||||
}
|
||||
}
|
||||
// All application data packets sent at this point are 0-RTT packets.
|
||||
// In the case of a Retry, we can assume that the server dropped all of them.
|
||||
for p := range h.appDataPackets.history.Packets() {
|
||||
if !p.declaredLost && !p.skippedPacket {
|
||||
for _, p := range h.appDataPackets.history.Packets() {
|
||||
if !p.declaredLost {
|
||||
h.queueFramesForRetransmission(p)
|
||||
}
|
||||
}
|
||||
@@ -977,21 +1045,22 @@ func (h *sentPacketHandler) ResetForRetry(now time.Time) {
|
||||
h.ptoCount = 0
|
||||
}
|
||||
|
||||
func (h *sentPacketHandler) MigratedPath(now time.Time, initialMaxDatagramSize protocol.ByteCount) {
|
||||
func (h *sentPacketHandler) MigratedPath(now monotime.Time, initialMaxDatagramSize protocol.ByteCount) {
|
||||
h.rttStats.ResetForPathMigration()
|
||||
for p := range h.appDataPackets.history.Packets() {
|
||||
h.appDataPackets.history.DeclareLost(p.PacketNumber)
|
||||
if !p.skippedPacket && !p.isPathProbePacket {
|
||||
for pn, p := range h.appDataPackets.history.Packets() {
|
||||
h.appDataPackets.history.DeclareLost(pn)
|
||||
if !p.isPathProbePacket {
|
||||
h.removeFromBytesInFlight(p)
|
||||
h.queueFramesForRetransmission(p)
|
||||
}
|
||||
}
|
||||
for p := range h.appDataPackets.history.PathProbes() {
|
||||
h.appDataPackets.history.RemovePathProbe(p.PacketNumber)
|
||||
for pn := range h.appDataPackets.history.PathProbes() {
|
||||
h.appDataPackets.history.RemovePathProbe(pn)
|
||||
}
|
||||
h.congestion = congestion.NewCubicSender(
|
||||
congestion.DefaultClock{},
|
||||
h.rttStats,
|
||||
h.connStats,
|
||||
initialMaxDatagramSize,
|
||||
true, // use Reno
|
||||
h.tracer,
|
||||
|
||||
+86
-39
@@ -3,25 +3,32 @@ package ackhandler
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"slices"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
)
|
||||
|
||||
const maxSkippedPackets = 4
|
||||
|
||||
type sentPacketHistory struct {
|
||||
packets []*packet
|
||||
pathProbePackets []*packet
|
||||
pathProbePackets []packetWithPacketNumber
|
||||
skippedPackets []protocol.PacketNumber
|
||||
|
||||
numOutstanding int
|
||||
|
||||
firstPacketNumber protocol.PacketNumber
|
||||
highestPacketNumber protocol.PacketNumber
|
||||
}
|
||||
|
||||
func newSentPacketHistory(isAppData bool) *sentPacketHistory {
|
||||
h := &sentPacketHistory{
|
||||
highestPacketNumber: protocol.InvalidPacketNumber,
|
||||
firstPacketNumber: protocol.InvalidPacketNumber,
|
||||
}
|
||||
if isAppData {
|
||||
h.packets = make([]*packet, 0, 32)
|
||||
h.skippedPackets = make([]protocol.PacketNumber, 0, maxSkippedPackets)
|
||||
} else {
|
||||
h.packets = make([]*packet, 0, 6)
|
||||
}
|
||||
@@ -35,14 +42,20 @@ func (h *sentPacketHistory) checkSequentialPacketNumberUse(pn protocol.PacketNum
|
||||
}
|
||||
}
|
||||
h.highestPacketNumber = pn
|
||||
if len(h.packets) == 0 {
|
||||
h.firstPacketNumber = pn
|
||||
}
|
||||
}
|
||||
|
||||
func (h *sentPacketHistory) SkippedPacket(pn protocol.PacketNumber) {
|
||||
h.checkSequentialPacketNumberUse(pn)
|
||||
h.packets = append(h.packets, &packet{
|
||||
PacketNumber: pn,
|
||||
skippedPacket: true,
|
||||
})
|
||||
if len(h.packets) > 0 {
|
||||
h.packets = append(h.packets, nil)
|
||||
}
|
||||
if len(h.skippedPackets) == maxSkippedPackets {
|
||||
h.skippedPackets = slices.Delete(h.skippedPackets, 0, 1)
|
||||
}
|
||||
h.skippedPackets = append(h.skippedPackets, pn)
|
||||
}
|
||||
|
||||
func (h *sentPacketHistory) SentNonAckElicitingPacket(pn protocol.PacketNumber) {
|
||||
@@ -52,40 +65,40 @@ func (h *sentPacketHistory) SentNonAckElicitingPacket(pn protocol.PacketNumber)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *sentPacketHistory) SentAckElicitingPacket(p *packet) {
|
||||
h.checkSequentialPacketNumberUse(p.PacketNumber)
|
||||
func (h *sentPacketHistory) SentAckElicitingPacket(pn protocol.PacketNumber, p *packet) {
|
||||
h.checkSequentialPacketNumberUse(pn)
|
||||
h.packets = append(h.packets, p)
|
||||
if p.outstanding() {
|
||||
h.numOutstanding++
|
||||
}
|
||||
}
|
||||
|
||||
func (h *sentPacketHistory) SentPathProbePacket(p *packet) {
|
||||
h.checkSequentialPacketNumberUse(p.PacketNumber)
|
||||
h.packets = append(h.packets, &packet{
|
||||
PacketNumber: p.PacketNumber,
|
||||
isPathProbePacket: true,
|
||||
})
|
||||
h.pathProbePackets = append(h.pathProbePackets, p)
|
||||
func (h *sentPacketHistory) SentPathProbePacket(pn protocol.PacketNumber, p *packet) {
|
||||
h.checkSequentialPacketNumberUse(pn)
|
||||
h.packets = append(h.packets, &packet{isPathProbePacket: true})
|
||||
h.pathProbePackets = append(h.pathProbePackets, packetWithPacketNumber{PacketNumber: pn, packet: p})
|
||||
}
|
||||
|
||||
func (h *sentPacketHistory) Packets() iter.Seq[*packet] {
|
||||
return func(yield func(*packet) bool) {
|
||||
for _, p := range h.packets {
|
||||
func (h *sentPacketHistory) Packets() iter.Seq2[protocol.PacketNumber, *packet] {
|
||||
return func(yield func(protocol.PacketNumber, *packet) bool) {
|
||||
// h.firstPacketNumber might be updated in the yield function,
|
||||
// so we need to save it here.
|
||||
firstPacketNumber := h.firstPacketNumber
|
||||
for i, p := range h.packets {
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
if !yield(p) {
|
||||
if !yield(firstPacketNumber+protocol.PacketNumber(i), p) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *sentPacketHistory) PathProbes() iter.Seq[*packet] {
|
||||
return func(yield func(*packet) bool) {
|
||||
func (h *sentPacketHistory) PathProbes() iter.Seq2[protocol.PacketNumber, *packet] {
|
||||
return func(yield func(protocol.PacketNumber, *packet) bool) {
|
||||
for _, p := range h.pathProbePackets {
|
||||
if !yield(p) {
|
||||
if !yield(p.PacketNumber, p.packet) {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -93,30 +106,42 @@ func (h *sentPacketHistory) PathProbes() iter.Seq[*packet] {
|
||||
}
|
||||
|
||||
// FirstOutstanding returns the first outstanding packet.
|
||||
func (h *sentPacketHistory) FirstOutstanding() *packet {
|
||||
func (h *sentPacketHistory) FirstOutstanding() (protocol.PacketNumber, *packet) {
|
||||
if !h.HasOutstandingPackets() {
|
||||
return nil
|
||||
return protocol.InvalidPacketNumber, nil
|
||||
}
|
||||
for _, p := range h.packets {
|
||||
for i, p := range h.packets {
|
||||
if p != nil && p.outstanding() {
|
||||
return p
|
||||
return h.firstPacketNumber + protocol.PacketNumber(i), p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return protocol.InvalidPacketNumber, nil
|
||||
}
|
||||
|
||||
// FirstOutstandingPathProbe returns the first outstanding path probe packet
|
||||
func (h *sentPacketHistory) FirstOutstandingPathProbe() *packet {
|
||||
func (h *sentPacketHistory) FirstOutstandingPathProbe() (protocol.PacketNumber, *packet) {
|
||||
if len(h.pathProbePackets) == 0 {
|
||||
return nil
|
||||
return protocol.InvalidPacketNumber, nil
|
||||
}
|
||||
return h.pathProbePackets[0].PacketNumber, h.pathProbePackets[0].packet
|
||||
}
|
||||
|
||||
func (h *sentPacketHistory) SkippedPackets() iter.Seq[protocol.PacketNumber] {
|
||||
return func(yield func(protocol.PacketNumber) bool) {
|
||||
for _, p := range h.skippedPackets {
|
||||
if !yield(p) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return h.pathProbePackets[0]
|
||||
}
|
||||
|
||||
func (h *sentPacketHistory) Len() int {
|
||||
return len(h.packets)
|
||||
}
|
||||
|
||||
// Remove removes a packet from the sent packet history.
|
||||
// It must not be used for skipped packet numbers.
|
||||
func (h *sentPacketHistory) Remove(pn protocol.PacketNumber) error {
|
||||
idx, ok := h.getIndex(pn)
|
||||
if !ok {
|
||||
@@ -131,19 +156,19 @@ func (h *sentPacketHistory) Remove(pn protocol.PacketNumber) error {
|
||||
}
|
||||
h.packets[idx] = nil
|
||||
// clean up all skipped packets directly before this packet number
|
||||
var hasPacketBefore bool
|
||||
for idx > 0 {
|
||||
idx--
|
||||
p := h.packets[idx]
|
||||
if p == nil || !p.skippedPacket {
|
||||
if h.packets[idx] != nil {
|
||||
hasPacketBefore = true
|
||||
break
|
||||
}
|
||||
h.packets[idx] = nil
|
||||
}
|
||||
if idx == 0 {
|
||||
if !hasPacketBefore {
|
||||
h.cleanupStart()
|
||||
}
|
||||
if len(h.packets) > 0 && h.packets[0] == nil {
|
||||
panic("remove failed")
|
||||
panic("cleanup failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -156,7 +181,7 @@ func (h *sentPacketHistory) RemovePathProbe(pn protocol.PacketNumber) *packet {
|
||||
idx := -1
|
||||
for i, p := range h.pathProbePackets {
|
||||
if p.PacketNumber == pn {
|
||||
packetToDelete = p
|
||||
packetToDelete = p.packet
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
@@ -174,11 +199,10 @@ func (h *sentPacketHistory) getIndex(p protocol.PacketNumber) (int, bool) {
|
||||
if len(h.packets) == 0 {
|
||||
return 0, false
|
||||
}
|
||||
first := h.packets[0].PacketNumber
|
||||
if p < first {
|
||||
if p < h.firstPacketNumber {
|
||||
return 0, false
|
||||
}
|
||||
index := int(p - first)
|
||||
index := int(p - h.firstPacketNumber)
|
||||
if index > len(h.packets)-1 {
|
||||
return 0, false
|
||||
}
|
||||
@@ -198,17 +222,19 @@ func (h *sentPacketHistory) cleanupStart() {
|
||||
for i, p := range h.packets {
|
||||
if p != nil {
|
||||
h.packets = h.packets[i:]
|
||||
h.firstPacketNumber += protocol.PacketNumber(i)
|
||||
return
|
||||
}
|
||||
}
|
||||
h.packets = h.packets[:0]
|
||||
h.firstPacketNumber = protocol.InvalidPacketNumber
|
||||
}
|
||||
|
||||
func (h *sentPacketHistory) LowestPacketNumber() protocol.PacketNumber {
|
||||
if len(h.packets) == 0 {
|
||||
return protocol.InvalidPacketNumber
|
||||
}
|
||||
return h.packets[0].PacketNumber
|
||||
return h.firstPacketNumber
|
||||
}
|
||||
|
||||
func (h *sentPacketHistory) DeclareLost(pn protocol.PacketNumber) {
|
||||
@@ -228,3 +254,24 @@ func (h *sentPacketHistory) DeclareLost(pn protocol.PacketNumber) {
|
||||
h.cleanupStart()
|
||||
}
|
||||
}
|
||||
|
||||
// Difference returns the difference between two packet numbers a and b (a - b),
|
||||
// taking into account any skipped packet numbers between them.
|
||||
//
|
||||
// Note that old skipped packets are garbage collected at some point,
|
||||
// so this function is not guaranteed to return the correct result after a while.
|
||||
func (h *sentPacketHistory) Difference(a, b protocol.PacketNumber) protocol.PacketNumber {
|
||||
diff := a - b
|
||||
if len(h.skippedPackets) == 0 {
|
||||
return diff
|
||||
}
|
||||
if a < h.skippedPackets[0] || b > h.skippedPackets[len(h.skippedPackets)-1] {
|
||||
return diff
|
||||
}
|
||||
for _, p := range h.skippedPackets {
|
||||
if p > b && p < a {
|
||||
diff--
|
||||
}
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
+6
-4
@@ -1,10 +1,12 @@
|
||||
package congestion
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
)
|
||||
|
||||
// A Clock returns the current time
|
||||
type Clock interface {
|
||||
Now() time.Time
|
||||
Now() monotime.Time
|
||||
}
|
||||
|
||||
// DefaultClock implements the Clock interface using the Go stdlib clock.
|
||||
@@ -13,6 +15,6 @@ type DefaultClock struct{}
|
||||
var _ Clock = DefaultClock{}
|
||||
|
||||
// Now gets the current time
|
||||
func (DefaultClock) Now() time.Time {
|
||||
return time.Now()
|
||||
func (DefaultClock) Now() monotime.Time {
|
||||
return monotime.Now()
|
||||
}
|
||||
|
||||
+6
-5
@@ -4,6 +4,7 @@ import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
)
|
||||
|
||||
@@ -42,7 +43,7 @@ type Cubic struct {
|
||||
numConnections int
|
||||
|
||||
// Time when this cycle started, after last loss event.
|
||||
epoch time.Time
|
||||
epoch monotime.Time
|
||||
|
||||
// Max congestion window used just before last loss event.
|
||||
// Note: to improve fairness to other streams an additional back off is
|
||||
@@ -77,7 +78,7 @@ func NewCubic(clock Clock) *Cubic {
|
||||
|
||||
// Reset is called after a timeout to reset the cubic state
|
||||
func (c *Cubic) Reset() {
|
||||
c.epoch = time.Time{}
|
||||
c.epoch = 0
|
||||
c.lastMaxCongestionWindow = 0
|
||||
c.ackedBytesCount = 0
|
||||
c.estimatedTCPcongestionWindow = 0
|
||||
@@ -121,7 +122,7 @@ func (c *Cubic) OnApplicationLimited() {
|
||||
// in such a period. This reset effectively freezes congestion window growth
|
||||
// through application-limited periods and allows Cubic growth to continue
|
||||
// when the entire window is being used.
|
||||
c.epoch = time.Time{}
|
||||
c.epoch = 0
|
||||
}
|
||||
|
||||
// CongestionWindowAfterPacketLoss computes a new congestion window to use after
|
||||
@@ -135,7 +136,7 @@ func (c *Cubic) CongestionWindowAfterPacketLoss(currentCongestionWindow protocol
|
||||
} else {
|
||||
c.lastMaxCongestionWindow = currentCongestionWindow
|
||||
}
|
||||
c.epoch = time.Time{} // Reset time.
|
||||
c.epoch = 0 // Reset time.
|
||||
return protocol.ByteCount(float32(currentCongestionWindow) * c.beta())
|
||||
}
|
||||
|
||||
@@ -147,7 +148,7 @@ func (c *Cubic) CongestionWindowAfterAck(
|
||||
ackedBytes protocol.ByteCount,
|
||||
currentCongestionWindow protocol.ByteCount,
|
||||
delayMin time.Duration,
|
||||
eventTime time.Time,
|
||||
eventTime monotime.Time,
|
||||
) protocol.ByteCount {
|
||||
c.ackedBytesCount += ackedBytes
|
||||
|
||||
|
||||
+18
-7
@@ -2,8 +2,8 @@ package congestion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
"github.com/quic-go/quic-go/logging"
|
||||
@@ -22,6 +22,7 @@ const (
|
||||
type cubicSender struct {
|
||||
hybridSlowStart HybridSlowStart
|
||||
rttStats *utils.RTTStats
|
||||
connStats *utils.ConnectionStats
|
||||
cubic *Cubic
|
||||
pacer *pacer
|
||||
clock Clock
|
||||
@@ -68,6 +69,7 @@ var (
|
||||
func NewCubicSender(
|
||||
clock Clock,
|
||||
rttStats *utils.RTTStats,
|
||||
connStats *utils.ConnectionStats,
|
||||
initialMaxDatagramSize protocol.ByteCount,
|
||||
reno bool,
|
||||
tracer *logging.ConnectionTracer,
|
||||
@@ -75,6 +77,7 @@ func NewCubicSender(
|
||||
return newCubicSender(
|
||||
clock,
|
||||
rttStats,
|
||||
connStats,
|
||||
reno,
|
||||
initialMaxDatagramSize,
|
||||
initialCongestionWindow*initialMaxDatagramSize,
|
||||
@@ -86,6 +89,7 @@ func NewCubicSender(
|
||||
func newCubicSender(
|
||||
clock Clock,
|
||||
rttStats *utils.RTTStats,
|
||||
connStats *utils.ConnectionStats,
|
||||
reno bool,
|
||||
initialMaxDatagramSize,
|
||||
initialCongestionWindow,
|
||||
@@ -94,6 +98,7 @@ func newCubicSender(
|
||||
) *cubicSender {
|
||||
c := &cubicSender{
|
||||
rttStats: rttStats,
|
||||
connStats: connStats,
|
||||
largestSentPacketNumber: protocol.InvalidPacketNumber,
|
||||
largestAckedPacketNumber: protocol.InvalidPacketNumber,
|
||||
largestSentAtLastCutback: protocol.InvalidPacketNumber,
|
||||
@@ -116,11 +121,11 @@ func newCubicSender(
|
||||
}
|
||||
|
||||
// TimeUntilSend returns when the next packet should be sent.
|
||||
func (c *cubicSender) TimeUntilSend(_ protocol.ByteCount) time.Time {
|
||||
func (c *cubicSender) TimeUntilSend(_ protocol.ByteCount) monotime.Time {
|
||||
return c.pacer.TimeUntilSend()
|
||||
}
|
||||
|
||||
func (c *cubicSender) HasPacingBudget(now time.Time) bool {
|
||||
func (c *cubicSender) HasPacingBudget(now monotime.Time) bool {
|
||||
return c.pacer.Budget(now) >= c.maxDatagramSize
|
||||
}
|
||||
|
||||
@@ -133,7 +138,7 @@ func (c *cubicSender) minCongestionWindow() protocol.ByteCount {
|
||||
}
|
||||
|
||||
func (c *cubicSender) OnPacketSent(
|
||||
sentTime time.Time,
|
||||
sentTime monotime.Time,
|
||||
_ protocol.ByteCount,
|
||||
packetNumber protocol.PacketNumber,
|
||||
bytes protocol.ByteCount,
|
||||
@@ -176,7 +181,7 @@ func (c *cubicSender) OnPacketAcked(
|
||||
ackedPacketNumber protocol.PacketNumber,
|
||||
ackedBytes protocol.ByteCount,
|
||||
priorInFlight protocol.ByteCount,
|
||||
eventTime time.Time,
|
||||
eventTime monotime.Time,
|
||||
) {
|
||||
c.largestAckedPacketNumber = max(ackedPacketNumber, c.largestAckedPacketNumber)
|
||||
if c.InRecovery() {
|
||||
@@ -189,6 +194,9 @@ func (c *cubicSender) OnPacketAcked(
|
||||
}
|
||||
|
||||
func (c *cubicSender) OnCongestionEvent(packetNumber protocol.PacketNumber, lostBytes, priorInFlight protocol.ByteCount) {
|
||||
c.connStats.PacketsLost.Add(1)
|
||||
c.connStats.BytesLost.Add(uint64(lostBytes))
|
||||
|
||||
// TCP NewReno (RFC6582) says that once a loss occurs, any losses in packets
|
||||
// already sent should be treated as a single loss event, since it's expected.
|
||||
if packetNumber <= c.largestSentAtLastCutback {
|
||||
@@ -218,7 +226,7 @@ func (c *cubicSender) maybeIncreaseCwnd(
|
||||
_ protocol.PacketNumber,
|
||||
ackedBytes protocol.ByteCount,
|
||||
priorInFlight protocol.ByteCount,
|
||||
eventTime time.Time,
|
||||
eventTime monotime.Time,
|
||||
) {
|
||||
// Do not increase the congestion window unless the sender is close to using
|
||||
// the current window.
|
||||
@@ -246,7 +254,10 @@ func (c *cubicSender) maybeIncreaseCwnd(
|
||||
c.numAckedPackets = 0
|
||||
}
|
||||
} else {
|
||||
c.congestionWindow = min(c.maxCongestionWindow(), c.cubic.CongestionWindowAfterAck(ackedBytes, c.congestionWindow, c.rttStats.MinRTT(), eventTime))
|
||||
c.congestionWindow = min(
|
||||
c.maxCongestionWindow(),
|
||||
c.cubic.CongestionWindowAfterAck(ackedBytes, c.congestionWindow, c.rttStats.MinRTT(), eventTime),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-6
@@ -1,19 +1,18 @@
|
||||
package congestion
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
)
|
||||
|
||||
// A SendAlgorithm performs congestion control
|
||||
type SendAlgorithm interface {
|
||||
TimeUntilSend(bytesInFlight protocol.ByteCount) time.Time
|
||||
HasPacingBudget(now time.Time) bool
|
||||
OnPacketSent(sentTime time.Time, bytesInFlight protocol.ByteCount, packetNumber protocol.PacketNumber, bytes protocol.ByteCount, isRetransmittable bool)
|
||||
TimeUntilSend(bytesInFlight protocol.ByteCount) monotime.Time
|
||||
HasPacingBudget(now monotime.Time) bool
|
||||
OnPacketSent(sentTime monotime.Time, bytesInFlight protocol.ByteCount, packetNumber protocol.PacketNumber, bytes protocol.ByteCount, isRetransmittable bool)
|
||||
CanSend(bytesInFlight protocol.ByteCount) bool
|
||||
MaybeExitSlowStart()
|
||||
OnPacketAcked(number protocol.PacketNumber, ackedBytes protocol.ByteCount, priorInFlight protocol.ByteCount, eventTime time.Time)
|
||||
OnPacketAcked(number protocol.PacketNumber, ackedBytes protocol.ByteCount, priorInFlight protocol.ByteCount, eventTime monotime.Time)
|
||||
OnCongestionEvent(number protocol.PacketNumber, lostBytes protocol.ByteCount, priorInFlight protocol.ByteCount)
|
||||
OnRetransmissionTimeout(packetsRetransmitted bool)
|
||||
SetMaxDatagramSize(protocol.ByteCount)
|
||||
|
||||
+7
-6
@@ -3,6 +3,7 @@ package congestion
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
)
|
||||
|
||||
@@ -12,7 +13,7 @@ const maxBurstSizePackets = 10
|
||||
type pacer struct {
|
||||
budgetAtLastSent protocol.ByteCount
|
||||
maxDatagramSize protocol.ByteCount
|
||||
lastSentTime time.Time
|
||||
lastSentTime monotime.Time
|
||||
adjustedBandwidth func() uint64 // in bytes/s
|
||||
}
|
||||
|
||||
@@ -33,7 +34,7 @@ func newPacer(getBandwidth func() Bandwidth) *pacer {
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *pacer) SentPacket(sendTime time.Time, size protocol.ByteCount) {
|
||||
func (p *pacer) SentPacket(sendTime monotime.Time, size protocol.ByteCount) {
|
||||
budget := p.Budget(sendTime)
|
||||
if size >= budget {
|
||||
p.budgetAtLastSent = 0
|
||||
@@ -43,7 +44,7 @@ func (p *pacer) SentPacket(sendTime time.Time, size protocol.ByteCount) {
|
||||
p.lastSentTime = sendTime
|
||||
}
|
||||
|
||||
func (p *pacer) Budget(now time.Time) protocol.ByteCount {
|
||||
func (p *pacer) Budget(now monotime.Time) protocol.ByteCount {
|
||||
if p.lastSentTime.IsZero() {
|
||||
return p.maxBurstSize()
|
||||
}
|
||||
@@ -62,10 +63,10 @@ func (p *pacer) maxBurstSize() protocol.ByteCount {
|
||||
}
|
||||
|
||||
// TimeUntilSend returns when the next packet should be sent.
|
||||
// It returns the zero value of time.Time if a packet can be sent immediately.
|
||||
func (p *pacer) TimeUntilSend() time.Time {
|
||||
// It returns zero if a packet can be sent immediately.
|
||||
func (p *pacer) TimeUntilSend() monotime.Time {
|
||||
if p.budgetAtLastSent >= p.maxDatagramSize {
|
||||
return time.Time{}
|
||||
return 0
|
||||
}
|
||||
diff := 1e9 * uint64(p.maxDatagramSize-p.budgetAtLastSent)
|
||||
bw := p.adjustedBandwidth()
|
||||
|
||||
+5
-4
@@ -4,6 +4,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
)
|
||||
@@ -25,7 +26,7 @@ type baseFlowController struct {
|
||||
|
||||
allowWindowIncrease func(size protocol.ByteCount) bool
|
||||
|
||||
epochStartTime time.Time
|
||||
epochStartTime monotime.Time
|
||||
epochStartOffset protocol.ByteCount
|
||||
rttStats *utils.RTTStats
|
||||
|
||||
@@ -77,7 +78,7 @@ func (c *baseFlowController) hasWindowUpdate() bool {
|
||||
|
||||
// getWindowUpdate updates the receive window, if necessary
|
||||
// it returns the new offset
|
||||
func (c *baseFlowController) getWindowUpdate(now time.Time) protocol.ByteCount {
|
||||
func (c *baseFlowController) getWindowUpdate(now monotime.Time) protocol.ByteCount {
|
||||
if !c.hasWindowUpdate() {
|
||||
return 0
|
||||
}
|
||||
@@ -89,7 +90,7 @@ func (c *baseFlowController) getWindowUpdate(now time.Time) protocol.ByteCount {
|
||||
|
||||
// maybeAdjustWindowSize increases the receiveWindowSize if we're sending updates too often.
|
||||
// For details about auto-tuning, see https://docs.google.com/document/d/1SExkMmGiz8VYzV3s9E35JQlJ73vhzCekKkDi85F1qCE/edit?usp=sharing.
|
||||
func (c *baseFlowController) maybeAdjustWindowSize(now time.Time) {
|
||||
func (c *baseFlowController) maybeAdjustWindowSize(now monotime.Time) {
|
||||
bytesReadInEpoch := c.bytesRead - c.epochStartOffset
|
||||
// don't do anything if less than half the window has been consumed
|
||||
if bytesReadInEpoch <= c.receiveWindowSize/2 {
|
||||
@@ -111,7 +112,7 @@ func (c *baseFlowController) maybeAdjustWindowSize(now time.Time) {
|
||||
c.startNewAutoTuningEpoch(now)
|
||||
}
|
||||
|
||||
func (c *baseFlowController) startNewAutoTuningEpoch(now time.Time) {
|
||||
func (c *baseFlowController) startNewAutoTuningEpoch(now monotime.Time) {
|
||||
c.epochStartTime = now
|
||||
c.epochStartOffset = c.bytesRead
|
||||
}
|
||||
|
||||
Generated
Vendored
+4
-4
@@ -3,8 +3,8 @@ package flowcontrol
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/qerr"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
@@ -38,7 +38,7 @@ func NewConnectionFlowController(
|
||||
}
|
||||
|
||||
// IncrementHighestReceived adds an increment to the highestReceived value
|
||||
func (c *connectionFlowController) IncrementHighestReceived(increment protocol.ByteCount, now time.Time) error {
|
||||
func (c *connectionFlowController) IncrementHighestReceived(increment protocol.ByteCount, now monotime.Time) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
@@ -65,7 +65,7 @@ func (c *connectionFlowController) AddBytesRead(n protocol.ByteCount) (hasWindow
|
||||
return c.hasWindowUpdate()
|
||||
}
|
||||
|
||||
func (c *connectionFlowController) GetWindowUpdate(now time.Time) protocol.ByteCount {
|
||||
func (c *connectionFlowController) GetWindowUpdate(now monotime.Time) protocol.ByteCount {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
@@ -79,7 +79,7 @@ func (c *connectionFlowController) GetWindowUpdate(now time.Time) protocol.ByteC
|
||||
|
||||
// EnsureMinimumWindowSize sets a minimum window size
|
||||
// it should make sure that the connection-level window is increased when a stream-level window grows
|
||||
func (c *connectionFlowController) EnsureMinimumWindowSize(inc protocol.ByteCount, now time.Time) {
|
||||
func (c *connectionFlowController) EnsureMinimumWindowSize(inc protocol.ByteCount, now monotime.Time) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
|
||||
+5
-6
@@ -1,8 +1,7 @@
|
||||
package flowcontrol
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
)
|
||||
|
||||
@@ -12,7 +11,7 @@ type flowController interface {
|
||||
UpdateSendWindow(protocol.ByteCount) (updated bool)
|
||||
AddBytesSent(protocol.ByteCount)
|
||||
// for receiving
|
||||
GetWindowUpdate(time.Time) protocol.ByteCount // returns 0 if no update is necessary
|
||||
GetWindowUpdate(monotime.Time) protocol.ByteCount // returns 0 if no update is necessary
|
||||
}
|
||||
|
||||
// A StreamFlowController is a flow controller for a QUIC stream.
|
||||
@@ -22,7 +21,7 @@ type StreamFlowController interface {
|
||||
// UpdateHighestReceived is called when a new highest offset is received
|
||||
// final has to be to true if this is the final offset of the stream,
|
||||
// as contained in a STREAM frame with FIN bit, and the RESET_STREAM frame
|
||||
UpdateHighestReceived(offset protocol.ByteCount, final bool, now time.Time) error
|
||||
UpdateHighestReceived(offset protocol.ByteCount, final bool, now monotime.Time) error
|
||||
// Abandon is called when reading from the stream is aborted early,
|
||||
// and there won't be any further calls to AddBytesRead.
|
||||
Abandon()
|
||||
@@ -41,7 +40,7 @@ type connectionFlowControllerI interface {
|
||||
ConnectionFlowController
|
||||
// The following two methods are not supposed to be called from outside this packet, but are needed internally
|
||||
// for sending
|
||||
EnsureMinimumWindowSize(protocol.ByteCount, time.Time)
|
||||
EnsureMinimumWindowSize(protocol.ByteCount, monotime.Time)
|
||||
// for receiving
|
||||
IncrementHighestReceived(protocol.ByteCount, time.Time) error
|
||||
IncrementHighestReceived(protocol.ByteCount, monotime.Time) error
|
||||
}
|
||||
|
||||
+3
-3
@@ -2,8 +2,8 @@ package flowcontrol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/qerr"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
@@ -46,7 +46,7 @@ func NewStreamFlowController(
|
||||
}
|
||||
|
||||
// UpdateHighestReceived updates the highestReceived value, if the offset is higher.
|
||||
func (c *streamFlowController) UpdateHighestReceived(offset protocol.ByteCount, final bool, now time.Time) error {
|
||||
func (c *streamFlowController) UpdateHighestReceived(offset protocol.ByteCount, final bool, now monotime.Time) error {
|
||||
// If the final offset for this stream is already known, check for consistency.
|
||||
if c.receivedFinalOffset {
|
||||
// If we receive another final offset, check that it's the same.
|
||||
@@ -135,7 +135,7 @@ func (c *streamFlowController) shouldQueueWindowUpdate() bool {
|
||||
return !c.receivedFinalOffset && c.hasWindowUpdate()
|
||||
}
|
||||
|
||||
func (c *streamFlowController) GetWindowUpdate(now time.Time) protocol.ByteCount {
|
||||
func (c *streamFlowController) GetWindowUpdate(now monotime.Time) protocol.ByteCount {
|
||||
// If we already received the final offset for this stream, the peer won't need any additional flow control credit.
|
||||
if c.receivedFinalOffset {
|
||||
return 0
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
)
|
||||
@@ -38,7 +38,7 @@ type LongHeaderOpener interface {
|
||||
type ShortHeaderOpener interface {
|
||||
headerDecryptor
|
||||
DecodePacketNumber(wirePN protocol.PacketNumber, wirePNLen protocol.PacketNumberLen) protocol.PacketNumber
|
||||
Open(dst, src []byte, rcvTime time.Time, pn protocol.PacketNumber, kp protocol.KeyPhaseBit, associatedData []byte) ([]byte, error)
|
||||
Open(dst, src []byte, rcvTime monotime.Time, pn protocol.PacketNumber, kp protocol.KeyPhaseBit, associatedData []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
// LongHeaderSealer seals a long header packet
|
||||
|
||||
+7
-7
@@ -7,8 +7,8 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/qerr"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
@@ -42,7 +42,7 @@ type updatableAEAD struct {
|
||||
invalidPacketCount uint64
|
||||
|
||||
// Time when the keys should be dropped. Keys are dropped on the next call to Open().
|
||||
prevRcvAEADExpiry time.Time
|
||||
prevRcvAEADExpiry monotime.Time
|
||||
prevRcvAEAD cipher.AEAD
|
||||
|
||||
firstRcvdWithCurrentKey protocol.PacketNumber
|
||||
@@ -97,7 +97,7 @@ func (a *updatableAEAD) rollKeys() {
|
||||
if a.tracer != nil && a.tracer.DroppedKey != nil {
|
||||
a.tracer.DroppedKey(a.keyPhase - 1)
|
||||
}
|
||||
a.prevRcvAEADExpiry = time.Time{}
|
||||
a.prevRcvAEADExpiry = 0
|
||||
}
|
||||
|
||||
a.keyPhase++
|
||||
@@ -115,7 +115,7 @@ func (a *updatableAEAD) rollKeys() {
|
||||
a.nextSendAEAD = createAEAD(a.suite, a.nextSendTrafficSecret, a.version)
|
||||
}
|
||||
|
||||
func (a *updatableAEAD) startKeyDropTimer(now time.Time) {
|
||||
func (a *updatableAEAD) startKeyDropTimer(now monotime.Time) {
|
||||
d := 3 * a.rttStats.PTO(true)
|
||||
a.logger.Debugf("Starting key drop timer to drop key phase %d (in %s)", a.keyPhase-1, d)
|
||||
a.prevRcvAEADExpiry = now.Add(d)
|
||||
@@ -171,7 +171,7 @@ func (a *updatableAEAD) DecodePacketNumber(wirePN protocol.PacketNumber, wirePNL
|
||||
return protocol.DecodePacketNumber(wirePNLen, a.highestRcvdPN, wirePN)
|
||||
}
|
||||
|
||||
func (a *updatableAEAD) Open(dst, src []byte, rcvTime time.Time, pn protocol.PacketNumber, kp protocol.KeyPhaseBit, ad []byte) ([]byte, error) {
|
||||
func (a *updatableAEAD) Open(dst, src []byte, rcvTime monotime.Time, pn protocol.PacketNumber, kp protocol.KeyPhaseBit, ad []byte) ([]byte, error) {
|
||||
dec, err := a.open(dst, src, rcvTime, pn, kp, ad)
|
||||
if err == ErrDecryptionFailed {
|
||||
a.invalidPacketCount++
|
||||
@@ -185,11 +185,11 @@ func (a *updatableAEAD) Open(dst, src []byte, rcvTime time.Time, pn protocol.Pac
|
||||
return dec, err
|
||||
}
|
||||
|
||||
func (a *updatableAEAD) open(dst, src []byte, rcvTime time.Time, pn protocol.PacketNumber, kp protocol.KeyPhaseBit, ad []byte) ([]byte, error) {
|
||||
func (a *updatableAEAD) open(dst, src []byte, rcvTime monotime.Time, pn protocol.PacketNumber, kp protocol.KeyPhaseBit, ad []byte) ([]byte, error) {
|
||||
if a.prevRcvAEAD != nil && !a.prevRcvAEADExpiry.IsZero() && rcvTime.After(a.prevRcvAEADExpiry) {
|
||||
a.prevRcvAEAD = nil
|
||||
a.logger.Debugf("Dropping key phase %d", a.keyPhase-1)
|
||||
a.prevRcvAEADExpiry = time.Time{}
|
||||
a.prevRcvAEADExpiry = 0
|
||||
if a.tracer != nil && a.tracer.DroppedKey != nil {
|
||||
a.tracer.DroppedKey(a.keyPhase - 1)
|
||||
}
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// Package monotime provides a monotonic time representation that is useful for
|
||||
// measuring elapsed time.
|
||||
// It is designed as a memory optimized drop-in replacement for time.Time, with
|
||||
// a monotime.Time consuming just 8 bytes instead of 24 bytes.
|
||||
package monotime
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// The absolute value doesn't matter, but it should be in the past,
|
||||
// so that every timestamp obtained with Now() is non-zero,
|
||||
// even on systems with low timer resolutions (e.g. Windows).
|
||||
var start = time.Now().Add(-time.Hour)
|
||||
|
||||
// A Time represents an instant in monotonic time.
|
||||
// Times can be compared using the comparison operators, but the specific
|
||||
// value is implementation-dependent and should not be relied upon.
|
||||
// The zero value of Time doesn't have any specific meaning.
|
||||
type Time int64
|
||||
|
||||
// Now returns the current monotonic time.
|
||||
func Now() Time {
|
||||
return Time(time.Since(start).Nanoseconds())
|
||||
}
|
||||
|
||||
// Sub returns the duration t-t2. If the result exceeds the maximum (or minimum)
|
||||
// value that can be stored in a Duration, the maximum (or minimum) duration
|
||||
// will be returned.
|
||||
// To compute t-d for a duration d, use t.Add(-d).
|
||||
func (t Time) Sub(t2 Time) time.Duration {
|
||||
return time.Duration(t - t2)
|
||||
}
|
||||
|
||||
// Add returns the time t+d.
|
||||
func (t Time) Add(d time.Duration) Time {
|
||||
return Time(int64(t) + d.Nanoseconds())
|
||||
}
|
||||
|
||||
// After reports whether the time instant t is after t2.
|
||||
func (t Time) After(t2 Time) bool {
|
||||
return t > t2
|
||||
}
|
||||
|
||||
// Before reports whether the time instant t is before t2.
|
||||
func (t Time) Before(t2 Time) bool {
|
||||
return t < t2
|
||||
}
|
||||
|
||||
// IsZero reports whether t represents the zero time instant.
|
||||
func (t Time) IsZero() bool {
|
||||
return t == 0
|
||||
}
|
||||
|
||||
// Equal reports whether t and t2 represent the same time instant.
|
||||
func (t Time) Equal(t2 Time) bool {
|
||||
return t == t2
|
||||
}
|
||||
|
||||
// ToTime converts the monotonic time to a time.Time value.
|
||||
// The returned time.Time will have the same instant as the monotonic time,
|
||||
// but may be subject to clock adjustments.
|
||||
func (t Time) ToTime() time.Time {
|
||||
if t.IsZero() {
|
||||
return time.Time{}
|
||||
}
|
||||
return start.Add(time.Duration(t))
|
||||
}
|
||||
|
||||
// Since returns the time elapsed since t. It is shorthand for Now().Sub(t).
|
||||
func Since(t Time) time.Duration {
|
||||
return Now().Sub(t)
|
||||
}
|
||||
|
||||
// Until returns the duration until t.
|
||||
// It is shorthand for t.Sub(Now()).
|
||||
// If t is in the past, the returned duration will be negative.
|
||||
func Until(t Time) time.Duration {
|
||||
return time.Duration(t - Now())
|
||||
}
|
||||
|
||||
// FromTime converts a time.Time to a monotonic Time.
|
||||
// The conversion is relative to the package's start time and may lose
|
||||
// precision if the time.Time is far from the start time.
|
||||
func FromTime(t time.Time) Time {
|
||||
if t.IsZero() {
|
||||
return 0
|
||||
}
|
||||
return Time(t.Sub(start).Nanoseconds())
|
||||
}
|
||||
+3
-3
@@ -2,8 +2,8 @@ package protocol
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ func (c ArbitraryLenConnectionID) String() string {
|
||||
if c.Len() == 0 {
|
||||
return "(empty)"
|
||||
}
|
||||
return fmt.Sprintf("%x", c.Bytes())
|
||||
return hex.EncodeToString(c.Bytes())
|
||||
}
|
||||
|
||||
const maxConnectionIDLen = 20
|
||||
@@ -100,7 +100,7 @@ func (c ConnectionID) String() string {
|
||||
if c.Len() == 0 {
|
||||
return "(empty)"
|
||||
}
|
||||
return fmt.Sprintf("%x", c.Bytes())
|
||||
return hex.EncodeToString(c.Bytes())
|
||||
}
|
||||
|
||||
type DefaultConnectionIDGenerator struct {
|
||||
|
||||
+3
@@ -2,6 +2,7 @@ package protocol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -95,6 +96,8 @@ func (e ECN) String() string {
|
||||
// A ByteCount in QUIC
|
||||
type ByteCount int64
|
||||
|
||||
type AtomicByteCount atomic.Int64
|
||||
|
||||
// MaxByteCount is the maximum value of a ByteCount
|
||||
const MaxByteCount = ByteCount(1<<62 - 1)
|
||||
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package utils
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
// ConnectionStats stores stats for the connection. See the public
|
||||
// ConnectionStats struct in connection.go for more information
|
||||
type ConnectionStats struct {
|
||||
BytesSent atomic.Uint64
|
||||
PacketsSent atomic.Uint64
|
||||
BytesReceived atomic.Uint64
|
||||
PacketsReceived atomic.Uint64
|
||||
BytesLost atomic.Uint64
|
||||
PacketsLost atomic.Uint64
|
||||
}
|
||||
+7
-7
@@ -31,9 +31,9 @@ type Logger interface {
|
||||
WithPrefix(prefix string) Logger
|
||||
Debug() bool
|
||||
|
||||
Errorf(format string, args ...interface{})
|
||||
Infof(format string, args ...interface{})
|
||||
Debugf(format string, args ...interface{})
|
||||
Errorf(format string, args ...any)
|
||||
Infof(format string, args ...any)
|
||||
Debugf(format string, args ...any)
|
||||
}
|
||||
|
||||
// DefaultLogger is used by quic-go for logging.
|
||||
@@ -61,27 +61,27 @@ func (l *defaultLogger) SetLogTimeFormat(format string) {
|
||||
}
|
||||
|
||||
// Debugf logs something
|
||||
func (l *defaultLogger) Debugf(format string, args ...interface{}) {
|
||||
func (l *defaultLogger) Debugf(format string, args ...any) {
|
||||
if l.logLevel == LogLevelDebug {
|
||||
l.logMessage(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Infof logs something
|
||||
func (l *defaultLogger) Infof(format string, args ...interface{}) {
|
||||
func (l *defaultLogger) Infof(format string, args ...any) {
|
||||
if l.logLevel >= LogLevelInfo {
|
||||
l.logMessage(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf logs something
|
||||
func (l *defaultLogger) Errorf(format string, args ...interface{}) {
|
||||
func (l *defaultLogger) Errorf(format string, args ...any) {
|
||||
if l.logLevel >= LogLevelError {
|
||||
l.logMessage(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *defaultLogger) logMessage(format string, args ...interface{}) {
|
||||
func (l *defaultLogger) logMessage(format string, args ...any) {
|
||||
var pre string
|
||||
|
||||
if len(l.timeFormat) > 0 {
|
||||
|
||||
+52
-25
@@ -1,6 +1,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
@@ -19,31 +20,41 @@ const (
|
||||
type RTTStats struct {
|
||||
hasMeasurement bool
|
||||
|
||||
minRTT time.Duration
|
||||
latestRTT time.Duration
|
||||
smoothedRTT time.Duration
|
||||
meanDeviation time.Duration
|
||||
minRTT atomic.Int64 // nanoseconds
|
||||
latestRTT atomic.Int64 // nanoseconds
|
||||
smoothedRTT atomic.Int64 // nanoseconds
|
||||
meanDeviation atomic.Int64 // nanoseconds
|
||||
|
||||
maxAckDelay time.Duration
|
||||
maxAckDelay atomic.Int64 // nanoseconds
|
||||
}
|
||||
|
||||
// MinRTT Returns the minRTT for the entire connection.
|
||||
// May return Zero if no valid updates have occurred.
|
||||
func (r *RTTStats) MinRTT() time.Duration { return r.minRTT }
|
||||
func (r *RTTStats) MinRTT() time.Duration {
|
||||
return time.Duration(r.minRTT.Load())
|
||||
}
|
||||
|
||||
// LatestRTT returns the most recent rtt measurement.
|
||||
// May return Zero if no valid updates have occurred.
|
||||
func (r *RTTStats) LatestRTT() time.Duration { return r.latestRTT }
|
||||
func (r *RTTStats) LatestRTT() time.Duration {
|
||||
return time.Duration(r.latestRTT.Load())
|
||||
}
|
||||
|
||||
// SmoothedRTT returns the smoothed RTT for the connection.
|
||||
// May return Zero if no valid updates have occurred.
|
||||
func (r *RTTStats) SmoothedRTT() time.Duration { return r.smoothedRTT }
|
||||
func (r *RTTStats) SmoothedRTT() time.Duration {
|
||||
return time.Duration(r.smoothedRTT.Load())
|
||||
}
|
||||
|
||||
// MeanDeviation gets the mean deviation
|
||||
func (r *RTTStats) MeanDeviation() time.Duration { return r.meanDeviation }
|
||||
func (r *RTTStats) MeanDeviation() time.Duration {
|
||||
return time.Duration(r.meanDeviation.Load())
|
||||
}
|
||||
|
||||
// MaxAckDelay gets the max_ack_delay advertised by the peer
|
||||
func (r *RTTStats) MaxAckDelay() time.Duration { return r.maxAckDelay }
|
||||
func (r *RTTStats) MaxAckDelay() time.Duration {
|
||||
return time.Duration(r.maxAckDelay.Load())
|
||||
}
|
||||
|
||||
// PTO gets the probe timeout duration.
|
||||
func (r *RTTStats) PTO(includeMaxAckDelay bool) time.Duration {
|
||||
@@ -67,32 +78,37 @@ func (r *RTTStats) UpdateRTT(sendDelta, ackDelay time.Duration) {
|
||||
// ackDelay but the raw observed sendDelta, since poor clock granularity at
|
||||
// the client may cause a high ackDelay to result in underestimation of the
|
||||
// r.minRTT.
|
||||
if r.minRTT == 0 || r.minRTT > sendDelta {
|
||||
r.minRTT = sendDelta
|
||||
minRTT := time.Duration(r.minRTT.Load())
|
||||
if minRTT == 0 || minRTT > sendDelta {
|
||||
minRTT = sendDelta
|
||||
r.minRTT.Store(int64(sendDelta))
|
||||
}
|
||||
|
||||
// Correct for ackDelay if information received from the peer results in a
|
||||
// an RTT sample at least as large as minRTT. Otherwise, only use the
|
||||
// sendDelta.
|
||||
sample := sendDelta
|
||||
if sample-r.minRTT >= ackDelay {
|
||||
if sample-minRTT >= ackDelay {
|
||||
sample -= ackDelay
|
||||
}
|
||||
r.latestRTT = sample
|
||||
r.latestRTT.Store(int64(sample))
|
||||
// First time call.
|
||||
if !r.hasMeasurement {
|
||||
r.hasMeasurement = true
|
||||
r.smoothedRTT = sample
|
||||
r.meanDeviation = sample / 2
|
||||
r.smoothedRTT.Store(int64(sample))
|
||||
r.meanDeviation.Store(int64(sample / 2))
|
||||
} else {
|
||||
r.meanDeviation = time.Duration(oneMinusBeta*float32(r.meanDeviation/time.Microsecond)+rttBeta*float32((r.smoothedRTT-sample).Abs()/time.Microsecond)) * time.Microsecond
|
||||
r.smoothedRTT = time.Duration((float32(r.smoothedRTT/time.Microsecond)*oneMinusAlpha)+(float32(sample/time.Microsecond)*rttAlpha)) * time.Microsecond
|
||||
smoothedRTT := r.SmoothedRTT()
|
||||
meanDev := time.Duration(oneMinusBeta*float32(r.MeanDeviation()/time.Microsecond)+rttBeta*float32((smoothedRTT-sample).Abs()/time.Microsecond)) * time.Microsecond
|
||||
newSmoothedRTT := time.Duration((float32(smoothedRTT/time.Microsecond)*oneMinusAlpha)+(float32(sample/time.Microsecond)*rttAlpha)) * time.Microsecond
|
||||
r.meanDeviation.Store(int64(meanDev))
|
||||
r.smoothedRTT.Store(int64(newSmoothedRTT))
|
||||
}
|
||||
}
|
||||
|
||||
// SetMaxAckDelay sets the max_ack_delay
|
||||
func (r *RTTStats) SetMaxAckDelay(mad time.Duration) {
|
||||
r.maxAckDelay = mad
|
||||
r.maxAckDelay.Store(int64(mad))
|
||||
}
|
||||
|
||||
// SetInitialRTT sets the initial RTT.
|
||||
@@ -105,15 +121,26 @@ func (r *RTTStats) SetInitialRTT(t time.Duration) {
|
||||
if r.hasMeasurement {
|
||||
return
|
||||
}
|
||||
r.smoothedRTT = t
|
||||
r.latestRTT = t
|
||||
r.smoothedRTT.Store(int64(t))
|
||||
r.latestRTT.Store(int64(t))
|
||||
}
|
||||
|
||||
func (r *RTTStats) ResetForPathMigration() {
|
||||
r.hasMeasurement = false
|
||||
r.minRTT = 0
|
||||
r.latestRTT = 0
|
||||
r.smoothedRTT = 0
|
||||
r.meanDeviation = 0
|
||||
r.minRTT.Store(0)
|
||||
r.latestRTT.Store(0)
|
||||
r.smoothedRTT.Store(0)
|
||||
r.meanDeviation.Store(0)
|
||||
// max_ack_delay remains valid
|
||||
}
|
||||
|
||||
func (r *RTTStats) Clone() *RTTStats {
|
||||
out := &RTTStats{}
|
||||
out.hasMeasurement = r.hasMeasurement
|
||||
out.minRTT.Store(r.minRTT.Load())
|
||||
out.latestRTT.Store(r.latestRTT.Load())
|
||||
out.smoothedRTT.Store(r.smoothedRTT.Load())
|
||||
out.meanDeviation.Store(r.meanDeviation.Load())
|
||||
out.maxAckDelay.Store(r.maxAckDelay.Load())
|
||||
return out
|
||||
}
|
||||
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Timer wrapper that behaves correctly when resetting
|
||||
type Timer struct {
|
||||
t *time.Timer
|
||||
read bool
|
||||
deadline time.Time
|
||||
}
|
||||
|
||||
// NewTimer creates a new timer that is not set
|
||||
func NewTimer() *Timer {
|
||||
return &Timer{t: time.NewTimer(time.Duration(math.MaxInt64))}
|
||||
}
|
||||
|
||||
// Chan returns the channel of the wrapped timer
|
||||
func (t *Timer) Chan() <-chan time.Time {
|
||||
return t.t.C
|
||||
}
|
||||
|
||||
// Reset the timer, no matter whether the value was read or not
|
||||
func (t *Timer) Reset(deadline time.Time) {
|
||||
if deadline.Equal(t.deadline) && !t.read {
|
||||
// No need to reset the timer
|
||||
return
|
||||
}
|
||||
|
||||
// We need to drain the timer if the value from its channel was not read yet.
|
||||
// See https://groups.google.com/forum/#!topic/golang-dev/c9UUfASVPoU
|
||||
if !t.t.Stop() && !t.read {
|
||||
<-t.t.C
|
||||
}
|
||||
if !deadline.IsZero() {
|
||||
t.t.Reset(time.Until(deadline))
|
||||
}
|
||||
|
||||
t.read = false
|
||||
t.deadline = deadline
|
||||
}
|
||||
|
||||
// SetRead should be called after the value from the chan was read
|
||||
func (t *Timer) SetRead() {
|
||||
t.read = true
|
||||
}
|
||||
|
||||
func (t *Timer) Deadline() time.Time {
|
||||
return t.deadline
|
||||
}
|
||||
|
||||
// Stop stops the timer
|
||||
func (t *Timer) Stop() {
|
||||
t.t.Stop()
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package wire
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/quicvarint"
|
||||
)
|
||||
|
||||
type AckFrequencyFrame struct {
|
||||
SequenceNumber uint64
|
||||
AckElicitingThreshold uint64
|
||||
RequestMaxAckDelay time.Duration
|
||||
ReorderingThreshold protocol.PacketNumber
|
||||
}
|
||||
|
||||
func parseAckFrequencyFrame(b []byte, _ protocol.Version) (*AckFrequencyFrame, int, error) {
|
||||
startLen := len(b)
|
||||
seq, l, err := quicvarint.Parse(b)
|
||||
if err != nil {
|
||||
return nil, 0, replaceUnexpectedEOF(err)
|
||||
}
|
||||
b = b[l:]
|
||||
aeth, l, err := quicvarint.Parse(b)
|
||||
if err != nil {
|
||||
return nil, 0, replaceUnexpectedEOF(err)
|
||||
}
|
||||
b = b[l:]
|
||||
mad, l, err := quicvarint.Parse(b)
|
||||
if err != nil {
|
||||
return nil, 0, replaceUnexpectedEOF(err)
|
||||
}
|
||||
// prevents overflows if the peer sends a very large value
|
||||
maxAckDelay := time.Duration(mad) * time.Microsecond
|
||||
if maxAckDelay < 0 {
|
||||
maxAckDelay = math.MaxInt64
|
||||
}
|
||||
b = b[l:]
|
||||
rth, l, err := quicvarint.Parse(b)
|
||||
if err != nil {
|
||||
return nil, 0, replaceUnexpectedEOF(err)
|
||||
}
|
||||
b = b[l:]
|
||||
|
||||
return &AckFrequencyFrame{
|
||||
SequenceNumber: seq,
|
||||
AckElicitingThreshold: aeth,
|
||||
RequestMaxAckDelay: maxAckDelay,
|
||||
ReorderingThreshold: protocol.PacketNumber(rth),
|
||||
}, startLen - len(b), nil
|
||||
}
|
||||
|
||||
func (f *AckFrequencyFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
|
||||
b = quicvarint.Append(b, uint64(FrameTypeAckFrequency))
|
||||
b = quicvarint.Append(b, f.SequenceNumber)
|
||||
b = quicvarint.Append(b, f.AckElicitingThreshold)
|
||||
b = quicvarint.Append(b, uint64(f.RequestMaxAckDelay/time.Microsecond))
|
||||
return quicvarint.Append(b, uint64(f.ReorderingThreshold)), nil
|
||||
}
|
||||
|
||||
func (f *AckFrequencyFrame) Length(_ protocol.Version) protocol.ByteCount {
|
||||
return protocol.ByteCount(2 + quicvarint.Len(f.SequenceNumber) + quicvarint.Len(f.AckElicitingThreshold) +
|
||||
quicvarint.Len(uint64(f.RequestMaxAckDelay/time.Microsecond)) + quicvarint.Len(uint64(f.ReorderingThreshold)))
|
||||
}
|
||||
+9
-2
@@ -17,6 +17,7 @@ type FrameParser struct {
|
||||
ackDelayExponent uint8
|
||||
supportsDatagrams bool
|
||||
supportsResetStreamAt bool
|
||||
supportsAckFrequency bool
|
||||
|
||||
// To avoid allocating when parsing, keep a single ACK frame struct.
|
||||
// It is used over and over again.
|
||||
@@ -24,10 +25,11 @@ type FrameParser struct {
|
||||
}
|
||||
|
||||
// NewFrameParser creates a new frame parser.
|
||||
func NewFrameParser(supportsDatagrams, supportsResetStreamAt bool) *FrameParser {
|
||||
func NewFrameParser(supportsDatagrams, supportsResetStreamAt, supportsAckFrequency bool) *FrameParser {
|
||||
return &FrameParser{
|
||||
supportsDatagrams: supportsDatagrams,
|
||||
supportsResetStreamAt: supportsResetStreamAt,
|
||||
supportsAckFrequency: supportsAckFrequency,
|
||||
ackFrame: &AckFrame{},
|
||||
}
|
||||
}
|
||||
@@ -52,7 +54,8 @@ func (p *FrameParser) ParseType(b []byte, encLevel protocol.EncryptionLevel) (Fr
|
||||
ft := FrameType(typ)
|
||||
valid := ft.isValidRFC9000() ||
|
||||
(p.supportsDatagrams && ft.IsDatagramFrameType()) ||
|
||||
(p.supportsResetStreamAt && ft == FrameTypeResetStreamAt)
|
||||
(p.supportsResetStreamAt && ft == FrameTypeResetStreamAt) ||
|
||||
(p.supportsAckFrequency && (ft == FrameTypeAckFrequency || ft == FrameTypeImmediateAck))
|
||||
if !valid {
|
||||
return 0, parsed, &qerr.TransportError{
|
||||
ErrorCode: qerr.FrameEncodingError,
|
||||
@@ -158,6 +161,10 @@ func (p *FrameParser) ParseLessCommonFrame(frameType FrameType, data []byte, v p
|
||||
frame = &HandshakeDoneFrame{}
|
||||
case FrameTypeResetStreamAt:
|
||||
frame, l, err = parseResetStreamFrame(data, true, v)
|
||||
case FrameTypeAckFrequency:
|
||||
frame, l, err = parseAckFrequencyFrame(data, v)
|
||||
case FrameTypeImmediateAck:
|
||||
frame = &ImmediateAckFrame{}
|
||||
default:
|
||||
err = errUnknownFrameType
|
||||
}
|
||||
|
||||
+5
-1
@@ -30,7 +30,11 @@ const (
|
||||
FrameTypeConnectionClose FrameType = 0x1c
|
||||
FrameTypeApplicationClose FrameType = 0x1d
|
||||
FrameTypeHandshakeDone FrameType = 0x1e
|
||||
FrameTypeResetStreamAt FrameType = 0x24 // https://datatracker.ietf.org/doc/draft-ietf-quic-reliable-stream-reset/06/
|
||||
// https://datatracker.ietf.org/doc/draft-ietf-quic-reliable-stream-reset/07/
|
||||
FrameTypeResetStreamAt FrameType = 0x24
|
||||
// https://datatracker.ietf.org/doc/draft-ietf-quic-ack-frequency/11/
|
||||
FrameTypeAckFrequency FrameType = 0xaf
|
||||
FrameTypeImmediateAck FrameType = 0x1f
|
||||
|
||||
FrameTypeDatagramNoLength FrameType = 0x30
|
||||
FrameTypeDatagramWithLength FrameType = 0x31
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package wire
|
||||
|
||||
import (
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/quicvarint"
|
||||
)
|
||||
|
||||
// An ImmediateAckFrame is an IMMEDIATE_ACK frame
|
||||
type ImmediateAckFrame struct{}
|
||||
|
||||
func (f *ImmediateAckFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
|
||||
return quicvarint.Append(b, uint64(FrameTypeImmediateAck)), nil
|
||||
}
|
||||
|
||||
// Length of a written frame
|
||||
func (f *ImmediateAckFrame) Length(_ protocol.Version) protocol.ByteCount {
|
||||
return protocol.ByteCount(quicvarint.Len(uint64(FrameTypeImmediateAck)))
|
||||
}
|
||||
+1
-1
@@ -9,7 +9,7 @@ import (
|
||||
var pool sync.Pool
|
||||
|
||||
func init() {
|
||||
pool.New = func() interface{} {
|
||||
pool.New = func() any {
|
||||
return &StreamFrame{
|
||||
Data: make([]byte, 0, protocol.MaxPacketBufferSize),
|
||||
fromPool: true,
|
||||
|
||||
+24
-7
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"time"
|
||||
@@ -47,6 +48,8 @@ const (
|
||||
maxDatagramFrameSizeParameterID transportParameterID = 0x20
|
||||
// https://datatracker.ietf.org/doc/draft-ietf-quic-reliable-stream-reset/06/
|
||||
resetStreamAtParameterID transportParameterID = 0x17f7586d2cb571
|
||||
// https://datatracker.ietf.org/doc/draft-ietf-quic-ack-frequency/11/
|
||||
minAckDelayParameterID transportParameterID = 0xff04de1b
|
||||
)
|
||||
|
||||
// PreferredAddress is the value encoding in the preferred_address transport parameter
|
||||
@@ -86,6 +89,7 @@ type TransportParameters struct {
|
||||
|
||||
MaxDatagramFrameSize protocol.ByteCount // RFC 9221
|
||||
EnableResetStreamAt bool // https://datatracker.ietf.org/doc/draft-ietf-quic-reliable-stream-reset/06/
|
||||
MinAckDelay *time.Duration
|
||||
}
|
||||
|
||||
// Unmarshal the transport parameters
|
||||
@@ -106,12 +110,12 @@ func (p *TransportParameters) unmarshal(b []byte, sentBy protocol.Perspective, f
|
||||
var (
|
||||
readOriginalDestinationConnectionID bool
|
||||
readInitialSourceConnectionID bool
|
||||
readActiveConnectionIDLimit bool
|
||||
)
|
||||
|
||||
p.AckDelayExponent = protocol.DefaultAckDelayExponent
|
||||
p.MaxAckDelay = protocol.DefaultMaxAckDelay
|
||||
p.MaxDatagramFrameSize = protocol.InvalidByteCount
|
||||
p.ActiveConnectionIDLimit = protocol.DefaultActiveConnectionIDLimit
|
||||
|
||||
for len(b) > 0 {
|
||||
paramIDInt, l, err := quicvarint.Parse(b)
|
||||
@@ -130,9 +134,6 @@ func (p *TransportParameters) unmarshal(b []byte, sentBy protocol.Perspective, f
|
||||
}
|
||||
parameterIDs = append(parameterIDs, paramID)
|
||||
switch paramID {
|
||||
case activeConnectionIDLimitParameterID:
|
||||
readActiveConnectionIDLimit = true
|
||||
fallthrough
|
||||
case maxIdleTimeoutParameterID,
|
||||
maxUDPPayloadSizeParameterID,
|
||||
initialMaxDataParameterID,
|
||||
@@ -143,7 +144,9 @@ func (p *TransportParameters) unmarshal(b []byte, sentBy protocol.Perspective, f
|
||||
initialMaxStreamsUniParameterID,
|
||||
maxAckDelayParameterID,
|
||||
maxDatagramFrameSizeParameterID,
|
||||
ackDelayExponentParameterID:
|
||||
ackDelayExponentParameterID,
|
||||
activeConnectionIDLimitParameterID,
|
||||
minAckDelayParameterID:
|
||||
if err := p.readNumericTransportParameter(b, paramID, int(paramLen)); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -212,8 +215,9 @@ func (p *TransportParameters) unmarshal(b []byte, sentBy protocol.Perspective, f
|
||||
}
|
||||
}
|
||||
|
||||
if !readActiveConnectionIDLimit {
|
||||
p.ActiveConnectionIDLimit = protocol.DefaultActiveConnectionIDLimit
|
||||
// min_ack_delay must be less or equal to max_ack_delay
|
||||
if p.MinAckDelay != nil && *p.MinAckDelay > p.MaxAckDelay {
|
||||
return fmt.Errorf("min_ack_delay (%s) is greater than max_ack_delay (%s)", *p.MinAckDelay, p.MaxAckDelay)
|
||||
}
|
||||
if !fromSessionTicket {
|
||||
if sentBy == protocol.PerspectiveServer && !readOriginalDestinationConnectionID {
|
||||
@@ -334,6 +338,12 @@ func (p *TransportParameters) readNumericTransportParameter(b []byte, paramID tr
|
||||
p.ActiveConnectionIDLimit = val
|
||||
case maxDatagramFrameSizeParameterID:
|
||||
p.MaxDatagramFrameSize = protocol.ByteCount(val)
|
||||
case minAckDelayParameterID:
|
||||
mad := time.Duration(val) * time.Microsecond
|
||||
if mad < 0 {
|
||||
mad = math.MaxInt64
|
||||
}
|
||||
p.MinAckDelay = &mad
|
||||
default:
|
||||
return fmt.Errorf("TransportParameter BUG: transport parameter %d not found", paramID)
|
||||
}
|
||||
@@ -445,6 +455,9 @@ func (p *TransportParameters) Marshal(pers protocol.Perspective) []byte {
|
||||
b = quicvarint.Append(b, uint64(resetStreamAtParameterID))
|
||||
b = quicvarint.Append(b, 0)
|
||||
}
|
||||
if p.MinAckDelay != nil {
|
||||
b = p.marshalVarintParam(b, minAckDelayParameterID, uint64(*p.MinAckDelay/time.Microsecond))
|
||||
}
|
||||
|
||||
if pers == protocol.PerspectiveClient && len(AdditionalTransportParametersClient) > 0 {
|
||||
for k, v := range AdditionalTransportParametersClient {
|
||||
@@ -561,6 +574,10 @@ func (p *TransportParameters) String() string {
|
||||
}
|
||||
logString += ", EnableResetStreamAt: %t"
|
||||
logParams = append(logParams, p.EnableResetStreamAt)
|
||||
if p.MinAckDelay != nil {
|
||||
logString += ", MinAckDelay: %s"
|
||||
logParams = append(logParams, *p.MinAckDelay)
|
||||
}
|
||||
logString += "}"
|
||||
return fmt.Sprintf(logString, logParams...)
|
||||
}
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ type ConnectionTracer struct {
|
||||
UpdatedMetrics func(rttStats *RTTStats, cwnd, bytesInFlight ByteCount, packetsInFlight int)
|
||||
AcknowledgedPacket func(encLevel EncryptionLevel, pn PacketNumber)
|
||||
LostPacket func(encLevel EncryptionLevel, pn PacketNumber, reason PacketLossReason)
|
||||
DetectedSpuriousLoss func(encLevel EncryptionLevel, pn PacketNumber, reordering uint64, dur time.Duration)
|
||||
UpdatedMTU func(mtu ByteCount, done bool)
|
||||
UpdatedCongestionState func(state CongestionState)
|
||||
UpdatedPTOCount func(value uint32)
|
||||
|
||||
+7
@@ -134,6 +134,13 @@ func NewMultiplexedConnectionTracer(tracers ...*ConnectionTracer) *ConnectionTra
|
||||
}
|
||||
}
|
||||
},
|
||||
DetectedSpuriousLoss: func(encLevel EncryptionLevel, pn PacketNumber, reordering uint64, dur time.Duration) {
|
||||
for _, t := range tracers {
|
||||
if t.DetectedSpuriousLoss != nil {
|
||||
t.DetectedSpuriousLoss(encLevel, pn, reordering, dur)
|
||||
}
|
||||
}
|
||||
},
|
||||
UpdatedMTU: func(mtu ByteCount, done bool) {
|
||||
for _, t := range tracers {
|
||||
if t.UpdatedMTU != nil {
|
||||
|
||||
+5
-1
@@ -3,7 +3,7 @@ package logging
|
||||
import "github.com/quic-go/quic-go/internal/wire"
|
||||
|
||||
// A Frame is a QUIC frame
|
||||
type Frame interface{}
|
||||
type Frame any
|
||||
|
||||
// The AckRange is used within the AckFrame.
|
||||
// It is a range of packet numbers that is being acknowledged.
|
||||
@@ -44,6 +44,10 @@ type (
|
||||
StreamsBlockedFrame = wire.StreamsBlockedFrame
|
||||
// A StreamDataBlockedFrame is a STREAM_DATA_BLOCKED frame.
|
||||
StreamDataBlockedFrame = wire.StreamDataBlockedFrame
|
||||
// An AckFrequencyFrame is an ACK_FREQUENCY frame.
|
||||
AckFrequencyFrame = wire.AckFrequencyFrame
|
||||
// An ImmediateAckFrame is an IMMEDIATE_ACK frame.
|
||||
ImmediateAckFrame = wire.ImmediateAckFrame
|
||||
)
|
||||
|
||||
// A CryptoFrame is a CRYPTO frame.
|
||||
|
||||
+15
-15
@@ -2,46 +2,46 @@
|
||||
|
||||
package quic
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_send_conn_test.go github.com/quic-go/quic-go SendConn"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_send_conn_test.go github.com/quic-go/quic-go SendConn"
|
||||
type SendConn = sendConn
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_raw_conn_test.go github.com/quic-go/quic-go RawConn"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_raw_conn_test.go github.com/quic-go/quic-go RawConn"
|
||||
type RawConn = rawConn
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_sender_test.go github.com/quic-go/quic-go Sender"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_sender_test.go github.com/quic-go/quic-go Sender"
|
||||
type Sender = sender
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_stream_sender_test.go github.com/quic-go/quic-go StreamSender"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_stream_sender_test.go github.com/quic-go/quic-go StreamSender"
|
||||
type StreamSender = streamSender
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_stream_control_frame_getter_test.go github.com/quic-go/quic-go StreamControlFrameGetter"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_stream_control_frame_getter_test.go github.com/quic-go/quic-go StreamControlFrameGetter"
|
||||
type StreamControlFrameGetter = streamControlFrameGetter
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_stream_frame_getter_test.go github.com/quic-go/quic-go StreamFrameGetter"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_stream_frame_getter_test.go github.com/quic-go/quic-go StreamFrameGetter"
|
||||
type StreamFrameGetter = streamFrameGetter
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_frame_source_test.go github.com/quic-go/quic-go FrameSource"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_frame_source_test.go github.com/quic-go/quic-go FrameSource"
|
||||
type FrameSource = frameSource
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_ack_frame_source_test.go github.com/quic-go/quic-go AckFrameSource"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_ack_frame_source_test.go github.com/quic-go/quic-go AckFrameSource"
|
||||
type AckFrameSource = ackFrameSource
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_sealing_manager_test.go github.com/quic-go/quic-go SealingManager"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_sealing_manager_test.go github.com/quic-go/quic-go SealingManager"
|
||||
type SealingManager = sealingManager
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_unpacker_test.go github.com/quic-go/quic-go Unpacker"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_unpacker_test.go github.com/quic-go/quic-go Unpacker"
|
||||
type Unpacker = unpacker
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_packer_test.go github.com/quic-go/quic-go Packer"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_packer_test.go github.com/quic-go/quic-go Packer"
|
||||
type Packer = packer
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_mtu_discoverer_test.go github.com/quic-go/quic-go MTUDiscoverer"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_mtu_discoverer_test.go github.com/quic-go/quic-go MTUDiscoverer"
|
||||
type MTUDiscoverer = mtuDiscoverer
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_conn_runner_test.go github.com/quic-go/quic-go ConnRunner"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_conn_runner_test.go github.com/quic-go/quic-go ConnRunner"
|
||||
type ConnRunner = connRunner
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_packet_handler_test.go github.com/quic-go/quic-go PacketHandler"
|
||||
//go:generate sh -c "go tool mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_packet_handler_test.go github.com/quic-go/quic-go PacketHandler"
|
||||
type PacketHandler = packetHandler
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -package quic -self_package github.com/quic-go/quic-go -self_package github.com/quic-go/quic-go -destination mock_packetconn_test.go net PacketConn"
|
||||
//go:generate sh -c "go tool mockgen -typed -package quic -self_package github.com/quic-go/quic-go -self_package github.com/quic-go/quic-go -destination mock_packetconn_test.go net PacketConn"
|
||||
|
||||
+10
-11
@@ -1,9 +1,8 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/ackhandler"
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
@@ -13,11 +12,11 @@ import (
|
||||
type mtuDiscoverer interface {
|
||||
// Start starts the MTU discovery process.
|
||||
// It's unnecessary to call ShouldSendProbe before that.
|
||||
Start(now time.Time)
|
||||
ShouldSendProbe(now time.Time) bool
|
||||
Start(now monotime.Time)
|
||||
ShouldSendProbe(now monotime.Time) bool
|
||||
CurrentSize() protocol.ByteCount
|
||||
GetPing(now time.Time) (ping ackhandler.Frame, datagramSize protocol.ByteCount)
|
||||
Reset(now time.Time, start, max protocol.ByteCount)
|
||||
GetPing(now monotime.Time) (ping ackhandler.Frame, datagramSize protocol.ByteCount)
|
||||
Reset(now monotime.Time, start, max protocol.ByteCount)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -88,7 +87,7 @@ const (
|
||||
// MTU discovery concludes once the interval min and max has been narrowed down to maxMTUDiff.
|
||||
|
||||
type mtuFinder struct {
|
||||
lastProbeTime time.Time
|
||||
lastProbeTime monotime.Time
|
||||
|
||||
rttStats *utils.RTTStats
|
||||
|
||||
@@ -147,11 +146,11 @@ func (f *mtuFinder) max() protocol.ByteCount {
|
||||
return f.lost[len(f.lost)-1]
|
||||
}
|
||||
|
||||
func (f *mtuFinder) Start(now time.Time) {
|
||||
func (f *mtuFinder) Start(now monotime.Time) {
|
||||
f.lastProbeTime = now // makes sure the first probe packet is not sent immediately
|
||||
}
|
||||
|
||||
func (f *mtuFinder) ShouldSendProbe(now time.Time) bool {
|
||||
func (f *mtuFinder) ShouldSendProbe(now monotime.Time) bool {
|
||||
if f.lastProbeTime.IsZero() {
|
||||
return false
|
||||
}
|
||||
@@ -161,7 +160,7 @@ func (f *mtuFinder) ShouldSendProbe(now time.Time) bool {
|
||||
return !now.Before(f.lastProbeTime.Add(mtuProbeDelay * f.rttStats.SmoothedRTT()))
|
||||
}
|
||||
|
||||
func (f *mtuFinder) GetPing(now time.Time) (ackhandler.Frame, protocol.ByteCount) {
|
||||
func (f *mtuFinder) GetPing(now monotime.Time) (ackhandler.Frame, protocol.ByteCount) {
|
||||
var size protocol.ByteCount
|
||||
if f.lastProbeWasLost {
|
||||
size = (f.min + f.lost[0]) / 2
|
||||
@@ -180,7 +179,7 @@ func (f *mtuFinder) CurrentSize() protocol.ByteCount {
|
||||
return f.min
|
||||
}
|
||||
|
||||
func (f *mtuFinder) Reset(now time.Time, start, max protocol.ByteCount) {
|
||||
func (f *mtuFinder) Reset(now monotime.Time, start, max protocol.ByteCount) {
|
||||
f.generation++
|
||||
f.lastProbeTime = now
|
||||
f.lastProbeWasLost = false
|
||||
|
||||
+3
-3
@@ -3,12 +3,12 @@
|
||||
# Install Go manually, since oss-fuzz ships with an outdated Go version.
|
||||
# See https://github.com/google/oss-fuzz/pull/10643.
|
||||
export CXX="${CXX} -lresolv" # required by Go 1.20
|
||||
wget https://go.dev/dl/go1.23.0.linux-amd64.tar.gz \
|
||||
wget https://go.dev/dl/go1.25.0.linux-amd64.tar.gz \
|
||||
&& mkdir temp-go \
|
||||
&& rm -rf /root/.go/* \
|
||||
&& tar -C temp-go/ -xzf go1.23.0.linux-amd64.tar.gz \
|
||||
&& tar -C temp-go/ -xzf go1.25.0.linux-amd64.tar.gz \
|
||||
&& mv temp-go/go/* /root/.go/ \
|
||||
&& rm -rf temp-go go1.23.0.linux-amd64.tar.gz
|
||||
&& rm -rf temp-go go1.25.0.linux-amd64.tar.gz
|
||||
|
||||
(
|
||||
# fuzz qpack
|
||||
|
||||
+18
-18
@@ -6,10 +6,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/ackhandler"
|
||||
"github.com/quic-go/quic-go/internal/handshake"
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/qerr"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
@@ -18,10 +18,10 @@ import (
|
||||
var errNothingToPack = errors.New("nothing to pack")
|
||||
|
||||
type packer interface {
|
||||
PackCoalescedPacket(onlyAck bool, maxPacketSize protocol.ByteCount, now time.Time, v protocol.Version) (*coalescedPacket, error)
|
||||
PackAckOnlyPacket(maxPacketSize protocol.ByteCount, now time.Time, v protocol.Version) (shortHeaderPacket, *packetBuffer, error)
|
||||
AppendPacket(_ *packetBuffer, maxPacketSize protocol.ByteCount, now time.Time, v protocol.Version) (shortHeaderPacket, error)
|
||||
PackPTOProbePacket(_ protocol.EncryptionLevel, _ protocol.ByteCount, addPingIfEmpty bool, now time.Time, v protocol.Version) (*coalescedPacket, error)
|
||||
PackCoalescedPacket(onlyAck bool, maxPacketSize protocol.ByteCount, now monotime.Time, v protocol.Version) (*coalescedPacket, error)
|
||||
PackAckOnlyPacket(maxPacketSize protocol.ByteCount, now monotime.Time, v protocol.Version) (shortHeaderPacket, *packetBuffer, error)
|
||||
AppendPacket(_ *packetBuffer, maxPacketSize protocol.ByteCount, now monotime.Time, v protocol.Version) (shortHeaderPacket, error)
|
||||
PackPTOProbePacket(_ protocol.EncryptionLevel, _ protocol.ByteCount, addPingIfEmpty bool, now monotime.Time, v protocol.Version) (*coalescedPacket, error)
|
||||
PackConnectionClose(*qerr.TransportError, protocol.ByteCount, protocol.Version) (*coalescedPacket, error)
|
||||
PackApplicationClose(*qerr.ApplicationError, protocol.ByteCount, protocol.Version) (*coalescedPacket, error)
|
||||
PackPathProbePacket(protocol.ConnectionID, []ackhandler.Frame, protocol.Version) (shortHeaderPacket, *packetBuffer, error)
|
||||
@@ -108,11 +108,11 @@ type sealingManager interface {
|
||||
|
||||
type frameSource interface {
|
||||
HasData() bool
|
||||
Append([]ackhandler.Frame, []ackhandler.StreamFrame, protocol.ByteCount, time.Time, protocol.Version) ([]ackhandler.Frame, []ackhandler.StreamFrame, protocol.ByteCount)
|
||||
Append([]ackhandler.Frame, []ackhandler.StreamFrame, protocol.ByteCount, monotime.Time, protocol.Version) ([]ackhandler.Frame, []ackhandler.StreamFrame, protocol.ByteCount)
|
||||
}
|
||||
|
||||
type ackFrameSource interface {
|
||||
GetAckFrame(_ protocol.EncryptionLevel, now time.Time, onlyIfQueued bool) *wire.AckFrame
|
||||
GetAckFrame(_ protocol.EncryptionLevel, now monotime.Time, onlyIfQueued bool) *wire.AckFrame
|
||||
}
|
||||
|
||||
type packetPacker struct {
|
||||
@@ -330,7 +330,7 @@ func (p *packetPacker) initialPaddingLen(frames []ackhandler.Frame, currentSize,
|
||||
// PackCoalescedPacket packs a new packet.
|
||||
// It packs an Initial / Handshake if there is data to send in these packet number spaces.
|
||||
// It should only be called before the handshake is confirmed.
|
||||
func (p *packetPacker) PackCoalescedPacket(onlyAck bool, maxSize protocol.ByteCount, now time.Time, v protocol.Version) (*coalescedPacket, error) {
|
||||
func (p *packetPacker) PackCoalescedPacket(onlyAck bool, maxSize protocol.ByteCount, now monotime.Time, v protocol.Version) (*coalescedPacket, error) {
|
||||
var (
|
||||
initialHdr, handshakeHdr, zeroRTTHdr *wire.ExtendedHeader
|
||||
initialPayload, handshakePayload, zeroRTTPayload, oneRTTPayload payload
|
||||
@@ -460,7 +460,7 @@ func (p *packetPacker) PackCoalescedPacket(onlyAck bool, maxSize protocol.ByteCo
|
||||
|
||||
// PackAckOnlyPacket packs a packet containing only an ACK in the application data packet number space.
|
||||
// It should be called after the handshake is confirmed.
|
||||
func (p *packetPacker) PackAckOnlyPacket(maxSize protocol.ByteCount, now time.Time, v protocol.Version) (shortHeaderPacket, *packetBuffer, error) {
|
||||
func (p *packetPacker) PackAckOnlyPacket(maxSize protocol.ByteCount, now monotime.Time, v protocol.Version) (shortHeaderPacket, *packetBuffer, error) {
|
||||
buf := getPacketBuffer()
|
||||
packet, err := p.appendPacket(buf, true, maxSize, now, v)
|
||||
return packet, buf, err
|
||||
@@ -468,7 +468,7 @@ func (p *packetPacker) PackAckOnlyPacket(maxSize protocol.ByteCount, now time.Ti
|
||||
|
||||
// AppendPacket packs a packet in the application data packet number space.
|
||||
// It should be called after the handshake is confirmed.
|
||||
func (p *packetPacker) AppendPacket(buf *packetBuffer, maxSize protocol.ByteCount, now time.Time, v protocol.Version) (shortHeaderPacket, error) {
|
||||
func (p *packetPacker) AppendPacket(buf *packetBuffer, maxSize protocol.ByteCount, now monotime.Time, v protocol.Version) (shortHeaderPacket, error) {
|
||||
return p.appendPacket(buf, false, maxSize, now, v)
|
||||
}
|
||||
|
||||
@@ -476,7 +476,7 @@ func (p *packetPacker) appendPacket(
|
||||
buf *packetBuffer,
|
||||
onlyAck bool,
|
||||
maxPacketSize protocol.ByteCount,
|
||||
now time.Time,
|
||||
now monotime.Time,
|
||||
v protocol.Version,
|
||||
) (shortHeaderPacket, error) {
|
||||
sealer, err := p.cryptoSetup.Get1RTTSealer()
|
||||
@@ -498,7 +498,7 @@ func (p *packetPacker) appendPacket(
|
||||
func (p *packetPacker) maybeGetCryptoPacket(
|
||||
maxPacketSize protocol.ByteCount,
|
||||
encLevel protocol.EncryptionLevel,
|
||||
now time.Time,
|
||||
now monotime.Time,
|
||||
addPingIfEmpty bool,
|
||||
onlyAck, ackAllowed bool,
|
||||
v protocol.Version,
|
||||
@@ -578,7 +578,7 @@ func (p *packetPacker) maybeGetCryptoPacket(
|
||||
return hdr, pl
|
||||
}
|
||||
|
||||
func (p *packetPacker) maybeGetAppDataPacketFor0RTT(sealer sealer, maxSize protocol.ByteCount, now time.Time, v protocol.Version) (*wire.ExtendedHeader, payload) {
|
||||
func (p *packetPacker) maybeGetAppDataPacketFor0RTT(sealer sealer, maxSize protocol.ByteCount, now monotime.Time, v protocol.Version) (*wire.ExtendedHeader, payload) {
|
||||
if p.perspective != protocol.PerspectiveClient {
|
||||
return nil, payload{}
|
||||
}
|
||||
@@ -592,7 +592,7 @@ func (p *packetPacker) maybeGetShortHeaderPacket(
|
||||
sealer handshake.ShortHeaderSealer,
|
||||
hdrLen, maxPacketSize protocol.ByteCount,
|
||||
onlyAck, ackAllowed bool,
|
||||
now time.Time,
|
||||
now monotime.Time,
|
||||
v protocol.Version,
|
||||
) payload {
|
||||
maxPayloadSize := maxPacketSize - hdrLen - protocol.ByteCount(sealer.Overhead())
|
||||
@@ -602,7 +602,7 @@ func (p *packetPacker) maybeGetShortHeaderPacket(
|
||||
func (p *packetPacker) maybeGetAppDataPacket(
|
||||
maxPayloadSize protocol.ByteCount,
|
||||
onlyAck, ackAllowed bool,
|
||||
now time.Time,
|
||||
now monotime.Time,
|
||||
v protocol.Version,
|
||||
) payload {
|
||||
pl := p.composeNextPacket(maxPayloadSize, onlyAck, ackAllowed, now, v)
|
||||
@@ -630,7 +630,7 @@ func (p *packetPacker) maybeGetAppDataPacket(
|
||||
func (p *packetPacker) composeNextPacket(
|
||||
maxPayloadSize protocol.ByteCount,
|
||||
onlyAck, ackAllowed bool,
|
||||
now time.Time,
|
||||
now monotime.Time,
|
||||
v protocol.Version,
|
||||
) payload {
|
||||
if onlyAck {
|
||||
@@ -716,7 +716,7 @@ func (p *packetPacker) PackPTOProbePacket(
|
||||
encLevel protocol.EncryptionLevel,
|
||||
maxPacketSize protocol.ByteCount,
|
||||
addPingIfEmpty bool,
|
||||
now time.Time,
|
||||
now monotime.Time,
|
||||
v protocol.Version,
|
||||
) (*coalescedPacket, error) {
|
||||
if encLevel == protocol.Encryption1RTT {
|
||||
@@ -769,7 +769,7 @@ func (p *packetPacker) PackPTOProbePacket(
|
||||
return packet, nil
|
||||
}
|
||||
|
||||
func (p *packetPacker) packPTOProbePacket1RTT(maxPacketSize protocol.ByteCount, addPingIfEmpty bool, now time.Time, v protocol.Version) (*coalescedPacket, error) {
|
||||
func (p *packetPacker) packPTOProbePacket1RTT(maxPacketSize protocol.ByteCount, addPingIfEmpty bool, now monotime.Time, v protocol.Version) (*coalescedPacket, error) {
|
||||
s, err := p.cryptoSetup.Get1RTTSealer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+3
-3
@@ -2,9 +2,9 @@ package quic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/handshake"
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/qerr"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
@@ -106,7 +106,7 @@ func (u *packetUnpacker) UnpackLongHeader(hdr *wire.Header, data []byte) (*unpac
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *packetUnpacker) UnpackShortHeader(rcvTime time.Time, data []byte) (protocol.PacketNumber, protocol.PacketNumberLen, protocol.KeyPhaseBit, []byte, error) {
|
||||
func (u *packetUnpacker) UnpackShortHeader(rcvTime monotime.Time, data []byte) (protocol.PacketNumber, protocol.PacketNumberLen, protocol.KeyPhaseBit, []byte, error) {
|
||||
opener, err := u.cs.Get1RTTOpener()
|
||||
if err != nil {
|
||||
return 0, 0, 0, nil, err
|
||||
@@ -144,7 +144,7 @@ func (u *packetUnpacker) unpackLongHeaderPacket(opener handshake.LongHeaderOpene
|
||||
return extHdr, decrypted, nil
|
||||
}
|
||||
|
||||
func (u *packetUnpacker) unpackShortHeaderPacket(opener handshake.ShortHeaderOpener, rcvTime time.Time, data []byte) (protocol.PacketNumber, protocol.PacketNumberLen, protocol.KeyPhaseBit, []byte, error) {
|
||||
func (u *packetUnpacker) unpackShortHeaderPacket(opener handshake.ShortHeaderOpener, rcvTime monotime.Time, data []byte) (protocol.PacketNumber, protocol.PacketNumberLen, protocol.KeyPhaseBit, []byte, error) {
|
||||
l, pn, pnLen, kp, parseErr := u.unpackShortHeader(opener, data)
|
||||
// If the reserved bits are set incorrectly, we still need to continue unpacking.
|
||||
// This avoids a timing side-channel, which otherwise might allow an attacker
|
||||
|
||||
+3
-2
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/ackhandler"
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
@@ -30,7 +31,7 @@ const pathTimeout = 5 * time.Second
|
||||
type path struct {
|
||||
id pathID
|
||||
addr net.Addr
|
||||
lastPacketTime time.Time
|
||||
lastPacketTime monotime.Time
|
||||
pathChallenge [8]byte
|
||||
validated bool
|
||||
rcvdNonProbing bool
|
||||
@@ -64,7 +65,7 @@ func newPathManager(
|
||||
// May return nil.
|
||||
func (pm *pathManager) HandlePacket(
|
||||
remoteAddr net.Addr,
|
||||
t time.Time,
|
||||
t monotime.Time,
|
||||
pathChallenge *wire.PathChallengeFrame, // may be nil if the packet didn't contain a PATH_CHALLENGE
|
||||
isNonProbing bool,
|
||||
) (_ protocol.ConnectionID, _ []ackhandler.Frame, shouldSwitch bool) {
|
||||
|
||||
+3
@@ -128,6 +128,9 @@ type pathManagerOutgoing struct {
|
||||
pathToSwitchTo *pathOutgoing
|
||||
}
|
||||
|
||||
// newPathManagerOutgoing creates a new pathManagerOutgoing object. This
|
||||
// function must be side-effect free as it may be called multiple times for a
|
||||
// single connection.
|
||||
func newPathManagerOutgoing(
|
||||
getConnID func(pathID) (_ protocol.ConnectionID, ok bool),
|
||||
retireConnID func(pathID),
|
||||
|
||||
+6
-1
@@ -31,7 +31,12 @@ func NewReader(r io.Reader) Reader {
|
||||
|
||||
func (r *byteReader) ReadByte() (byte, error) {
|
||||
var b [1]byte
|
||||
n, err := r.Read(b[:])
|
||||
var n int
|
||||
var err error
|
||||
for n == 0 && err == nil {
|
||||
n, err = r.Read(b[:])
|
||||
}
|
||||
|
||||
if n == 1 && err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
|
||||
+14
-14
@@ -8,9 +8,9 @@ import (
|
||||
|
||||
"github.com/quic-go/quic-go/internal/ackhandler"
|
||||
"github.com/quic-go/quic-go/internal/flowcontrol"
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/qerr"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
)
|
||||
|
||||
@@ -47,7 +47,7 @@ type ReceiveStream struct {
|
||||
|
||||
readChan chan struct{}
|
||||
readOnce chan struct{} // cap: 1, to protect against concurrent use of Read
|
||||
deadline time.Time
|
||||
deadline monotime.Time
|
||||
|
||||
flowController flowcontrol.StreamFlowController
|
||||
}
|
||||
@@ -140,7 +140,7 @@ func (s *ReceiveStream) readImpl(p []byte) (hasStreamWindowUpdate bool, hasConnW
|
||||
}
|
||||
|
||||
var bytesRead int
|
||||
var deadlineTimer *utils.Timer
|
||||
var deadlineTimer *time.Timer
|
||||
for bytesRead < len(p) {
|
||||
if s.currentFrame == nil || s.readPosInFrame >= len(s.currentFrame) {
|
||||
s.dequeueNextFrame()
|
||||
@@ -161,14 +161,15 @@ func (s *ReceiveStream) readImpl(p []byte) (hasStreamWindowUpdate bool, hasConnW
|
||||
|
||||
deadline := s.deadline
|
||||
if !deadline.IsZero() {
|
||||
if !time.Now().Before(deadline) {
|
||||
if !monotime.Now().Before(deadline) {
|
||||
return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, errDeadline
|
||||
}
|
||||
if deadlineTimer == nil {
|
||||
deadlineTimer = utils.NewTimer()
|
||||
deadlineTimer = time.NewTimer(monotime.Until(deadline))
|
||||
defer deadlineTimer.Stop()
|
||||
} else {
|
||||
deadlineTimer.Reset(monotime.Until(deadline))
|
||||
}
|
||||
deadlineTimer.Reset(deadline)
|
||||
}
|
||||
|
||||
if s.currentFrame != nil || s.currentFrameIsLast {
|
||||
@@ -181,8 +182,7 @@ func (s *ReceiveStream) readImpl(p []byte) (hasStreamWindowUpdate bool, hasConnW
|
||||
} else {
|
||||
select {
|
||||
case <-s.readChan:
|
||||
case <-deadlineTimer.Chan():
|
||||
deadlineTimer.SetRead()
|
||||
case <-deadlineTimer.C:
|
||||
}
|
||||
}
|
||||
s.mutex.Lock()
|
||||
@@ -283,7 +283,7 @@ func (s *ReceiveStream) cancelReadImpl(errorCode qerr.StreamErrorCode) (queuedNe
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *ReceiveStream) handleStreamFrame(frame *wire.StreamFrame, now time.Time) error {
|
||||
func (s *ReceiveStream) handleStreamFrame(frame *wire.StreamFrame, now monotime.Time) error {
|
||||
s.mutex.Lock()
|
||||
err := s.handleStreamFrameImpl(frame, now)
|
||||
completed := s.isNewlyCompleted()
|
||||
@@ -296,7 +296,7 @@ func (s *ReceiveStream) handleStreamFrame(frame *wire.StreamFrame, now time.Time
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ReceiveStream) handleStreamFrameImpl(frame *wire.StreamFrame, now time.Time) error {
|
||||
func (s *ReceiveStream) handleStreamFrameImpl(frame *wire.StreamFrame, now monotime.Time) error {
|
||||
maxOffset := frame.Offset + frame.DataLen()
|
||||
if err := s.flowController.UpdateHighestReceived(maxOffset, frame.Fin, now); err != nil {
|
||||
return err
|
||||
@@ -314,7 +314,7 @@ func (s *ReceiveStream) handleStreamFrameImpl(frame *wire.StreamFrame, now time.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ReceiveStream) handleResetStreamFrame(frame *wire.ResetStreamFrame, now time.Time) error {
|
||||
func (s *ReceiveStream) handleResetStreamFrame(frame *wire.ResetStreamFrame, now monotime.Time) error {
|
||||
s.mutex.Lock()
|
||||
err := s.handleResetStreamFrameImpl(frame, now)
|
||||
completed := s.isNewlyCompleted()
|
||||
@@ -326,7 +326,7 @@ func (s *ReceiveStream) handleResetStreamFrame(frame *wire.ResetStreamFrame, now
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ReceiveStream) handleResetStreamFrameImpl(frame *wire.ResetStreamFrame, now time.Time) error {
|
||||
func (s *ReceiveStream) handleResetStreamFrameImpl(frame *wire.ResetStreamFrame, now monotime.Time) error {
|
||||
if s.closeForShutdownErr != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -358,7 +358,7 @@ func (s *ReceiveStream) handleResetStreamFrameImpl(frame *wire.ResetStreamFrame,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ReceiveStream) getControlFrame(now time.Time) (_ ackhandler.Frame, ok, hasMore bool) {
|
||||
func (s *ReceiveStream) getControlFrame(now monotime.Time) (_ ackhandler.Frame, ok, hasMore bool) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
@@ -386,7 +386,7 @@ func (s *ReceiveStream) getControlFrame(now time.Time) (_ ackhandler.Frame, ok,
|
||||
// A zero value for t means Read will not time out.
|
||||
func (s *ReceiveStream) SetReadDeadline(t time.Time) error {
|
||||
s.mutex.Lock()
|
||||
s.deadline = t
|
||||
s.deadline = monotime.FromTime(t)
|
||||
s.mutex.Unlock()
|
||||
s.signalRead()
|
||||
return nil
|
||||
|
||||
+30
-14
@@ -8,8 +8,8 @@ import (
|
||||
|
||||
"github.com/quic-go/quic-go/internal/ackhandler"
|
||||
"github.com/quic-go/quic-go/internal/flowcontrol"
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
)
|
||||
|
||||
@@ -52,7 +52,7 @@ type SendStream struct {
|
||||
|
||||
writeChan chan struct{}
|
||||
writeOnce chan struct{}
|
||||
deadline time.Time
|
||||
deadline monotime.Time
|
||||
|
||||
flowController flowcontrol.StreamFlowController
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func (s *SendStream) write(p []byte) (bool /* is newly completed */, int, error)
|
||||
if s.finishedWriting {
|
||||
return false, 0, fmt.Errorf("write on closed stream %d", s.streamID)
|
||||
}
|
||||
if !s.deadline.IsZero() && !time.Now().Before(s.deadline) {
|
||||
if !s.deadline.IsZero() && !monotime.Now().Before(s.deadline) {
|
||||
return false, 0, errDeadline
|
||||
}
|
||||
if len(p) == 0 {
|
||||
@@ -128,13 +128,13 @@ func (s *SendStream) write(p []byte) (bool /* is newly completed */, int, error)
|
||||
s.dataForWriting = p
|
||||
|
||||
var (
|
||||
deadlineTimer *utils.Timer
|
||||
deadlineTimer *time.Timer
|
||||
bytesWritten int
|
||||
notifiedSender bool
|
||||
)
|
||||
for {
|
||||
var copied bool
|
||||
var deadline time.Time
|
||||
var deadline monotime.Time
|
||||
// As soon as dataForWriting becomes smaller than a certain size x, we copy all the data to a STREAM frame (s.nextFrame),
|
||||
// which can then be popped the next time we assemble a packet.
|
||||
// This allows us to return Write() when all data but x bytes have been sent out.
|
||||
@@ -161,15 +161,16 @@ func (s *SendStream) write(p []byte) (bool /* is newly completed */, int, error)
|
||||
bytesWritten = len(p) - len(s.dataForWriting)
|
||||
deadline = s.deadline
|
||||
if !deadline.IsZero() {
|
||||
if !time.Now().Before(deadline) {
|
||||
if !monotime.Now().Before(deadline) {
|
||||
s.dataForWriting = nil
|
||||
return false, bytesWritten, errDeadline
|
||||
}
|
||||
if deadlineTimer == nil {
|
||||
deadlineTimer = utils.NewTimer()
|
||||
deadlineTimer = time.NewTimer(monotime.Until(deadline))
|
||||
defer deadlineTimer.Stop()
|
||||
} else {
|
||||
deadlineTimer.Reset(monotime.Until(deadline))
|
||||
}
|
||||
deadlineTimer.Reset(deadline)
|
||||
}
|
||||
if s.dataForWriting == nil || s.shutdownErr != nil || s.resetErr != nil {
|
||||
break
|
||||
@@ -190,8 +191,7 @@ func (s *SendStream) write(p []byte) (bool /* is newly completed */, int, error)
|
||||
} else {
|
||||
select {
|
||||
case <-s.writeChan:
|
||||
case <-deadlineTimer.Chan():
|
||||
deadlineTimer.SetRead()
|
||||
case <-deadlineTimer.C:
|
||||
}
|
||||
}
|
||||
s.mutex.Lock()
|
||||
@@ -454,6 +454,19 @@ func (s *SendStream) SetReliableBoundary() {
|
||||
}
|
||||
}
|
||||
|
||||
// returnFramesToPool returns all queued frames to the sync.Pool
|
||||
func (s *SendStream) returnFramesToPool() {
|
||||
for _, f := range s.retransmissionQueue {
|
||||
f.PutBack()
|
||||
}
|
||||
clear(s.retransmissionQueue)
|
||||
s.retransmissionQueue = nil
|
||||
if s.nextFrame != nil {
|
||||
s.nextFrame.PutBack()
|
||||
s.nextFrame = nil
|
||||
}
|
||||
}
|
||||
|
||||
// CancelWrite aborts sending on this stream.
|
||||
// Data already written, but not yet delivered to the peer is not guaranteed to be delivered reliably.
|
||||
// Write will unblock immediately, and future calls to Write will fail.
|
||||
@@ -485,7 +498,7 @@ func (s *SendStream) CancelWrite(errorCode StreamErrorCode) {
|
||||
reliableOffset := s.reliableOffset()
|
||||
if reliableOffset == 0 {
|
||||
s.numOutstandingFrames = 0
|
||||
s.retransmissionQueue = nil
|
||||
s.returnFramesToPool()
|
||||
}
|
||||
s.queuedResetStreamFrame = &wire.ResetStreamFrame{
|
||||
StreamID: s.streamID,
|
||||
@@ -561,7 +574,7 @@ func (s *SendStream) handleStopSendingFrame(f *wire.StopSendingFrame) {
|
||||
// if the peer stopped reading from the stream, there's no need to transmit any data reliably
|
||||
s.reliableSize = 0
|
||||
s.numOutstandingFrames = 0
|
||||
s.retransmissionQueue = nil
|
||||
s.returnFramesToPool()
|
||||
if s.resetErr == nil {
|
||||
s.resetErr = &StreamError{StreamID: s.streamID, ErrorCode: f.ErrorCode, Remote: true}
|
||||
s.ctxCancel(s.resetErr)
|
||||
@@ -577,7 +590,7 @@ func (s *SendStream) handleStopSendingFrame(f *wire.StopSendingFrame) {
|
||||
s.sender.onHasStreamControlFrame(s.streamID, s)
|
||||
}
|
||||
|
||||
func (s *SendStream) getControlFrame(time.Time) (_ ackhandler.Frame, ok, hasMore bool) {
|
||||
func (s *SendStream) getControlFrame(monotime.Time) (_ ackhandler.Frame, ok, hasMore bool) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
@@ -616,7 +629,7 @@ func (s *SendStream) Context() context.Context {
|
||||
// A zero value for t means Write will not time out.
|
||||
func (s *SendStream) SetWriteDeadline(t time.Time) error {
|
||||
s.mutex.Lock()
|
||||
s.deadline = t
|
||||
s.deadline = monotime.FromTime(t)
|
||||
s.mutex.Unlock()
|
||||
s.signalWrite()
|
||||
return nil
|
||||
@@ -629,6 +642,7 @@ func (s *SendStream) closeForShutdown(err error) {
|
||||
s.mutex.Lock()
|
||||
if s.shutdownErr == nil && !s.finishedWriting {
|
||||
s.shutdownErr = err
|
||||
s.returnFramesToPool()
|
||||
}
|
||||
s.mutex.Unlock()
|
||||
s.signalWrite()
|
||||
@@ -673,6 +687,8 @@ func (s *sendStreamAckHandler) OnLost(f wire.Frame) {
|
||||
// If the reliable size was 0 when the stream was cancelled,
|
||||
// the number of outstanding frames was immediately set to 0, and the retransmission queue was dropped.
|
||||
if s.resetErr != nil && (*SendStream)(s).reliableOffset() == 0 {
|
||||
// Return the frame to pool since it won't be retransmitted
|
||||
sf.PutBack()
|
||||
s.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
+10
-6
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/handshake"
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/qerr"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
@@ -34,7 +35,7 @@ type packetHandler interface {
|
||||
|
||||
type zeroRTTQueue struct {
|
||||
packets []receivedPacket
|
||||
expiration time.Time
|
||||
expiration monotime.Time
|
||||
}
|
||||
|
||||
type rejectedPacket struct {
|
||||
@@ -62,7 +63,7 @@ type baseServer struct {
|
||||
|
||||
receivedPackets chan receivedPacket
|
||||
|
||||
nextZeroRTTCleanup time.Time
|
||||
nextZeroRTTCleanup monotime.Time
|
||||
zeroRTTQueues map[protocol.ConnectionID]*zeroRTTQueue // only initialized if acceptEarlyConns == true
|
||||
|
||||
connContext func(context.Context, *ClientInfo) (context.Context, error)
|
||||
@@ -132,7 +133,7 @@ func (l *Listener) Accept(ctx context.Context) (*Conn, error) {
|
||||
// Close closes the listener.
|
||||
// Accept will return [ErrServerClosed] as soon as all connections in the accept queue have been accepted.
|
||||
// QUIC handshakes that are still in flight will be rejected with a CONNECTION_REFUSED error.
|
||||
// Already established (accepted)connections will be unaffected.
|
||||
// Already established (accepted) connections will be unaffected.
|
||||
func (l *Listener) Close() error {
|
||||
return l.baseServer.Close()
|
||||
}
|
||||
@@ -161,7 +162,10 @@ func (l *EarlyListener) Accept(ctx context.Context) (*Conn, error) {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Close the server. All active connections will be closed.
|
||||
// Close closes the listener.
|
||||
// Accept will return [ErrServerClosed] as soon as all connections in the accept queue have been accepted.
|
||||
// Early connections that are still in flight will be rejected with a CONNECTION_REFUSED error.
|
||||
// Already established (accepted) connections will be unaffected.
|
||||
func (l *EarlyListener) Close() error {
|
||||
return l.baseServer.Close()
|
||||
}
|
||||
@@ -535,10 +539,10 @@ func (s *baseServer) handle0RTTPacket(p receivedPacket) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *baseServer) cleanupZeroRTTQueues(now time.Time) {
|
||||
func (s *baseServer) cleanupZeroRTTQueues(now monotime.Time) {
|
||||
// Iterate over all queues to find those that are expired.
|
||||
// This is ok since we're placing a pretty low limit on the number of queues.
|
||||
var nextCleanup time.Time
|
||||
var nextCleanup monotime.Time
|
||||
for connID, q := range s.zeroRTTQueues {
|
||||
if q.expiration.After(now) {
|
||||
if nextCleanup.IsZero() || nextCleanup.After(q.expiration) {
|
||||
|
||||
+4
-3
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/quic-go/quic-go/internal/ackhandler"
|
||||
"github.com/quic-go/quic-go/internal/flowcontrol"
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
)
|
||||
@@ -147,11 +148,11 @@ func (s *Stream) Close() error {
|
||||
return s.sendStr.Close()
|
||||
}
|
||||
|
||||
func (s *Stream) handleResetStreamFrame(frame *wire.ResetStreamFrame, rcvTime time.Time) error {
|
||||
func (s *Stream) handleResetStreamFrame(frame *wire.ResetStreamFrame, rcvTime monotime.Time) error {
|
||||
return s.receiveStr.handleResetStreamFrame(frame, rcvTime)
|
||||
}
|
||||
|
||||
func (s *Stream) handleStreamFrame(frame *wire.StreamFrame, rcvTime time.Time) error {
|
||||
func (s *Stream) handleStreamFrame(frame *wire.StreamFrame, rcvTime monotime.Time) error {
|
||||
return s.receiveStr.handleStreamFrame(frame, rcvTime)
|
||||
}
|
||||
|
||||
@@ -171,7 +172,7 @@ func (s *Stream) popStreamFrame(maxBytes protocol.ByteCount, v protocol.Version)
|
||||
return s.sendStr.popStreamFrame(maxBytes, v)
|
||||
}
|
||||
|
||||
func (s *Stream) getControlFrame(now time.Time) (_ ackhandler.Frame, ok, hasMore bool) {
|
||||
func (s *Stream) getControlFrame(now monotime.Time) (_ ackhandler.Frame, ok, hasMore bool) {
|
||||
f, ok, _ := s.sendStr.getControlFrame(now)
|
||||
if ok {
|
||||
return f, true, true
|
||||
|
||||
+5
-5
@@ -4,9 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/flowcontrol"
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/qerr"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
@@ -251,8 +251,8 @@ func (m *streamsMap) HandleStopSendingFrame(f *wire.StopSendingFrame) error {
|
||||
}
|
||||
|
||||
type receiveStreamFrameHandler interface {
|
||||
handleResetStreamFrame(*wire.ResetStreamFrame, time.Time) error
|
||||
handleStreamFrame(*wire.StreamFrame, time.Time) error
|
||||
handleResetStreamFrame(*wire.ResetStreamFrame, monotime.Time) error
|
||||
handleStreamFrame(*wire.StreamFrame, monotime.Time) error
|
||||
}
|
||||
|
||||
func (m *streamsMap) getReceiveStream(id protocol.StreamID) (receiveStreamFrameHandler, error) {
|
||||
@@ -295,7 +295,7 @@ func (m *streamsMap) HandleStreamDataBlockedFrame(f *wire.StreamDataBlockedFrame
|
||||
return nil // we don't need to do anything in response to a STREAM_DATA_BLOCKED frame
|
||||
}
|
||||
|
||||
func (m *streamsMap) HandleResetStreamFrame(f *wire.ResetStreamFrame, rcvTime time.Time) error {
|
||||
func (m *streamsMap) HandleResetStreamFrame(f *wire.ResetStreamFrame, rcvTime monotime.Time) error {
|
||||
str, err := m.getReceiveStream(f.StreamID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -306,7 +306,7 @@ func (m *streamsMap) HandleResetStreamFrame(f *wire.ResetStreamFrame, rcvTime ti
|
||||
return str.handleResetStreamFrame(f, rcvTime)
|
||||
}
|
||||
|
||||
func (m *streamsMap) HandleStreamFrame(f *wire.StreamFrame, rcvTime time.Time) error {
|
||||
func (m *streamsMap) HandleStreamFrame(f *wire.StreamFrame, rcvTime monotime.Time) error {
|
||||
str, err := m.getReceiveStream(f.StreamID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+2
-1
@@ -10,6 +10,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
)
|
||||
@@ -123,7 +124,7 @@ func (c *basicConn) ReadPacket() (receivedPacket, error) {
|
||||
}
|
||||
return receivedPacket{
|
||||
remoteAddr: addr,
|
||||
rcvTime: time.Now(),
|
||||
rcvTime: monotime.Now(),
|
||||
data: buffer.Data[:n],
|
||||
buffer: buffer,
|
||||
}, nil
|
||||
|
||||
+2
-2
@@ -12,13 +12,13 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/quic-go/quic-go/internal/monotime"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
)
|
||||
@@ -185,7 +185,7 @@ func (c *oobConn) ReadPacket() (receivedPacket, error) {
|
||||
data := msg.OOB[:msg.NN]
|
||||
p := receivedPacket{
|
||||
remoteAddr: msg.Addr,
|
||||
rcvTime: time.Now(),
|
||||
rcvTime: monotime.Now(),
|
||||
data: msg.Buffers[0][:msg.N],
|
||||
buffer: buffer,
|
||||
}
|
||||
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
//go:build tools
|
||||
|
||||
package quic
|
||||
|
||||
import (
|
||||
_ "go.uber.org/mock/mockgen"
|
||||
)
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
# This is the official list of GoMock authors for copyright purposes.
|
||||
# This file is distinct from the CONTRIBUTORS files.
|
||||
# See the latter for an explanation.
|
||||
|
||||
# Names should be added to this file as
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Alex Reece <[email protected]>
|
||||
Google Inc.
|
||||
-202
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
|
||||
"go.uber.org/mock/mockgen/model"
|
||||
|
||||
"golang.org/x/tools/go/gcexportdata"
|
||||
)
|
||||
|
||||
func archiveMode(importPath string, symbols []string, archive string) (*model.Package, error) {
|
||||
f, err := os.Open(archive)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
r, err := gcexportdata.NewReader(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read export data %q: %v", archive, err)
|
||||
}
|
||||
|
||||
fset := token.NewFileSet()
|
||||
imports := make(map[string]*types.Package)
|
||||
tp, err := gcexportdata.Read(r, fset, imports, importPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pkg := &model.Package{
|
||||
Name: tp.Name(),
|
||||
PkgPath: tp.Path(),
|
||||
Interfaces: make([]*model.Interface, 0, len(symbols)),
|
||||
}
|
||||
for _, name := range symbols {
|
||||
m := tp.Scope().Lookup(name)
|
||||
tn, ok := m.(*types.TypeName)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ti, ok := tn.Type().Underlying().(*types.Interface)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
it, err := model.InterfaceFromGoTypesType(ti)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
it.Name = m.Name()
|
||||
pkg.Interfaces = append(pkg.Interfaces, it)
|
||||
}
|
||||
return pkg, nil
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
deprecatedFlagProgOnly = "prog_only"
|
||||
deprecatedFlagExecOnly = "exec_only"
|
||||
)
|
||||
|
||||
var (
|
||||
_ = flag.Bool("prog_only", false, "DEPRECATED (reflect mode) Only generate the reflection program; write it to stdout and exit.")
|
||||
_ = flag.String("exec_only", "", "DEPRECATED (reflect mode) If set, execute this reflection program.")
|
||||
)
|
||||
|
||||
// notifyAboutDeprecatedFlags prints a warning message for a deprecated flags if they are set.
|
||||
func notifyAboutDeprecatedFlags() {
|
||||
const resetColorPostfix = "\033[0m"
|
||||
logger := initWarningLogger()
|
||||
|
||||
flag.Visit(func(f *flag.Flag) {
|
||||
switch f.Name {
|
||||
case deprecatedFlagProgOnly:
|
||||
logger.Println("The -prog_only flag is deprecated and has no effect.", resetColorPostfix)
|
||||
case deprecatedFlagExecOnly:
|
||||
logger.Println("The -exec_only flag is deprecated and has no effect.", resetColorPostfix)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func initWarningLogger() *log.Logger {
|
||||
const (
|
||||
yellowColor = "\033[33m"
|
||||
warningPrefix = yellowColor + "WARNING: "
|
||||
)
|
||||
|
||||
return log.New(os.Stdout, warningPrefix, log.Ldate|log.Ltime)
|
||||
}
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"go.uber.org/mock/mockgen/model"
|
||||
)
|
||||
|
||||
func getTypeSpecTypeParams(ts *ast.TypeSpec) []*ast.Field {
|
||||
if ts == nil || ts.TypeParams == nil {
|
||||
return nil
|
||||
}
|
||||
return ts.TypeParams.List
|
||||
}
|
||||
|
||||
func (p *fileParser) parseGenericType(pkg string, typ ast.Expr, tps map[string]model.Type) (model.Type, error) {
|
||||
switch v := typ.(type) {
|
||||
case *ast.IndexExpr:
|
||||
m, err := p.parseType(pkg, v.X, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nm, ok := m.(*model.NamedType)
|
||||
if !ok {
|
||||
return m, nil
|
||||
}
|
||||
t, err := p.parseType(pkg, v.Index, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nm.TypeParams = &model.TypeParametersType{TypeParameters: []model.Type{t}}
|
||||
return m, nil
|
||||
case *ast.IndexListExpr:
|
||||
m, err := p.parseType(pkg, v.X, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nm, ok := m.(*model.NamedType)
|
||||
if !ok {
|
||||
return m, nil
|
||||
}
|
||||
var ts []model.Type
|
||||
for _, expr := range v.Indices {
|
||||
t, err := p.parseType(pkg, expr, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts = append(ts, t)
|
||||
}
|
||||
nm.TypeParams = &model.TypeParametersType{TypeParameters: ts}
|
||||
return m, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *fileParser) parseGenericMethod(field *ast.Field, it *namedInterface, iface *model.Interface, pkg string, tps map[string]model.Type) ([]*model.Method, error) {
|
||||
var indices []ast.Expr
|
||||
var typ ast.Expr
|
||||
switch v := field.Type.(type) {
|
||||
case *ast.IndexExpr:
|
||||
indices = []ast.Expr{v.Index}
|
||||
typ = v.X
|
||||
case *ast.IndexListExpr:
|
||||
indices = v.Indices
|
||||
typ = v.X
|
||||
case *ast.UnaryExpr:
|
||||
if v.Op == token.TILDE {
|
||||
return nil, errConstraintInterface
|
||||
}
|
||||
return nil, fmt.Errorf("~T may only appear as constraint for %T", field.Type)
|
||||
case *ast.BinaryExpr:
|
||||
if v.Op == token.OR {
|
||||
return nil, errConstraintInterface
|
||||
}
|
||||
return nil, fmt.Errorf("A|B may only appear as constraint for %T", field.Type)
|
||||
default:
|
||||
return nil, fmt.Errorf("don't know how to mock method of type %T", field.Type)
|
||||
}
|
||||
|
||||
nf := &ast.Field{
|
||||
Doc: field.Comment,
|
||||
Names: field.Names,
|
||||
Type: typ,
|
||||
Tag: field.Tag,
|
||||
Comment: field.Comment,
|
||||
}
|
||||
|
||||
it.embeddedInstTypeParams = indices
|
||||
|
||||
return p.parseMethod(nf, it, iface, pkg, tps)
|
||||
}
|
||||
|
||||
var errConstraintInterface = errors.New("interface contains constraints")
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"os"
|
||||
|
||||
"go.uber.org/mock/mockgen/model"
|
||||
)
|
||||
|
||||
func gobMode(path string) (*model.Package, error) {
|
||||
in, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer in.Close()
|
||||
var pkg model.Package
|
||||
if err := gob.NewDecoder(in).Decode(&pkg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pkg, nil
|
||||
}
|
||||
-945
@@ -1,945 +0,0 @@
|
||||
// Copyright 2010 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// MockGen generates mock implementations of Go interfaces.
|
||||
package main
|
||||
|
||||
// TODO: This does not support recursive embedded interfaces.
|
||||
// TODO: This does not support embedding package-local interfaces in a separate file.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/mod/modfile"
|
||||
toolsimports "golang.org/x/tools/imports"
|
||||
|
||||
"go.uber.org/mock/mockgen/model"
|
||||
)
|
||||
|
||||
const (
|
||||
gomockImportPath = "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
var (
|
||||
version = ""
|
||||
commit = "none"
|
||||
date = "unknown"
|
||||
)
|
||||
|
||||
var (
|
||||
archive = flag.String("archive", "", "(archive mode) Input Go archive file; enables archive mode.")
|
||||
source = flag.String("source", "", "(source mode) Input Go source file; enables source mode.")
|
||||
destination = flag.String("destination", "", "Output file; defaults to stdout.")
|
||||
mockNames = flag.String("mock_names", "", "Comma-separated interfaceName=mockName pairs of explicit mock names to use. Mock names default to 'Mock'+ interfaceName suffix.")
|
||||
packageOut = flag.String("package", "", "Package of the generated code; defaults to the package of the input with a 'mock_' prefix.")
|
||||
selfPackage = flag.String("self_package", "", "The full package import path for the generated code. The purpose of this flag is to prevent import cycles in the generated code by trying to include its own package. This can happen if the mock's package is set to one of its inputs (usually the main one) and the output is stdio so mockgen cannot detect the final output package. Setting this flag will then tell mockgen which import to exclude.")
|
||||
writeCmdComment = flag.Bool("write_command_comment", true, "Writes the command used as a comment if true.")
|
||||
writePkgComment = flag.Bool("write_package_comment", true, "Writes package documentation comment (godoc) if true.")
|
||||
writeSourceComment = flag.Bool("write_source_comment", true, "Writes original file (source mode) or interface names (package mode) comment if true.")
|
||||
writeGenerateDirective = flag.Bool("write_generate_directive", false, "Add //go:generate directive to regenerate the mock")
|
||||
copyrightFile = flag.String("copyright_file", "", "Copyright file used to add copyright header")
|
||||
buildConstraint = flag.String("build_constraint", "", "If non-empty, added as //go:build <constraint>")
|
||||
typed = flag.Bool("typed", false, "Generate Type-safe 'Return', 'Do', 'DoAndReturn' function")
|
||||
imports = flag.String("imports", "", "(source mode) Comma-separated name=path pairs of explicit imports to use.")
|
||||
auxFiles = flag.String("aux_files", "", "(source mode) Comma-separated pkg=path pairs of auxiliary Go source files.")
|
||||
modelGob = flag.String("model_gob", "", "Skip package/source loading entirely and use the gob encoded model.Package at the given path")
|
||||
excludeInterfaces = flag.String("exclude_interfaces", "", "Comma-separated names of interfaces to be excluded")
|
||||
debugParser = flag.Bool("debug_parser", false, "Print out parser results only.")
|
||||
showVersion = flag.Bool("version", false, "Print version.")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
notifyAboutDeprecatedFlags()
|
||||
|
||||
if *showVersion {
|
||||
printVersion()
|
||||
return
|
||||
}
|
||||
|
||||
var pkg *model.Package
|
||||
var err error
|
||||
var packageName string
|
||||
|
||||
// Switch between modes
|
||||
switch {
|
||||
case *modelGob != "": // gob mode
|
||||
pkg, err = gobMode(*modelGob)
|
||||
case *source != "": // source mode
|
||||
pkg, err = sourceMode(*source)
|
||||
case *archive != "": // archive mode
|
||||
checkArgs()
|
||||
packageName = flag.Arg(0)
|
||||
interfaces := strings.Split(flag.Arg(1), ",")
|
||||
pkg, err = archiveMode(packageName, interfaces, *archive)
|
||||
|
||||
default: // package mode
|
||||
checkArgs()
|
||||
packageName = flag.Arg(0)
|
||||
interfaces := strings.Split(flag.Arg(1), ",")
|
||||
|
||||
if packageName == "." {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalf("Get current directory failed: %v", err)
|
||||
}
|
||||
packageName, err = packageNameOfDir(dir)
|
||||
if err != nil {
|
||||
log.Fatalf("Parse package name failed: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
parser := packageModeParser{}
|
||||
pkg, err = parser.parsePackage(packageName, interfaces)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Loading input failed: %v", err)
|
||||
}
|
||||
|
||||
if *debugParser {
|
||||
pkg.Print(os.Stdout)
|
||||
return
|
||||
}
|
||||
|
||||
outputPackageName := *packageOut
|
||||
if outputPackageName == "" {
|
||||
// pkg.Name in package mode is the base name of the import path,
|
||||
// which might have characters that are illegal to have in package names.
|
||||
outputPackageName = "mock_" + sanitize(pkg.Name)
|
||||
}
|
||||
|
||||
// outputPackagePath represents the fully qualified name of the package of
|
||||
// the generated code. Its purposes are to prevent the module from importing
|
||||
// itself and to prevent qualifying type names that come from its own
|
||||
// package (i.e. if there is a type called X then we want to print "X" not
|
||||
// "package.X" since "package" is this package). This can happen if the mock
|
||||
// is output into an already existing package.
|
||||
outputPackagePath := *selfPackage
|
||||
if outputPackagePath == "" && *destination != "" {
|
||||
dstPath, err := filepath.Abs(filepath.Dir(*destination))
|
||||
if err == nil {
|
||||
pkgPath, err := parsePackageImport(dstPath)
|
||||
if err == nil {
|
||||
outputPackagePath = pkgPath
|
||||
} else {
|
||||
log.Println("Unable to infer -self_package from destination file path:", err)
|
||||
}
|
||||
} else {
|
||||
log.Println("Unable to determine destination file path:", err)
|
||||
}
|
||||
}
|
||||
|
||||
g := &generator{
|
||||
buildConstraint: *buildConstraint,
|
||||
}
|
||||
if *source != "" {
|
||||
g.filename = *source
|
||||
} else if *archive != "" {
|
||||
g.filename = *archive
|
||||
} else {
|
||||
g.srcPackage = packageName
|
||||
g.srcInterfaces = flag.Arg(1)
|
||||
}
|
||||
g.destination = *destination
|
||||
|
||||
if *mockNames != "" {
|
||||
g.mockNames = parseMockNames(*mockNames)
|
||||
}
|
||||
if *copyrightFile != "" {
|
||||
header, err := os.ReadFile(*copyrightFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed reading copyright file: %v", err)
|
||||
}
|
||||
|
||||
g.copyrightHeader = string(header)
|
||||
}
|
||||
if err := g.Generate(pkg, outputPackageName, outputPackagePath); err != nil {
|
||||
log.Fatalf("Failed generating mock: %v", err)
|
||||
}
|
||||
output := g.Output()
|
||||
dst := os.Stdout
|
||||
if len(*destination) > 0 {
|
||||
if err := os.MkdirAll(filepath.Dir(*destination), os.ModePerm); err != nil {
|
||||
log.Fatalf("Unable to create directory: %v", err)
|
||||
}
|
||||
existing, err := os.ReadFile(*destination)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
log.Fatalf("Failed reading pre-exiting destination file: %v", err)
|
||||
}
|
||||
if len(existing) == len(output) && bytes.Equal(existing, output) {
|
||||
return
|
||||
}
|
||||
f, err := os.Create(*destination)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed opening destination file: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
dst = f
|
||||
}
|
||||
if _, err := dst.Write(output); err != nil {
|
||||
log.Fatalf("Failed writing to destination: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func parseMockNames(names string) map[string]string {
|
||||
mocksMap := make(map[string]string)
|
||||
for _, kv := range strings.Split(names, ",") {
|
||||
parts := strings.SplitN(kv, "=", 2)
|
||||
if len(parts) != 2 || parts[1] == "" {
|
||||
log.Fatalf("bad mock names spec: %v", kv)
|
||||
}
|
||||
mocksMap[parts[0]] = parts[1]
|
||||
}
|
||||
return mocksMap
|
||||
}
|
||||
|
||||
func parseExcludeInterfaces(names string) map[string]struct{} {
|
||||
splitNames := strings.Split(names, ",")
|
||||
namesSet := make(map[string]struct{}, len(splitNames))
|
||||
for _, name := range splitNames {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
namesSet[name] = struct{}{}
|
||||
}
|
||||
|
||||
if len(namesSet) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return namesSet
|
||||
}
|
||||
|
||||
func checkArgs() {
|
||||
if flag.NArg() != 2 {
|
||||
usage()
|
||||
log.Fatal("Expected exactly two arguments")
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
_, _ = io.WriteString(os.Stderr, usageText)
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
const usageText = `mockgen has three modes of operation: archive, source and package.
|
||||
|
||||
Source mode generates mock interfaces from a source file.
|
||||
It is enabled by using the -source flag. Other flags that
|
||||
may be useful in this mode are -imports, -aux_files and -exclude_interfaces.
|
||||
Example:
|
||||
mockgen -source=foo.go [other options]
|
||||
|
||||
Package mode works by specifying the package and interface names.
|
||||
It is enabled by passing two non-flag arguments: an import path, and a
|
||||
comma-separated list of symbols.
|
||||
You can use "." to refer to the current path's package.
|
||||
Example:
|
||||
mockgen database/sql/driver Conn,Driver
|
||||
mockgen . SomeInterface
|
||||
|
||||
Archive mode generates mock interfaces from a package archive
|
||||
file (.a). It is enabled by using the -archive flag and two
|
||||
non-flag arguments: an import path, and a comma-separated
|
||||
list of symbols.
|
||||
Example:
|
||||
mockgen -archive=pkg.a database/sql/driver Conn,Driver
|
||||
|
||||
`
|
||||
|
||||
type generator struct {
|
||||
buf bytes.Buffer
|
||||
indent string
|
||||
mockNames map[string]string // may be empty
|
||||
filename string // may be empty
|
||||
destination string // may be empty
|
||||
srcPackage, srcInterfaces string // may be empty
|
||||
copyrightHeader string
|
||||
buildConstraint string // may be empty
|
||||
|
||||
packageMap map[string]string // map from import path to package name
|
||||
}
|
||||
|
||||
func (g *generator) p(format string, args ...any) {
|
||||
_, _ = fmt.Fprintf(&g.buf, g.indent+format+"\n", args...)
|
||||
}
|
||||
|
||||
func (g *generator) in() {
|
||||
g.indent += "\t"
|
||||
}
|
||||
|
||||
func (g *generator) out() {
|
||||
if len(g.indent) > 0 {
|
||||
g.indent = g.indent[0 : len(g.indent)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// sanitize cleans up a string to make a suitable package name.
|
||||
func sanitize(s string) string {
|
||||
t := ""
|
||||
for _, r := range s {
|
||||
if t == "" {
|
||||
if unicode.IsLetter(r) || r == '_' {
|
||||
t += string(r)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
|
||||
t += string(r)
|
||||
continue
|
||||
}
|
||||
}
|
||||
t += "_"
|
||||
}
|
||||
if t == "_" {
|
||||
t = "x"
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (g *generator) Generate(pkg *model.Package, outputPkgName string, outputPackagePath string) error {
|
||||
if outputPkgName != pkg.Name && *selfPackage == "" {
|
||||
// reset outputPackagePath if it's not passed in through -self_package
|
||||
outputPackagePath = ""
|
||||
}
|
||||
|
||||
if g.copyrightHeader != "" {
|
||||
lines := strings.Split(g.copyrightHeader, "\n")
|
||||
for _, line := range lines {
|
||||
g.p("// %s", line)
|
||||
}
|
||||
g.p("")
|
||||
}
|
||||
|
||||
if g.buildConstraint != "" {
|
||||
g.p("//go:build %s", g.buildConstraint)
|
||||
// https://pkg.go.dev/cmd/go#hdr-Build_constraints:~:text=a%20build%20constraint%20should%20be%20followed%20by%20a%20blank%20line
|
||||
g.p("")
|
||||
}
|
||||
|
||||
g.p("// Code generated by MockGen. DO NOT EDIT.")
|
||||
if *writeSourceComment {
|
||||
if g.filename != "" {
|
||||
g.p("// Source: %v", g.filename)
|
||||
} else {
|
||||
g.p("// Source: %v (interfaces: %v)", g.srcPackage, g.srcInterfaces)
|
||||
}
|
||||
}
|
||||
if *writeCmdComment {
|
||||
g.p("//")
|
||||
g.p("// Generated by this command:")
|
||||
g.p("//")
|
||||
// only log the name of the executable, not the full path
|
||||
name := filepath.Base(os.Args[0])
|
||||
if runtime.GOOS == "windows" {
|
||||
name = strings.TrimSuffix(name, ".exe")
|
||||
}
|
||||
g.p("//\t%v", strings.Join(append([]string{name}, os.Args[1:]...), " "))
|
||||
g.p("//")
|
||||
}
|
||||
|
||||
// Get all required imports, and generate unique names for them all.
|
||||
im := pkg.Imports()
|
||||
im[gomockImportPath] = true
|
||||
|
||||
// Only import reflect if it's used. We only use reflect in mocked methods
|
||||
// so only import if any of the mocked interfaces have methods.
|
||||
for _, intf := range pkg.Interfaces {
|
||||
if len(intf.Methods) > 0 {
|
||||
im["reflect"] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Sort keys to make import alias generation predictable
|
||||
sortedPaths := make([]string, len(im))
|
||||
x := 0
|
||||
for pth := range im {
|
||||
sortedPaths[x] = pth
|
||||
x++
|
||||
}
|
||||
sort.Strings(sortedPaths)
|
||||
|
||||
packagesName := createPackageMap(sortedPaths)
|
||||
|
||||
definedImports := make(map[string]string, len(im))
|
||||
if *imports != "" {
|
||||
for _, kv := range strings.Split(*imports, ",") {
|
||||
eq := strings.Index(kv, "=")
|
||||
if k, v := kv[:eq], kv[eq+1:]; k != "." {
|
||||
definedImports[v] = k
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g.packageMap = make(map[string]string, len(im))
|
||||
localNames := make(map[string]bool, len(im))
|
||||
for _, pth := range sortedPaths {
|
||||
base, ok := packagesName[pth]
|
||||
if !ok {
|
||||
base = sanitize(path.Base(pth))
|
||||
}
|
||||
|
||||
// Local names for an imported package can usually be the basename of the import path.
|
||||
// A couple of situations don't permit that, such as duplicate local names
|
||||
// (e.g. importing "html/template" and "text/template"), or where the basename is
|
||||
// a keyword (e.g. "foo/case") or when defining a name for that by using the -imports flag.
|
||||
// try base0, base1, ...
|
||||
pkgName := base
|
||||
|
||||
if _, ok := definedImports[pth]; ok {
|
||||
pkgName = definedImports[pth]
|
||||
}
|
||||
|
||||
i := 0
|
||||
for localNames[pkgName] || token.Lookup(pkgName).IsKeyword() || pkgName == "any" {
|
||||
pkgName = base + strconv.Itoa(i)
|
||||
i++
|
||||
}
|
||||
|
||||
// Avoid importing package if source pkg == output pkg
|
||||
if pth == pkg.PkgPath && outputPackagePath == pkg.PkgPath {
|
||||
continue
|
||||
}
|
||||
|
||||
g.packageMap[pth] = pkgName
|
||||
localNames[pkgName] = true
|
||||
}
|
||||
|
||||
// Ensure there is an empty line between “generated by” block and
|
||||
// package documentation comments to follow the recommendations:
|
||||
// https://go.dev/wiki/CodeReviewComments#package-comments
|
||||
// That is, “generated by” should not be a package comment.
|
||||
g.p("")
|
||||
|
||||
if *writePkgComment {
|
||||
g.p("// Package %v is a generated GoMock package.", outputPkgName)
|
||||
}
|
||||
g.p("package %v", outputPkgName)
|
||||
g.p("")
|
||||
g.p("import (")
|
||||
g.in()
|
||||
for pkgPath, pkgName := range g.packageMap {
|
||||
if pkgPath == outputPackagePath {
|
||||
continue
|
||||
}
|
||||
g.p("%v %q", pkgName, pkgPath)
|
||||
}
|
||||
for _, pkgPath := range pkg.DotImports {
|
||||
g.p(". %q", pkgPath)
|
||||
}
|
||||
g.out()
|
||||
g.p(")")
|
||||
|
||||
if *writeGenerateDirective {
|
||||
g.p("//go:generate %v", strings.Join(os.Args, " "))
|
||||
}
|
||||
|
||||
for _, intf := range pkg.Interfaces {
|
||||
if err := g.GenerateMockInterface(intf, outputPackagePath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// The name of the mock type to use for the given interface identifier.
|
||||
func (g *generator) mockName(typeName string) string {
|
||||
if mockName, ok := g.mockNames[typeName]; ok {
|
||||
return mockName
|
||||
}
|
||||
|
||||
return "Mock" + typeName
|
||||
}
|
||||
|
||||
// formattedTypeParams returns a long and short form of type param info used for
|
||||
// printing. If analyzing a interface with type param [I any, O any] the result
|
||||
// will be:
|
||||
// "[I any, O any]", "[I, O]"
|
||||
func (g *generator) formattedTypeParams(it *model.Interface, pkgOverride string) (string, string) {
|
||||
if len(it.TypeParams) == 0 {
|
||||
return "", ""
|
||||
}
|
||||
var long, short strings.Builder
|
||||
long.WriteString("[")
|
||||
short.WriteString("[")
|
||||
for i, v := range it.TypeParams {
|
||||
if i != 0 {
|
||||
long.WriteString(", ")
|
||||
short.WriteString(", ")
|
||||
}
|
||||
long.WriteString(v.Name)
|
||||
short.WriteString(v.Name)
|
||||
long.WriteString(fmt.Sprintf(" %s", v.Type.String(g.packageMap, pkgOverride)))
|
||||
}
|
||||
long.WriteString("]")
|
||||
short.WriteString("]")
|
||||
return long.String(), short.String()
|
||||
}
|
||||
|
||||
func (g *generator) GenerateMockInterface(intf *model.Interface, outputPackagePath string) error {
|
||||
mockType := g.mockName(intf.Name)
|
||||
longTp, shortTp := g.formattedTypeParams(intf, outputPackagePath)
|
||||
|
||||
g.p("")
|
||||
g.p("// %v is a mock of %v interface.", mockType, intf.Name)
|
||||
g.p("type %v%v struct {", mockType, longTp)
|
||||
g.in()
|
||||
g.p("ctrl *gomock.Controller")
|
||||
g.p("recorder *%vMockRecorder%v", mockType, shortTp)
|
||||
g.p("isgomock struct{}")
|
||||
g.out()
|
||||
g.p("}")
|
||||
g.p("")
|
||||
|
||||
g.p("// %vMockRecorder is the mock recorder for %v.", mockType, mockType)
|
||||
g.p("type %vMockRecorder%v struct {", mockType, longTp)
|
||||
g.in()
|
||||
g.p("mock *%v%v", mockType, shortTp)
|
||||
g.out()
|
||||
g.p("}")
|
||||
g.p("")
|
||||
|
||||
g.p("// New%v creates a new mock instance.", mockType)
|
||||
g.p("func New%v%v(ctrl *gomock.Controller) *%v%v {", mockType, longTp, mockType, shortTp)
|
||||
g.in()
|
||||
g.p("mock := &%v%v{ctrl: ctrl}", mockType, shortTp)
|
||||
g.p("mock.recorder = &%vMockRecorder%v{mock}", mockType, shortTp)
|
||||
g.p("return mock")
|
||||
g.out()
|
||||
g.p("}")
|
||||
g.p("")
|
||||
|
||||
// XXX: possible name collision here if someone has EXPECT in their interface.
|
||||
g.p("// EXPECT returns an object that allows the caller to indicate expected use.")
|
||||
g.p("func (m *%v%v) EXPECT() *%vMockRecorder%v {", mockType, shortTp, mockType, shortTp)
|
||||
g.in()
|
||||
g.p("return m.recorder")
|
||||
g.out()
|
||||
g.p("}")
|
||||
|
||||
g.GenerateMockMethods(mockType, intf, outputPackagePath, longTp, shortTp, *typed)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type byMethodName []*model.Method
|
||||
|
||||
func (b byMethodName) Len() int { return len(b) }
|
||||
func (b byMethodName) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||
func (b byMethodName) Less(i, j int) bool { return b[i].Name < b[j].Name }
|
||||
|
||||
func (g *generator) GenerateMockMethods(mockType string, intf *model.Interface, pkgOverride, longTp, shortTp string, typed bool) {
|
||||
sort.Sort(byMethodName(intf.Methods))
|
||||
for _, m := range intf.Methods {
|
||||
g.p("")
|
||||
_ = g.GenerateMockMethod(mockType, m, pkgOverride, shortTp)
|
||||
g.p("")
|
||||
_ = g.GenerateMockRecorderMethod(intf, m, shortTp, typed)
|
||||
if typed {
|
||||
g.p("")
|
||||
_ = g.GenerateMockReturnCallMethod(intf, m, pkgOverride, longTp, shortTp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeArgString(argNames, argTypes []string) string {
|
||||
args := make([]string, len(argNames))
|
||||
for i, name := range argNames {
|
||||
// specify the type only once for consecutive args of the same type
|
||||
if i+1 < len(argTypes) && argTypes[i] == argTypes[i+1] {
|
||||
args[i] = name
|
||||
} else {
|
||||
args[i] = name + " " + argTypes[i]
|
||||
}
|
||||
}
|
||||
return strings.Join(args, ", ")
|
||||
}
|
||||
|
||||
// GenerateMockMethod generates a mock method implementation.
|
||||
// If non-empty, pkgOverride is the package in which unqualified types reside.
|
||||
func (g *generator) GenerateMockMethod(mockType string, m *model.Method, pkgOverride, shortTp string) error {
|
||||
argNames := g.getArgNames(m, true /* in */)
|
||||
argTypes := g.getArgTypes(m, pkgOverride, true /* in */)
|
||||
argString := makeArgString(argNames, argTypes)
|
||||
|
||||
rets := make([]string, len(m.Out))
|
||||
for i, p := range m.Out {
|
||||
rets[i] = p.Type.String(g.packageMap, pkgOverride)
|
||||
}
|
||||
retString := strings.Join(rets, ", ")
|
||||
if len(rets) > 1 {
|
||||
retString = "(" + retString + ")"
|
||||
}
|
||||
if retString != "" {
|
||||
retString = " " + retString
|
||||
}
|
||||
|
||||
ia := newIdentifierAllocator(argNames)
|
||||
idRecv := ia.allocateIdentifier("m")
|
||||
|
||||
g.p("// %v mocks base method.", m.Name)
|
||||
g.p("func (%v *%v%v) %v(%v)%v {", idRecv, mockType, shortTp, m.Name, argString, retString)
|
||||
g.in()
|
||||
g.p("%s.ctrl.T.Helper()", idRecv)
|
||||
|
||||
var callArgs string
|
||||
if m.Variadic == nil {
|
||||
if len(argNames) > 0 {
|
||||
callArgs = ", " + strings.Join(argNames, ", ")
|
||||
}
|
||||
} else {
|
||||
// Non-trivial. The generated code must build a []any,
|
||||
// but the variadic argument may be any type.
|
||||
idVarArgs := ia.allocateIdentifier("varargs")
|
||||
idVArg := ia.allocateIdentifier("a")
|
||||
g.p("%s := []any{%s}", idVarArgs, strings.Join(argNames[:len(argNames)-1], ", "))
|
||||
g.p("for _, %s := range %s {", idVArg, argNames[len(argNames)-1])
|
||||
g.in()
|
||||
g.p("%s = append(%s, %s)", idVarArgs, idVarArgs, idVArg)
|
||||
g.out()
|
||||
g.p("}")
|
||||
callArgs = ", " + idVarArgs + "..."
|
||||
}
|
||||
if len(m.Out) == 0 {
|
||||
g.p(`%v.ctrl.Call(%v, %q%v)`, idRecv, idRecv, m.Name, callArgs)
|
||||
} else {
|
||||
idRet := ia.allocateIdentifier("ret")
|
||||
g.p(`%v := %v.ctrl.Call(%v, %q%v)`, idRet, idRecv, idRecv, m.Name, callArgs)
|
||||
|
||||
// Go does not allow "naked" type assertions on nil values, so we use the two-value form here.
|
||||
// The value of that is either (x.(T), true) or (Z, false), where Z is the zero value for T.
|
||||
// Happily, this coincides with the semantics we want here.
|
||||
retNames := make([]string, len(rets))
|
||||
for i, t := range rets {
|
||||
retNames[i] = ia.allocateIdentifier(fmt.Sprintf("ret%d", i))
|
||||
g.p("%s, _ := %s[%d].(%s)", retNames[i], idRet, i, t)
|
||||
}
|
||||
g.p("return " + strings.Join(retNames, ", "))
|
||||
}
|
||||
|
||||
g.out()
|
||||
g.p("}")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *generator) GenerateMockRecorderMethod(intf *model.Interface, m *model.Method, shortTp string, typed bool) error {
|
||||
mockType := g.mockName(intf.Name)
|
||||
argNames := g.getArgNames(m, true)
|
||||
|
||||
var argString string
|
||||
if m.Variadic == nil {
|
||||
argString = strings.Join(argNames, ", ")
|
||||
} else {
|
||||
argString = strings.Join(argNames[:len(argNames)-1], ", ")
|
||||
}
|
||||
if argString != "" {
|
||||
argString += " any"
|
||||
}
|
||||
|
||||
if m.Variadic != nil {
|
||||
if argString != "" {
|
||||
argString += ", "
|
||||
}
|
||||
argString += fmt.Sprintf("%s ...any", argNames[len(argNames)-1])
|
||||
}
|
||||
|
||||
ia := newIdentifierAllocator(argNames)
|
||||
idRecv := ia.allocateIdentifier("mr")
|
||||
|
||||
g.p("// %v indicates an expected call of %v.", m.Name, m.Name)
|
||||
if typed {
|
||||
g.p("func (%s *%vMockRecorder%v) %v(%v) *%s%sCall%s {", idRecv, mockType, shortTp, m.Name, argString, mockType, m.Name, shortTp)
|
||||
} else {
|
||||
g.p("func (%s *%vMockRecorder%v) %v(%v) *gomock.Call {", idRecv, mockType, shortTp, m.Name, argString)
|
||||
}
|
||||
|
||||
g.in()
|
||||
g.p("%s.mock.ctrl.T.Helper()", idRecv)
|
||||
|
||||
var callArgs string
|
||||
if m.Variadic == nil {
|
||||
if len(argNames) > 0 {
|
||||
callArgs = ", " + strings.Join(argNames, ", ")
|
||||
}
|
||||
} else {
|
||||
if len(argNames) == 1 {
|
||||
// Easy: just use ... to push the arguments through.
|
||||
callArgs = ", " + argNames[0] + "..."
|
||||
} else {
|
||||
// Hard: create a temporary slice.
|
||||
idVarArgs := ia.allocateIdentifier("varargs")
|
||||
g.p("%s := append([]any{%s}, %s...)",
|
||||
idVarArgs,
|
||||
strings.Join(argNames[:len(argNames)-1], ", "),
|
||||
argNames[len(argNames)-1])
|
||||
callArgs = ", " + idVarArgs + "..."
|
||||
}
|
||||
}
|
||||
if typed {
|
||||
g.p(`call := %s.mock.ctrl.RecordCallWithMethodType(%s.mock, "%s", reflect.TypeOf((*%s%s)(nil).%s)%s)`, idRecv, idRecv, m.Name, mockType, shortTp, m.Name, callArgs)
|
||||
g.p(`return &%s%sCall%s{Call: call}`, mockType, m.Name, shortTp)
|
||||
} else {
|
||||
g.p(`return %s.mock.ctrl.RecordCallWithMethodType(%s.mock, "%s", reflect.TypeOf((*%s%s)(nil).%s)%s)`, idRecv, idRecv, m.Name, mockType, shortTp, m.Name, callArgs)
|
||||
}
|
||||
|
||||
g.out()
|
||||
g.p("}")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *generator) GenerateMockReturnCallMethod(intf *model.Interface, m *model.Method, pkgOverride, longTp, shortTp string) error {
|
||||
mockType := g.mockName(intf.Name)
|
||||
argNames := g.getArgNames(m, true /* in */)
|
||||
retNames := g.getArgNames(m, false /* out */)
|
||||
argTypes := g.getArgTypes(m, pkgOverride, true /* in */)
|
||||
retTypes := g.getArgTypes(m, pkgOverride, false /* out */)
|
||||
argString := strings.Join(argTypes, ", ")
|
||||
|
||||
rets := make([]string, len(m.Out))
|
||||
for i, p := range m.Out {
|
||||
rets[i] = p.Type.String(g.packageMap, pkgOverride)
|
||||
}
|
||||
|
||||
var retString string
|
||||
switch {
|
||||
case len(rets) == 1:
|
||||
retString = " " + rets[0]
|
||||
case len(rets) > 1:
|
||||
retString = " (" + strings.Join(rets, ", ") + ")"
|
||||
}
|
||||
|
||||
ia := newIdentifierAllocator(argNames)
|
||||
idRecv := ia.allocateIdentifier("c")
|
||||
|
||||
recvStructName := mockType + m.Name
|
||||
|
||||
g.p("// %s%sCall wrap *gomock.Call", mockType, m.Name)
|
||||
g.p("type %s%sCall%s struct{", mockType, m.Name, longTp)
|
||||
g.in()
|
||||
g.p("*gomock.Call")
|
||||
g.out()
|
||||
g.p("}")
|
||||
|
||||
g.p("// Return rewrite *gomock.Call.Return")
|
||||
g.p("func (%s *%sCall%s) Return(%v) *%sCall%s {", idRecv, recvStructName, shortTp, makeArgString(retNames, retTypes), recvStructName, shortTp)
|
||||
g.in()
|
||||
var retArgs string
|
||||
if len(retNames) > 0 {
|
||||
retArgs = strings.Join(retNames, ", ")
|
||||
}
|
||||
g.p(`%s.Call = %v.Call.Return(%v)`, idRecv, idRecv, retArgs)
|
||||
g.p("return %s", idRecv)
|
||||
g.out()
|
||||
g.p("}")
|
||||
|
||||
g.p("// Do rewrite *gomock.Call.Do")
|
||||
g.p("func (%s *%sCall%s) Do(f func(%v)%v) *%sCall%s {", idRecv, recvStructName, shortTp, argString, retString, recvStructName, shortTp)
|
||||
g.in()
|
||||
g.p(`%s.Call = %v.Call.Do(f)`, idRecv, idRecv)
|
||||
g.p("return %s", idRecv)
|
||||
g.out()
|
||||
g.p("}")
|
||||
|
||||
g.p("// DoAndReturn rewrite *gomock.Call.DoAndReturn")
|
||||
g.p("func (%s *%sCall%s) DoAndReturn(f func(%v)%v) *%sCall%s {", idRecv, recvStructName, shortTp, argString, retString, recvStructName, shortTp)
|
||||
g.in()
|
||||
g.p(`%s.Call = %v.Call.DoAndReturn(f)`, idRecv, idRecv)
|
||||
g.p("return %s", idRecv)
|
||||
g.out()
|
||||
g.p("}")
|
||||
return nil
|
||||
}
|
||||
|
||||
// nameExistsAsPackage returns true if the name exists as a package name.
|
||||
// This is used to avoid name collisions when generating mock method arguments.
|
||||
func (g *generator) nameExistsAsPackage(name string) bool {
|
||||
for _, symbolName := range g.packageMap {
|
||||
if symbolName == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *generator) getArgNames(m *model.Method, in bool) []string {
|
||||
var params []*model.Parameter
|
||||
if in {
|
||||
params = m.In
|
||||
} else {
|
||||
params = m.Out
|
||||
}
|
||||
argNames := make([]string, len(params))
|
||||
|
||||
for i, p := range params {
|
||||
name := p.Name
|
||||
|
||||
if name == "" || name == "_" || g.nameExistsAsPackage(name) {
|
||||
name = fmt.Sprintf("arg%d", i)
|
||||
}
|
||||
argNames[i] = name
|
||||
}
|
||||
if m.Variadic != nil && in {
|
||||
name := m.Variadic.Name
|
||||
|
||||
if name == "" || g.nameExistsAsPackage(name) {
|
||||
name = fmt.Sprintf("arg%d", len(params))
|
||||
}
|
||||
argNames = append(argNames, name)
|
||||
}
|
||||
return argNames
|
||||
}
|
||||
|
||||
func (g *generator) getArgTypes(m *model.Method, pkgOverride string, in bool) []string {
|
||||
var params []*model.Parameter
|
||||
if in {
|
||||
params = m.In
|
||||
} else {
|
||||
params = m.Out
|
||||
}
|
||||
argTypes := make([]string, len(params))
|
||||
for i, p := range params {
|
||||
argTypes[i] = p.Type.String(g.packageMap, pkgOverride)
|
||||
}
|
||||
if m.Variadic != nil {
|
||||
argTypes = append(argTypes, "..."+m.Variadic.Type.String(g.packageMap, pkgOverride))
|
||||
}
|
||||
return argTypes
|
||||
}
|
||||
|
||||
type identifierAllocator map[string]struct{}
|
||||
|
||||
func newIdentifierAllocator(taken []string) identifierAllocator {
|
||||
a := make(identifierAllocator, len(taken))
|
||||
for _, s := range taken {
|
||||
a[s] = struct{}{}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (o identifierAllocator) allocateIdentifier(want string) string {
|
||||
id := want
|
||||
for i := 2; ; i++ {
|
||||
if _, ok := o[id]; !ok {
|
||||
o[id] = struct{}{}
|
||||
return id
|
||||
}
|
||||
id = want + "_" + strconv.Itoa(i)
|
||||
}
|
||||
}
|
||||
|
||||
// Output returns the generator's output, formatted in the standard Go style.
|
||||
func (g *generator) Output() []byte {
|
||||
src, err := toolsimports.Process(g.destination, g.buf.Bytes(), nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to format generated source code: %s\n%s", err, g.buf.String())
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
// createPackageMap returns a map of import path to package name
|
||||
// for specified importPaths.
|
||||
func createPackageMap(importPaths []string) map[string]string {
|
||||
var pkg struct {
|
||||
Name string
|
||||
ImportPath string
|
||||
}
|
||||
pkgMap := make(map[string]string)
|
||||
b := bytes.NewBuffer(nil)
|
||||
args := []string{"list", "-json=ImportPath,Name"}
|
||||
args = append(args, importPaths...)
|
||||
cmd := exec.Command("go", args...)
|
||||
cmd.Stdout = b
|
||||
cmd.Run()
|
||||
dec := json.NewDecoder(b)
|
||||
for dec.More() {
|
||||
err := dec.Decode(&pkg)
|
||||
if err != nil {
|
||||
log.Printf("failed to decode 'go list' output: %v", err)
|
||||
continue
|
||||
}
|
||||
pkgMap[pkg.ImportPath] = pkg.Name
|
||||
}
|
||||
return pkgMap
|
||||
}
|
||||
|
||||
func printVersion() {
|
||||
if version != "" {
|
||||
fmt.Printf("v%s\nCommit: %s\nDate: %s\n", version, commit, date)
|
||||
} else {
|
||||
printModuleVersion()
|
||||
}
|
||||
}
|
||||
|
||||
// parseImportPackage get package import path via source file
|
||||
// an alternative implementation is to use:
|
||||
// cfg := &packages.Config{Mode: packages.NeedName, Tests: true, Dir: srcDir}
|
||||
// pkgs, err := packages.Load(cfg, "file="+source)
|
||||
// However, it will call "go list" and slow down the performance
|
||||
func parsePackageImport(srcDir string) (string, error) {
|
||||
moduleMode := os.Getenv("GO111MODULE")
|
||||
// trying to find the module
|
||||
if moduleMode != "off" {
|
||||
currentDir := srcDir
|
||||
for {
|
||||
dat, err := os.ReadFile(filepath.Join(currentDir, "go.mod"))
|
||||
if os.IsNotExist(err) {
|
||||
if currentDir == filepath.Dir(currentDir) {
|
||||
// at the root
|
||||
break
|
||||
}
|
||||
currentDir = filepath.Dir(currentDir)
|
||||
continue
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
modulePath := modfile.ModulePath(dat)
|
||||
return filepath.ToSlash(filepath.Join(modulePath, strings.TrimPrefix(srcDir, currentDir))), nil
|
||||
}
|
||||
}
|
||||
// fall back to GOPATH mode
|
||||
goPaths := os.Getenv("GOPATH")
|
||||
if goPaths == "" {
|
||||
return "", fmt.Errorf("GOPATH is not set")
|
||||
}
|
||||
goPathList := strings.Split(goPaths, string(os.PathListSeparator))
|
||||
for _, goPath := range goPathList {
|
||||
sourceRoot := filepath.Join(goPath, "src") + string(os.PathSeparator)
|
||||
if strings.HasPrefix(srcDir, sourceRoot) {
|
||||
return filepath.ToSlash(strings.TrimPrefix(srcDir, sourceRoot)), nil
|
||||
}
|
||||
}
|
||||
return "", errOutsideGoPath
|
||||
}
|
||||
-533
@@ -1,533 +0,0 @@
|
||||
// Copyright 2012 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package model contains the data model necessary for generating mock implementations.
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// pkgPath is the importable path for package model
|
||||
const pkgPath = "go.uber.org/mock/mockgen/model"
|
||||
|
||||
// Package is a Go package. It may be a subset.
|
||||
type Package struct {
|
||||
Name string
|
||||
PkgPath string
|
||||
Interfaces []*Interface
|
||||
DotImports []string
|
||||
}
|
||||
|
||||
// Print writes the package name and its exported interfaces.
|
||||
func (pkg *Package) Print(w io.Writer) {
|
||||
_, _ = fmt.Fprintf(w, "package %s\n", pkg.Name)
|
||||
for _, intf := range pkg.Interfaces {
|
||||
intf.Print(w)
|
||||
}
|
||||
}
|
||||
|
||||
// Imports returns the imports needed by the Package as a set of import paths.
|
||||
func (pkg *Package) Imports() map[string]bool {
|
||||
im := make(map[string]bool)
|
||||
for _, intf := range pkg.Interfaces {
|
||||
intf.addImports(im)
|
||||
for _, tp := range intf.TypeParams {
|
||||
tp.Type.addImports(im)
|
||||
}
|
||||
}
|
||||
return im
|
||||
}
|
||||
|
||||
// Interface is a Go interface.
|
||||
type Interface struct {
|
||||
Name string
|
||||
Methods []*Method
|
||||
TypeParams []*Parameter
|
||||
}
|
||||
|
||||
// Print writes the interface name and its methods.
|
||||
func (intf *Interface) Print(w io.Writer) {
|
||||
_, _ = fmt.Fprintf(w, "interface %s\n", intf.Name)
|
||||
for _, m := range intf.Methods {
|
||||
m.Print(w)
|
||||
}
|
||||
}
|
||||
|
||||
func (intf *Interface) addImports(im map[string]bool) {
|
||||
for _, m := range intf.Methods {
|
||||
m.addImports(im)
|
||||
}
|
||||
}
|
||||
|
||||
// AddMethod adds a new method, de-duplicating by method name.
|
||||
func (intf *Interface) AddMethod(m *Method) {
|
||||
for _, me := range intf.Methods {
|
||||
if me.Name == m.Name {
|
||||
return
|
||||
}
|
||||
}
|
||||
intf.Methods = append(intf.Methods, m)
|
||||
}
|
||||
|
||||
// Method is a single method of an interface.
|
||||
type Method struct {
|
||||
Name string
|
||||
In, Out []*Parameter
|
||||
Variadic *Parameter // may be nil
|
||||
}
|
||||
|
||||
// Print writes the method name and its signature.
|
||||
func (m *Method) Print(w io.Writer) {
|
||||
_, _ = fmt.Fprintf(w, " - method %s\n", m.Name)
|
||||
if len(m.In) > 0 {
|
||||
_, _ = fmt.Fprintf(w, " in:\n")
|
||||
for _, p := range m.In {
|
||||
p.Print(w)
|
||||
}
|
||||
}
|
||||
if m.Variadic != nil {
|
||||
_, _ = fmt.Fprintf(w, " ...:\n")
|
||||
m.Variadic.Print(w)
|
||||
}
|
||||
if len(m.Out) > 0 {
|
||||
_, _ = fmt.Fprintf(w, " out:\n")
|
||||
for _, p := range m.Out {
|
||||
p.Print(w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Method) addImports(im map[string]bool) {
|
||||
for _, p := range m.In {
|
||||
p.Type.addImports(im)
|
||||
}
|
||||
if m.Variadic != nil {
|
||||
m.Variadic.Type.addImports(im)
|
||||
}
|
||||
for _, p := range m.Out {
|
||||
p.Type.addImports(im)
|
||||
}
|
||||
}
|
||||
|
||||
// Parameter is an argument or return parameter of a method.
|
||||
type Parameter struct {
|
||||
Name string // may be empty
|
||||
Type Type
|
||||
}
|
||||
|
||||
// Print writes a method parameter.
|
||||
func (p *Parameter) Print(w io.Writer) {
|
||||
n := p.Name
|
||||
if n == "" {
|
||||
n = `""`
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, " - %v: %v\n", n, p.Type.String(nil, ""))
|
||||
}
|
||||
|
||||
// Type is a Go type.
|
||||
type Type interface {
|
||||
String(pm map[string]string, pkgOverride string) string
|
||||
addImports(im map[string]bool)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Call gob.RegisterName with pkgPath as prefix to avoid conflicting with
|
||||
// github.com/golang/mock/mockgen/model 's registration.
|
||||
gob.RegisterName(pkgPath+".ArrayType", &ArrayType{})
|
||||
gob.RegisterName(pkgPath+".ChanType", &ChanType{})
|
||||
gob.RegisterName(pkgPath+".FuncType", &FuncType{})
|
||||
gob.RegisterName(pkgPath+".MapType", &MapType{})
|
||||
gob.RegisterName(pkgPath+".NamedType", &NamedType{})
|
||||
gob.RegisterName(pkgPath+".PointerType", &PointerType{})
|
||||
|
||||
// Call gob.RegisterName to make sure it has the consistent name registered
|
||||
// for both gob decoder and encoder.
|
||||
//
|
||||
// For a non-pointer type, gob.Register will try to get package full path by
|
||||
// calling rt.PkgPath() for a name to register. If your project has vendor
|
||||
// directory, it is possible that PkgPath will get a path like this:
|
||||
// ../../../vendor/go.uber.org/mock/mockgen/model
|
||||
gob.RegisterName(pkgPath+".PredeclaredType", PredeclaredType(""))
|
||||
}
|
||||
|
||||
// ArrayType is an array or slice type.
|
||||
type ArrayType struct {
|
||||
Len int // -1 for slices, >= 0 for arrays
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (at *ArrayType) String(pm map[string]string, pkgOverride string) string {
|
||||
s := "[]"
|
||||
if at.Len > -1 {
|
||||
s = fmt.Sprintf("[%d]", at.Len)
|
||||
}
|
||||
return s + at.Type.String(pm, pkgOverride)
|
||||
}
|
||||
|
||||
func (at *ArrayType) addImports(im map[string]bool) { at.Type.addImports(im) }
|
||||
|
||||
// ChanType is a channel type.
|
||||
type ChanType struct {
|
||||
Dir ChanDir // 0, 1 or 2
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (ct *ChanType) String(pm map[string]string, pkgOverride string) string {
|
||||
s := ct.Type.String(pm, pkgOverride)
|
||||
if ct.Dir == RecvDir {
|
||||
return "<-chan " + s
|
||||
}
|
||||
if ct.Dir == SendDir {
|
||||
return "chan<- " + s
|
||||
}
|
||||
return "chan " + s
|
||||
}
|
||||
|
||||
func (ct *ChanType) addImports(im map[string]bool) { ct.Type.addImports(im) }
|
||||
|
||||
// ChanDir is a channel direction.
|
||||
type ChanDir int
|
||||
|
||||
// Constants for channel directions.
|
||||
const (
|
||||
RecvDir ChanDir = 1
|
||||
SendDir ChanDir = 2
|
||||
)
|
||||
|
||||
// FuncType is a function type.
|
||||
type FuncType struct {
|
||||
In, Out []*Parameter
|
||||
Variadic *Parameter // may be nil
|
||||
}
|
||||
|
||||
func (ft *FuncType) String(pm map[string]string, pkgOverride string) string {
|
||||
args := make([]string, len(ft.In))
|
||||
for i, p := range ft.In {
|
||||
args[i] = p.Type.String(pm, pkgOverride)
|
||||
}
|
||||
if ft.Variadic != nil {
|
||||
args = append(args, "..."+ft.Variadic.Type.String(pm, pkgOverride))
|
||||
}
|
||||
rets := make([]string, len(ft.Out))
|
||||
for i, p := range ft.Out {
|
||||
rets[i] = p.Type.String(pm, pkgOverride)
|
||||
}
|
||||
retString := strings.Join(rets, ", ")
|
||||
if nOut := len(ft.Out); nOut == 1 {
|
||||
retString = " " + retString
|
||||
} else if nOut > 1 {
|
||||
retString = " (" + retString + ")"
|
||||
}
|
||||
return "func(" + strings.Join(args, ", ") + ")" + retString
|
||||
}
|
||||
|
||||
func (ft *FuncType) addImports(im map[string]bool) {
|
||||
for _, p := range ft.In {
|
||||
p.Type.addImports(im)
|
||||
}
|
||||
if ft.Variadic != nil {
|
||||
ft.Variadic.Type.addImports(im)
|
||||
}
|
||||
for _, p := range ft.Out {
|
||||
p.Type.addImports(im)
|
||||
}
|
||||
}
|
||||
|
||||
// MapType is a map type.
|
||||
type MapType struct {
|
||||
Key, Value Type
|
||||
}
|
||||
|
||||
func (mt *MapType) String(pm map[string]string, pkgOverride string) string {
|
||||
return "map[" + mt.Key.String(pm, pkgOverride) + "]" + mt.Value.String(pm, pkgOverride)
|
||||
}
|
||||
|
||||
func (mt *MapType) addImports(im map[string]bool) {
|
||||
mt.Key.addImports(im)
|
||||
mt.Value.addImports(im)
|
||||
}
|
||||
|
||||
// NamedType is an exported type in a package.
|
||||
type NamedType struct {
|
||||
Package string // may be empty
|
||||
Type string
|
||||
TypeParams *TypeParametersType
|
||||
}
|
||||
|
||||
func (nt *NamedType) String(pm map[string]string, pkgOverride string) string {
|
||||
if pkgOverride == nt.Package {
|
||||
return nt.Type + nt.TypeParams.String(pm, pkgOverride)
|
||||
}
|
||||
prefix := pm[nt.Package]
|
||||
if prefix != "" {
|
||||
return prefix + "." + nt.Type + nt.TypeParams.String(pm, pkgOverride)
|
||||
}
|
||||
|
||||
return nt.Type + nt.TypeParams.String(pm, pkgOverride)
|
||||
}
|
||||
|
||||
func (nt *NamedType) addImports(im map[string]bool) {
|
||||
if nt.Package != "" {
|
||||
im[nt.Package] = true
|
||||
}
|
||||
nt.TypeParams.addImports(im)
|
||||
}
|
||||
|
||||
// PointerType is a pointer to another type.
|
||||
type PointerType struct {
|
||||
Type Type
|
||||
}
|
||||
|
||||
func (pt *PointerType) String(pm map[string]string, pkgOverride string) string {
|
||||
return "*" + pt.Type.String(pm, pkgOverride)
|
||||
}
|
||||
func (pt *PointerType) addImports(im map[string]bool) { pt.Type.addImports(im) }
|
||||
|
||||
// PredeclaredType is a predeclared type such as "int".
|
||||
type PredeclaredType string
|
||||
|
||||
func (pt PredeclaredType) String(map[string]string, string) string { return string(pt) }
|
||||
func (pt PredeclaredType) addImports(map[string]bool) {}
|
||||
|
||||
// TypeParametersType contains type parameters for a NamedType.
|
||||
type TypeParametersType struct {
|
||||
TypeParameters []Type
|
||||
}
|
||||
|
||||
func (tp *TypeParametersType) String(pm map[string]string, pkgOverride string) string {
|
||||
if tp == nil || len(tp.TypeParameters) == 0 {
|
||||
return ""
|
||||
}
|
||||
var sb strings.Builder
|
||||
sb.WriteString("[")
|
||||
for i, v := range tp.TypeParameters {
|
||||
if i != 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(v.String(pm, pkgOverride))
|
||||
}
|
||||
sb.WriteString("]")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (tp *TypeParametersType) addImports(im map[string]bool) {
|
||||
if tp == nil {
|
||||
return
|
||||
}
|
||||
for _, v := range tp.TypeParameters {
|
||||
v.addImports(im)
|
||||
}
|
||||
}
|
||||
|
||||
// The following code is intended to be called by the program generated by ../reflect.go.
|
||||
|
||||
// InterfaceFromInterfaceType returns a pointer to an interface for the
|
||||
// given reflection interface type.
|
||||
func InterfaceFromInterfaceType(it reflect.Type) (*Interface, error) {
|
||||
if it.Kind() != reflect.Interface {
|
||||
return nil, fmt.Errorf("%v is not an interface", it)
|
||||
}
|
||||
intf := &Interface{}
|
||||
|
||||
for i := 0; i < it.NumMethod(); i++ {
|
||||
mt := it.Method(i)
|
||||
// TODO: need to skip unexported methods? or just raise an error?
|
||||
m := &Method{
|
||||
Name: mt.Name,
|
||||
}
|
||||
|
||||
var err error
|
||||
m.In, m.Variadic, m.Out, err = funcArgsFromType(mt.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
intf.AddMethod(m)
|
||||
}
|
||||
|
||||
return intf, nil
|
||||
}
|
||||
|
||||
// t's Kind must be a reflect.Func.
|
||||
func funcArgsFromType(t reflect.Type) (in []*Parameter, variadic *Parameter, out []*Parameter, err error) {
|
||||
nin := t.NumIn()
|
||||
if t.IsVariadic() {
|
||||
nin--
|
||||
}
|
||||
var p *Parameter
|
||||
for i := 0; i < nin; i++ {
|
||||
p, err = parameterFromType(t.In(i))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
in = append(in, p)
|
||||
}
|
||||
if t.IsVariadic() {
|
||||
p, err = parameterFromType(t.In(nin).Elem())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
variadic = p
|
||||
}
|
||||
for i := 0; i < t.NumOut(); i++ {
|
||||
p, err = parameterFromType(t.Out(i))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parameterFromType(t reflect.Type) (*Parameter, error) {
|
||||
tt, err := typeFromType(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Parameter{Type: tt}, nil
|
||||
}
|
||||
|
||||
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
var byteType = reflect.TypeOf(byte(0))
|
||||
|
||||
func typeFromType(t reflect.Type) (Type, error) {
|
||||
// Hack workaround for https://golang.org/issue/3853.
|
||||
// This explicit check should not be necessary.
|
||||
if t == byteType {
|
||||
return PredeclaredType("byte"), nil
|
||||
}
|
||||
|
||||
if imp := t.PkgPath(); imp != "" {
|
||||
return &NamedType{
|
||||
Package: impPath(imp),
|
||||
Type: t.Name(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// only unnamed or predeclared types after here
|
||||
|
||||
// Lots of types have element types. Let's do the parsing and error checking for all of them.
|
||||
var elemType Type
|
||||
switch t.Kind() {
|
||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
var err error
|
||||
elemType, err = typeFromType(t.Elem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Array:
|
||||
return &ArrayType{
|
||||
Len: t.Len(),
|
||||
Type: elemType,
|
||||
}, nil
|
||||
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||
reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.String:
|
||||
return PredeclaredType(t.Kind().String()), nil
|
||||
case reflect.Chan:
|
||||
var dir ChanDir
|
||||
switch t.ChanDir() {
|
||||
case reflect.RecvDir:
|
||||
dir = RecvDir
|
||||
case reflect.SendDir:
|
||||
dir = SendDir
|
||||
}
|
||||
return &ChanType{
|
||||
Dir: dir,
|
||||
Type: elemType,
|
||||
}, nil
|
||||
case reflect.Func:
|
||||
in, variadic, out, err := funcArgsFromType(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &FuncType{
|
||||
In: in,
|
||||
Out: out,
|
||||
Variadic: variadic,
|
||||
}, nil
|
||||
case reflect.Interface:
|
||||
// Two special interfaces.
|
||||
if t.NumMethod() == 0 {
|
||||
return PredeclaredType("any"), nil
|
||||
}
|
||||
if t == errorType {
|
||||
return PredeclaredType("error"), nil
|
||||
}
|
||||
case reflect.Map:
|
||||
kt, err := typeFromType(t.Key())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &MapType{
|
||||
Key: kt,
|
||||
Value: elemType,
|
||||
}, nil
|
||||
case reflect.Ptr:
|
||||
return &PointerType{
|
||||
Type: elemType,
|
||||
}, nil
|
||||
case reflect.Slice:
|
||||
return &ArrayType{
|
||||
Len: -1,
|
||||
Type: elemType,
|
||||
}, nil
|
||||
case reflect.Struct:
|
||||
if t.NumField() == 0 {
|
||||
return PredeclaredType("struct{}"), nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Struct, UnsafePointer
|
||||
return nil, fmt.Errorf("can't yet turn %v (%v) into a model.Type", t, t.Kind())
|
||||
}
|
||||
|
||||
// impPath sanitizes the package path returned by `PkgPath` method of a reflect Type so that
|
||||
// it is importable. PkgPath might return a path that includes "vendor". These paths do not
|
||||
// compile, so we need to remove everything up to and including "/vendor/".
|
||||
// See https://github.com/golang/go/issues/12019.
|
||||
func impPath(imp string) string {
|
||||
if strings.HasPrefix(imp, "vendor/") {
|
||||
imp = "/" + imp
|
||||
}
|
||||
if i := strings.LastIndex(imp, "/vendor/"); i != -1 {
|
||||
imp = imp[i+len("/vendor/"):]
|
||||
}
|
||||
return imp
|
||||
}
|
||||
|
||||
// ErrorInterface represent built-in error interface.
|
||||
var ErrorInterface = Interface{
|
||||
Name: "error",
|
||||
Methods: []*Method{
|
||||
{
|
||||
Name: "Error",
|
||||
Out: []*Parameter{
|
||||
{
|
||||
Name: "",
|
||||
Type: PredeclaredType("string"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
-160
@@ -1,160 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// InterfaceFromGoTypesType returns a pointer to an interface for the
|
||||
// given interface type loaded from archive.
|
||||
func InterfaceFromGoTypesType(it *types.Interface) (*Interface, error) {
|
||||
intf := &Interface{}
|
||||
|
||||
for i := 0; i < it.NumMethods(); i++ {
|
||||
mt := it.Method(i)
|
||||
// Skip unexported methods.
|
||||
if !mt.Exported() {
|
||||
continue
|
||||
}
|
||||
m := &Method{
|
||||
Name: mt.Name(),
|
||||
}
|
||||
|
||||
var err error
|
||||
m.In, m.Variadic, m.Out, err = funcArgsFromGoTypesType(mt.Type().(*types.Signature))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("method %q: %w", mt.Name(), err)
|
||||
}
|
||||
|
||||
intf.AddMethod(m)
|
||||
}
|
||||
|
||||
return intf, nil
|
||||
}
|
||||
|
||||
func funcArgsFromGoTypesType(t *types.Signature) (in []*Parameter, variadic *Parameter, out []*Parameter, err error) {
|
||||
nin := t.Params().Len()
|
||||
if t.Variadic() {
|
||||
nin--
|
||||
}
|
||||
for i := 0; i < nin; i++ {
|
||||
p, err := parameterFromGoTypesType(t.Params().At(i), false)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
in = append(in, p)
|
||||
}
|
||||
if t.Variadic() {
|
||||
p, err := parameterFromGoTypesType(t.Params().At(nin), true)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
variadic = p
|
||||
}
|
||||
for i := 0; i < t.Results().Len(); i++ {
|
||||
p, err := parameterFromGoTypesType(t.Results().At(i), false)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parameterFromGoTypesType(v *types.Var, variadic bool) (*Parameter, error) {
|
||||
t := v.Type()
|
||||
if variadic {
|
||||
t = t.(*types.Slice).Elem()
|
||||
}
|
||||
tt, err := typeFromGoTypesType(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Parameter{Name: v.Name(), Type: tt}, nil
|
||||
}
|
||||
|
||||
func typeFromGoTypesType(t types.Type) (Type, error) {
|
||||
if t, ok := t.(*types.Named); ok {
|
||||
tn := t.Obj()
|
||||
if tn.Pkg() == nil {
|
||||
return PredeclaredType(tn.Name()), nil
|
||||
}
|
||||
return &NamedType{
|
||||
Package: tn.Pkg().Path(),
|
||||
Type: tn.Name(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// only unnamed or predeclared types after here
|
||||
|
||||
// Lots of types have element types. Let's do the parsing and error checking for all of them.
|
||||
var elemType Type
|
||||
if t, ok := t.(interface{ Elem() types.Type }); ok {
|
||||
var err error
|
||||
elemType, err = typeFromGoTypesType(t.Elem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
switch t := t.(type) {
|
||||
case *types.Array:
|
||||
return &ArrayType{
|
||||
Len: int(t.Len()),
|
||||
Type: elemType,
|
||||
}, nil
|
||||
case *types.Basic:
|
||||
return PredeclaredType(t.String()), nil
|
||||
case *types.Chan:
|
||||
var dir ChanDir
|
||||
switch t.Dir() {
|
||||
case types.RecvOnly:
|
||||
dir = RecvDir
|
||||
case types.SendOnly:
|
||||
dir = SendDir
|
||||
}
|
||||
return &ChanType{
|
||||
Dir: dir,
|
||||
Type: elemType,
|
||||
}, nil
|
||||
case *types.Signature:
|
||||
in, variadic, out, err := funcArgsFromGoTypesType(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &FuncType{
|
||||
In: in,
|
||||
Out: out,
|
||||
Variadic: variadic,
|
||||
}, nil
|
||||
case *types.Interface:
|
||||
if t.NumMethods() == 0 {
|
||||
return PredeclaredType("interface{}"), nil
|
||||
}
|
||||
case *types.Map:
|
||||
kt, err := typeFromGoTypesType(t.Key())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &MapType{
|
||||
Key: kt,
|
||||
Value: elemType,
|
||||
}, nil
|
||||
case *types.Pointer:
|
||||
return &PointerType{
|
||||
Type: elemType,
|
||||
}, nil
|
||||
case *types.Slice:
|
||||
return &ArrayType{
|
||||
Len: -1,
|
||||
Type: elemType,
|
||||
}, nil
|
||||
case *types.Struct:
|
||||
if t.NumFields() == 0 {
|
||||
return PredeclaredType("struct{}"), nil
|
||||
}
|
||||
// TODO: UnsafePointer
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("can't yet turn %v (%T) into a model.Type", t.String(), t)
|
||||
}
|
||||
-359
@@ -1,359 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/mock/mockgen/model"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
var (
|
||||
buildFlags = flag.String("build_flags", "", "(package mode) Additional flags for go build.")
|
||||
)
|
||||
|
||||
type packageModeParser struct {
|
||||
pkgName string
|
||||
}
|
||||
|
||||
func (p *packageModeParser) parsePackage(packageName string, ifaces []string) (*model.Package, error) {
|
||||
p.pkgName = packageName
|
||||
|
||||
pkg, err := p.loadPackage(packageName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load package: %w", err)
|
||||
}
|
||||
|
||||
interfaces, err := p.extractInterfacesFromPackage(pkg, ifaces)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("extract interfaces from package: %w", err)
|
||||
}
|
||||
|
||||
return &model.Package{
|
||||
Name: pkg.Types.Name(),
|
||||
PkgPath: packageName,
|
||||
Interfaces: interfaces,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *packageModeParser) loadPackage(packageName string) (*packages.Package, error) {
|
||||
var buildFlagsSet []string
|
||||
if *buildFlags != "" {
|
||||
buildFlagsSet = strings.Split(*buildFlags, " ")
|
||||
}
|
||||
|
||||
cfg := &packages.Config{
|
||||
Mode: packages.NeedDeps | packages.NeedImports | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedEmbedFiles | packages.LoadSyntax,
|
||||
BuildFlags: buildFlagsSet,
|
||||
}
|
||||
pkgs, err := packages.Load(cfg, packageName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load packages: %w", err)
|
||||
}
|
||||
|
||||
if len(pkgs) != 1 {
|
||||
return nil, fmt.Errorf("packages length must be 1: %d", len(pkgs))
|
||||
}
|
||||
|
||||
if len(pkgs[0].Errors) > 0 {
|
||||
errs := make([]error, len(pkgs[0].Errors))
|
||||
for i, err := range pkgs[0].Errors {
|
||||
errs[i] = err
|
||||
}
|
||||
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
|
||||
return pkgs[0], nil
|
||||
}
|
||||
|
||||
func (p *packageModeParser) extractInterfacesFromPackage(pkg *packages.Package, ifaces []string) ([]*model.Interface, error) {
|
||||
interfaces := make([]*model.Interface, len(ifaces))
|
||||
for i, iface := range ifaces {
|
||||
obj := pkg.Types.Scope().Lookup(iface)
|
||||
if obj == nil {
|
||||
return nil, fmt.Errorf("interface %s does not exist", iface)
|
||||
}
|
||||
|
||||
modelIface, err := p.parseInterface(obj)
|
||||
if err != nil {
|
||||
return nil, newParseTypeError("parse interface", obj.Name(), err)
|
||||
}
|
||||
|
||||
interfaces[i] = modelIface
|
||||
}
|
||||
|
||||
return interfaces, nil
|
||||
}
|
||||
|
||||
func (p *packageModeParser) parseInterface(obj types.Object) (*model.Interface, error) {
|
||||
named, ok := types.Unalias(obj.Type()).(*types.Named)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not an interface. it is a %T", obj.Name(), obj.Type().Underlying())
|
||||
}
|
||||
|
||||
iface, ok := named.Underlying().(*types.Interface)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not an interface. it is a %T", obj.Name(), obj.Type().Underlying())
|
||||
}
|
||||
|
||||
if p.isConstraint(iface) {
|
||||
return nil, fmt.Errorf("interface %s is a constraint", obj.Name())
|
||||
}
|
||||
|
||||
methods := make([]*model.Method, iface.NumMethods())
|
||||
for i := range iface.NumMethods() {
|
||||
method := iface.Method(i)
|
||||
typedMethod, ok := method.Type().(*types.Signature)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("method %s is not a signature", method.Name())
|
||||
}
|
||||
|
||||
modelFunc, err := p.parseFunc(typedMethod)
|
||||
if err != nil {
|
||||
return nil, newParseTypeError("parse method", typedMethod.String(), err)
|
||||
}
|
||||
|
||||
methods[i] = &model.Method{
|
||||
Name: method.Name(),
|
||||
In: modelFunc.In,
|
||||
Out: modelFunc.Out,
|
||||
Variadic: modelFunc.Variadic,
|
||||
}
|
||||
}
|
||||
|
||||
if named.TypeParams() == nil {
|
||||
return &model.Interface{Name: obj.Name(), Methods: methods}, nil
|
||||
}
|
||||
|
||||
typeParams := make([]*model.Parameter, named.TypeParams().Len())
|
||||
for i := range named.TypeParams().Len() {
|
||||
param := named.TypeParams().At(i)
|
||||
typeParam, err := p.parseConstraint(param)
|
||||
if err != nil {
|
||||
return nil, newParseTypeError("parse type parameter", param.String(), err)
|
||||
}
|
||||
|
||||
typeParams[i] = &model.Parameter{Name: param.Obj().Name(), Type: typeParam}
|
||||
}
|
||||
|
||||
return &model.Interface{Name: obj.Name(), Methods: methods, TypeParams: typeParams}, nil
|
||||
}
|
||||
|
||||
func (o *packageModeParser) isConstraint(t *types.Interface) bool {
|
||||
for i := range t.NumEmbeddeds() {
|
||||
embed := t.EmbeddedType(i)
|
||||
if _, ok := embed.Underlying().(*types.Interface); !ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *packageModeParser) parseType(t types.Type) (model.Type, error) {
|
||||
switch t := t.(type) {
|
||||
case *types.Array:
|
||||
elementType, err := p.parseType(t.Elem())
|
||||
if err != nil {
|
||||
return nil, newParseTypeError("parse array type", t.Elem().String(), err)
|
||||
}
|
||||
return &model.ArrayType{Len: int(t.Len()), Type: elementType}, nil
|
||||
case *types.Slice:
|
||||
elementType, err := p.parseType(t.Elem())
|
||||
if err != nil {
|
||||
return nil, newParseTypeError("parse slice type", t.Elem().String(), err)
|
||||
}
|
||||
|
||||
return &model.ArrayType{Len: -1, Type: elementType}, nil
|
||||
case *types.Chan:
|
||||
var dir model.ChanDir
|
||||
switch t.Dir() {
|
||||
case types.RecvOnly:
|
||||
dir = model.RecvDir
|
||||
case types.SendOnly:
|
||||
dir = model.SendDir
|
||||
}
|
||||
|
||||
chanType, err := p.parseType(t.Elem())
|
||||
if err != nil {
|
||||
return nil, newParseTypeError("parse chan type", t.Elem().String(), err)
|
||||
}
|
||||
|
||||
return &model.ChanType{Dir: dir, Type: chanType}, nil
|
||||
case *types.Signature:
|
||||
sig, err := p.parseFunc(t)
|
||||
if err != nil {
|
||||
return nil, newParseTypeError("parse signature", t.String(), err)
|
||||
}
|
||||
|
||||
return sig, nil
|
||||
case *types.Named, *types.Alias:
|
||||
object := t.(interface{ Obj() *types.TypeName })
|
||||
name := object.Obj().Name()
|
||||
var pkg string
|
||||
if object.Obj().Pkg() != nil {
|
||||
pkg = object.Obj().Pkg().Path()
|
||||
}
|
||||
|
||||
// TypeArgs method not available for aliases in go1.22
|
||||
genericType, ok := t.(interface{ TypeArgs() *types.TypeList })
|
||||
if !ok || genericType.TypeArgs() == nil {
|
||||
return &model.NamedType{
|
||||
Package: pkg,
|
||||
Type: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
typeParams := &model.TypeParametersType{TypeParameters: make([]model.Type, genericType.TypeArgs().Len())}
|
||||
for i := range genericType.TypeArgs().Len() {
|
||||
typeParam := genericType.TypeArgs().At(i)
|
||||
typedParam, err := p.parseType(typeParam)
|
||||
if err != nil {
|
||||
return nil, newParseTypeError("parse type parameter", typeParam.String(), err)
|
||||
}
|
||||
|
||||
typeParams.TypeParameters[i] = typedParam
|
||||
}
|
||||
|
||||
return &model.NamedType{
|
||||
Package: pkg,
|
||||
Type: name,
|
||||
TypeParams: typeParams,
|
||||
}, nil
|
||||
case *types.Interface:
|
||||
if t.Empty() {
|
||||
return model.PredeclaredType("any"), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("cannot handle non-empty unnamed interfaces")
|
||||
case *types.Map:
|
||||
key, err := p.parseType(t.Key())
|
||||
if err != nil {
|
||||
return nil, newParseTypeError("parse map key", t.Key().String(), err)
|
||||
}
|
||||
value, err := p.parseType(t.Elem())
|
||||
if err != nil {
|
||||
return nil, newParseTypeError("parse map value", t.Elem().String(), err)
|
||||
}
|
||||
|
||||
return &model.MapType{Key: key, Value: value}, nil
|
||||
case *types.Pointer:
|
||||
valueType, err := p.parseType(t.Elem())
|
||||
if err != nil {
|
||||
return nil, newParseTypeError("parse pointer type", t.Elem().String(), err)
|
||||
}
|
||||
|
||||
return &model.PointerType{Type: valueType}, nil
|
||||
case *types.Struct:
|
||||
if t.NumFields() > 0 {
|
||||
return nil, fmt.Errorf("cannot handle non-empty unnamed structs")
|
||||
}
|
||||
|
||||
return model.PredeclaredType("struct{}"), nil
|
||||
case *types.Basic:
|
||||
return model.PredeclaredType(t.Name()), nil
|
||||
case *types.Tuple:
|
||||
panic("tuple field") // TODO
|
||||
case *types.TypeParam:
|
||||
return &model.NamedType{Type: t.Obj().Name()}, nil
|
||||
default:
|
||||
panic("unknown type") // TODO
|
||||
}
|
||||
}
|
||||
|
||||
func (p *packageModeParser) parseFunc(sig *types.Signature) (*model.FuncType, error) {
|
||||
var variadic *model.Parameter
|
||||
params := make([]*model.Parameter, 0, sig.Params().Len())
|
||||
for i := range sig.Params().Len() {
|
||||
param := sig.Params().At(i)
|
||||
|
||||
isVariadicParam := i == sig.Params().Len()-1 && sig.Variadic()
|
||||
parseType := param.Type()
|
||||
if isVariadicParam {
|
||||
sliceType, ok := param.Type().(*types.Slice)
|
||||
if !ok {
|
||||
return nil, newParseTypeError("variadic parameter is not a slice", param.String(), nil)
|
||||
}
|
||||
|
||||
parseType = sliceType.Elem()
|
||||
}
|
||||
|
||||
paramType, err := p.parseType(parseType)
|
||||
if err != nil {
|
||||
return nil, newParseTypeError("parse parameter type", parseType.String(), err)
|
||||
}
|
||||
|
||||
modelParameter := &model.Parameter{Type: paramType, Name: param.Name()}
|
||||
|
||||
if isVariadicParam {
|
||||
variadic = modelParameter
|
||||
} else {
|
||||
params = append(params, modelParameter)
|
||||
}
|
||||
}
|
||||
|
||||
if len(params) == 0 {
|
||||
params = nil
|
||||
}
|
||||
|
||||
results := make([]*model.Parameter, sig.Results().Len())
|
||||
for i := range sig.Results().Len() {
|
||||
result := sig.Results().At(i)
|
||||
|
||||
resultType, err := p.parseType(result.Type())
|
||||
if err != nil {
|
||||
return nil, newParseTypeError("parse result type", result.Type().String(), err)
|
||||
}
|
||||
|
||||
results[i] = &model.Parameter{Type: resultType, Name: result.Name()}
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
results = nil
|
||||
}
|
||||
|
||||
return &model.FuncType{
|
||||
In: params,
|
||||
Out: results,
|
||||
Variadic: variadic,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *packageModeParser) parseConstraint(t *types.TypeParam) (model.Type, error) {
|
||||
if t == nil {
|
||||
return nil, fmt.Errorf("nil type param")
|
||||
}
|
||||
|
||||
typeParam, err := p.parseType(t.Constraint())
|
||||
if err != nil {
|
||||
return nil, newParseTypeError("parse constraint type", t.Constraint().String(), err)
|
||||
}
|
||||
|
||||
return typeParam, nil
|
||||
}
|
||||
|
||||
type parseTypeError struct {
|
||||
message string
|
||||
typeString string
|
||||
error error
|
||||
}
|
||||
|
||||
func newParseTypeError(message string, typeString string, error error) *parseTypeError {
|
||||
return &parseTypeError{typeString: typeString, error: error, message: message}
|
||||
}
|
||||
|
||||
func (p parseTypeError) Error() string {
|
||||
if p.error != nil {
|
||||
return fmt.Sprintf("%s: error parsing %s: %s", p.message, p.typeString, p.error)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s: error parsing type %s", p.message, p.typeString)
|
||||
}
|
||||
|
||||
func (p parseTypeError) Unwrap() error {
|
||||
return p.error
|
||||
}
|
||||
-805
@@ -1,805 +0,0 @@
|
||||
// Copyright 2012 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
// This file contains the model construction by parsing source files.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/importer"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/mock/mockgen/model"
|
||||
)
|
||||
|
||||
// sourceMode generates mocks via source file.
|
||||
func sourceMode(source string) (*model.Package, error) {
|
||||
srcDir, err := filepath.Abs(filepath.Dir(source))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed getting source directory: %v", err)
|
||||
}
|
||||
|
||||
packageImport, err := parsePackageImport(srcDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fs := token.NewFileSet()
|
||||
file, err := parser.ParseFile(fs, source, nil, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed parsing source file %v: %v", source, err)
|
||||
}
|
||||
|
||||
p := &fileParser{
|
||||
fileSet: fs,
|
||||
imports: make(map[string]importedPackage),
|
||||
importedInterfaces: newInterfaceCache(),
|
||||
auxInterfaces: newInterfaceCache(),
|
||||
srcDir: srcDir,
|
||||
}
|
||||
|
||||
// Handle -imports.
|
||||
dotImports := make(map[string]bool)
|
||||
if *imports != "" {
|
||||
for _, kv := range strings.Split(*imports, ",") {
|
||||
eq := strings.Index(kv, "=")
|
||||
k, v := kv[:eq], kv[eq+1:]
|
||||
if k == "." {
|
||||
dotImports[v] = true
|
||||
} else {
|
||||
p.imports[k] = importedPkg{path: v}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if *excludeInterfaces != "" {
|
||||
p.excludeNamesSet = parseExcludeInterfaces(*excludeInterfaces)
|
||||
}
|
||||
|
||||
// Handle -aux_files.
|
||||
if err := p.parseAuxFiles(*auxFiles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.addAuxInterfacesFromFile(packageImport, file) // this file
|
||||
|
||||
pkg, err := p.parseFile(packageImport, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for pkgPath := range dotImports {
|
||||
pkg.DotImports = append(pkg.DotImports, pkgPath)
|
||||
}
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
type importedPackage interface {
|
||||
Path() string
|
||||
Parser() *fileParser
|
||||
}
|
||||
|
||||
type importedPkg struct {
|
||||
path string
|
||||
parser *fileParser
|
||||
}
|
||||
|
||||
func (i importedPkg) Path() string { return i.path }
|
||||
func (i importedPkg) Parser() *fileParser { return i.parser }
|
||||
|
||||
// duplicateImport is a bit of a misnomer. Currently the parser can't
|
||||
// handle cases of multi-file packages importing different packages
|
||||
// under the same name. Often these imports would not be problematic,
|
||||
// so this type lets us defer raising an error unless the package name
|
||||
// is actually used.
|
||||
type duplicateImport struct {
|
||||
name string
|
||||
duplicates []string
|
||||
}
|
||||
|
||||
func (d duplicateImport) Error() string {
|
||||
return fmt.Sprintf("%q is ambiguous because of duplicate imports: %v", d.name, d.duplicates)
|
||||
}
|
||||
|
||||
func (d duplicateImport) Path() string { log.Fatal(d.Error()); return "" }
|
||||
func (d duplicateImport) Parser() *fileParser { log.Fatal(d.Error()); return nil }
|
||||
|
||||
type interfaceCache struct {
|
||||
m map[string]map[string]*namedInterface
|
||||
}
|
||||
|
||||
func newInterfaceCache() *interfaceCache {
|
||||
return &interfaceCache{
|
||||
m: make(map[string]map[string]*namedInterface),
|
||||
}
|
||||
}
|
||||
|
||||
func (i *interfaceCache) Set(pkg, name string, it *namedInterface) {
|
||||
if _, ok := i.m[pkg]; !ok {
|
||||
i.m[pkg] = make(map[string]*namedInterface)
|
||||
}
|
||||
i.m[pkg][name] = it
|
||||
}
|
||||
|
||||
func (i *interfaceCache) Get(pkg, name string) *namedInterface {
|
||||
if _, ok := i.m[pkg]; !ok {
|
||||
return nil
|
||||
}
|
||||
return i.m[pkg][name]
|
||||
}
|
||||
|
||||
func (i *interfaceCache) GetASTIface(pkg, name string) *ast.InterfaceType {
|
||||
if _, ok := i.m[pkg]; !ok {
|
||||
return nil
|
||||
}
|
||||
it, ok := i.m[pkg][name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return it.it
|
||||
}
|
||||
|
||||
type fileParser struct {
|
||||
fileSet *token.FileSet
|
||||
imports map[string]importedPackage // package name => imported package
|
||||
importedInterfaces *interfaceCache
|
||||
auxFiles []*ast.File
|
||||
auxInterfaces *interfaceCache
|
||||
srcDir string
|
||||
excludeNamesSet map[string]struct{}
|
||||
}
|
||||
|
||||
func (p *fileParser) errorf(pos token.Pos, format string, args ...any) error {
|
||||
ps := p.fileSet.Position(pos)
|
||||
format = "%s:%d:%d: " + format
|
||||
args = append([]any{ps.Filename, ps.Line, ps.Column}, args...)
|
||||
return fmt.Errorf(format, args...)
|
||||
}
|
||||
|
||||
func (p *fileParser) parseAuxFiles(auxFiles string) error {
|
||||
auxFiles = strings.TrimSpace(auxFiles)
|
||||
if auxFiles == "" {
|
||||
return nil
|
||||
}
|
||||
for _, kv := range strings.Split(auxFiles, ",") {
|
||||
parts := strings.SplitN(kv, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("bad aux file spec: %v", kv)
|
||||
}
|
||||
pkg, fpath := parts[0], parts[1]
|
||||
|
||||
file, err := parser.ParseFile(p.fileSet, fpath, nil, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.auxFiles = append(p.auxFiles, file)
|
||||
p.addAuxInterfacesFromFile(pkg, file)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *fileParser) addAuxInterfacesFromFile(pkg string, file *ast.File) {
|
||||
for ni := range iterInterfaces(file) {
|
||||
p.auxInterfaces.Set(pkg, ni.name.Name, ni)
|
||||
}
|
||||
}
|
||||
|
||||
// parseFile loads all file imports and auxiliary files import into the
|
||||
// fileParser, parses all file interfaces and returns package model.
|
||||
func (p *fileParser) parseFile(importPath string, file *ast.File) (*model.Package, error) {
|
||||
allImports, dotImports := importsOfFile(file)
|
||||
// Don't stomp imports provided by -imports. Those should take precedence.
|
||||
for pkg, pkgI := range allImports {
|
||||
if _, ok := p.imports[pkg]; !ok {
|
||||
p.imports[pkg] = pkgI
|
||||
}
|
||||
}
|
||||
// Add imports from auxiliary files, which might be needed for embedded interfaces.
|
||||
// Don't stomp any other imports.
|
||||
for _, f := range p.auxFiles {
|
||||
auxImports, _ := importsOfFile(f)
|
||||
for pkg, pkgI := range auxImports {
|
||||
if _, ok := p.imports[pkg]; !ok {
|
||||
p.imports[pkg] = pkgI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var is []*model.Interface
|
||||
for ni := range iterInterfaces(file) {
|
||||
if _, ok := p.excludeNamesSet[ni.name.String()]; ok {
|
||||
continue
|
||||
}
|
||||
i, err := p.parseInterface(ni.name.String(), importPath, ni)
|
||||
if errors.Is(err, errConstraintInterface) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
is = append(is, i)
|
||||
}
|
||||
return &model.Package{
|
||||
Name: file.Name.String(),
|
||||
PkgPath: importPath,
|
||||
Interfaces: is,
|
||||
DotImports: dotImports,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parsePackage loads package specified by path, parses it and returns
|
||||
// a new fileParser with the parsed imports and interfaces.
|
||||
func (p *fileParser) parsePackage(path string) (*fileParser, error) {
|
||||
newP := &fileParser{
|
||||
fileSet: token.NewFileSet(),
|
||||
imports: make(map[string]importedPackage),
|
||||
importedInterfaces: newInterfaceCache(),
|
||||
auxInterfaces: newInterfaceCache(),
|
||||
srcDir: p.srcDir,
|
||||
}
|
||||
|
||||
var pkgs map[string]*ast.Package
|
||||
if imp, err := build.Import(path, newP.srcDir, build.FindOnly); err != nil {
|
||||
return nil, err
|
||||
} else if pkgs, err = parser.ParseDir(newP.fileSet, imp.Dir, nil, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, pkg := range pkgs {
|
||||
file := ast.MergePackageFiles(pkg, ast.FilterFuncDuplicates|ast.FilterUnassociatedComments|ast.FilterImportDuplicates)
|
||||
for ni := range iterInterfaces(file) {
|
||||
newP.importedInterfaces.Set(path, ni.name.Name, ni)
|
||||
}
|
||||
imports, _ := importsOfFile(file)
|
||||
for pkgName, pkgI := range imports {
|
||||
newP.imports[pkgName] = pkgI
|
||||
}
|
||||
}
|
||||
return newP, nil
|
||||
}
|
||||
|
||||
func (p *fileParser) constructInstParams(pkg string, params []*ast.Field, instParams []model.Type, embeddedInstParams []ast.Expr, tps map[string]model.Type) ([]model.Type, error) {
|
||||
pm := make(map[string]int)
|
||||
var i int
|
||||
for _, v := range params {
|
||||
for _, n := range v.Names {
|
||||
pm[n.Name] = i
|
||||
instParams = append(instParams, model.PredeclaredType(n.Name))
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
var runtimeInstParams []model.Type
|
||||
for _, instParam := range embeddedInstParams {
|
||||
switch t := instParam.(type) {
|
||||
case *ast.Ident:
|
||||
if idx, ok := pm[t.Name]; ok {
|
||||
runtimeInstParams = append(runtimeInstParams, instParams[idx])
|
||||
continue
|
||||
}
|
||||
}
|
||||
modelType, err := p.parseType(pkg, instParam, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runtimeInstParams = append(runtimeInstParams, modelType)
|
||||
}
|
||||
|
||||
return runtimeInstParams, nil
|
||||
}
|
||||
|
||||
func (p *fileParser) constructTps(it *namedInterface) (tps map[string]model.Type) {
|
||||
tps = make(map[string]model.Type)
|
||||
n := 0
|
||||
for _, tp := range it.typeParams {
|
||||
for _, tm := range tp.Names {
|
||||
tps[tm.Name] = nil
|
||||
if len(it.instTypes) != 0 {
|
||||
tps[tm.Name] = it.instTypes[n]
|
||||
n++
|
||||
}
|
||||
}
|
||||
}
|
||||
return tps
|
||||
}
|
||||
|
||||
// parseInterface loads interface specified by pkg and name, parses it and returns
|
||||
// a new model with the parsed.
|
||||
func (p *fileParser) parseInterface(name, pkg string, it *namedInterface) (*model.Interface, error) {
|
||||
iface := &model.Interface{Name: name}
|
||||
tps := p.constructTps(it)
|
||||
tp, err := p.parseFieldList(pkg, it.typeParams, tps)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse interface type parameters: %v", name)
|
||||
}
|
||||
|
||||
iface.TypeParams = tp
|
||||
for _, field := range it.it.Methods.List {
|
||||
var methods []*model.Method
|
||||
if methods, err = p.parseMethod(field, it, iface, pkg, tps); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, m := range methods {
|
||||
iface.AddMethod(m)
|
||||
}
|
||||
}
|
||||
return iface, nil
|
||||
}
|
||||
|
||||
func (p *fileParser) parseMethod(field *ast.Field, it *namedInterface, iface *model.Interface, pkg string, tps map[string]model.Type) ([]*model.Method, error) {
|
||||
// {} for git diff
|
||||
{
|
||||
switch v := field.Type.(type) {
|
||||
case *ast.FuncType:
|
||||
if nn := len(field.Names); nn != 1 {
|
||||
return nil, fmt.Errorf("expected one name for interface %v, got %d", iface.Name, nn)
|
||||
}
|
||||
m := &model.Method{
|
||||
Name: field.Names[0].String(),
|
||||
}
|
||||
var err error
|
||||
m.In, m.Variadic, m.Out, err = p.parseFunc(pkg, v, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []*model.Method{m}, nil
|
||||
case *ast.Ident:
|
||||
// Embedded interface in this package.
|
||||
embeddedIfaceType := p.auxInterfaces.Get(pkg, v.String())
|
||||
if embeddedIfaceType == nil {
|
||||
embeddedIfaceType = p.importedInterfaces.Get(pkg, v.String())
|
||||
}
|
||||
|
||||
var embeddedIface *model.Interface
|
||||
if embeddedIfaceType != nil {
|
||||
var err error
|
||||
embeddedIfaceType.instTypes, err = p.constructInstParams(pkg, it.typeParams, it.instTypes, it.embeddedInstTypeParams, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
embeddedIface, err = p.parseInterface(v.String(), pkg, embeddedIfaceType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
// This is built-in error interface.
|
||||
if v.String() == model.ErrorInterface.Name {
|
||||
embeddedIface = &model.ErrorInterface
|
||||
} else {
|
||||
ip, err := p.parsePackage(pkg)
|
||||
if err != nil {
|
||||
return nil, p.errorf(v.Pos(), "could not parse package %s: %v", pkg, err)
|
||||
}
|
||||
|
||||
if embeddedIfaceType = ip.importedInterfaces.Get(pkg, v.String()); embeddedIfaceType == nil {
|
||||
return nil, p.errorf(v.Pos(), "unknown embedded interface %s.%s", pkg, v.String())
|
||||
}
|
||||
|
||||
embeddedIfaceType.instTypes, err = p.constructInstParams(pkg, it.typeParams, it.instTypes, it.embeddedInstTypeParams, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
embeddedIface, err = ip.parseInterface(v.String(), pkg, embeddedIfaceType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return embeddedIface.Methods, nil
|
||||
case *ast.SelectorExpr:
|
||||
// Embedded interface in another package.
|
||||
filePkg, sel := v.X.(*ast.Ident).String(), v.Sel.String()
|
||||
embeddedPkg, ok := p.imports[filePkg]
|
||||
if !ok {
|
||||
return nil, p.errorf(v.X.Pos(), "unknown package %s", filePkg)
|
||||
}
|
||||
|
||||
var embeddedIface *model.Interface
|
||||
var err error
|
||||
embeddedIfaceType := p.auxInterfaces.Get(filePkg, sel)
|
||||
if embeddedIfaceType != nil {
|
||||
embeddedIfaceType.instTypes, err = p.constructInstParams(pkg, it.typeParams, it.instTypes, it.embeddedInstTypeParams, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
embeddedIface, err = p.parseInterface(sel, filePkg, embeddedIfaceType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
path := embeddedPkg.Path()
|
||||
parser := embeddedPkg.Parser()
|
||||
if parser == nil {
|
||||
ip, err := p.parsePackage(path)
|
||||
if err != nil {
|
||||
return nil, p.errorf(v.Pos(), "could not parse package %s: %v", path, err)
|
||||
}
|
||||
parser = ip
|
||||
p.imports[filePkg] = importedPkg{
|
||||
path: embeddedPkg.Path(),
|
||||
parser: parser,
|
||||
}
|
||||
}
|
||||
if embeddedIfaceType = parser.importedInterfaces.Get(path, sel); embeddedIfaceType == nil {
|
||||
return nil, p.errorf(v.Pos(), "unknown embedded interface %s.%s", path, sel)
|
||||
}
|
||||
|
||||
embeddedIfaceType.instTypes, err = p.constructInstParams(pkg, it.typeParams, it.instTypes, it.embeddedInstTypeParams, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
embeddedIface, err = parser.parseInterface(sel, path, embeddedIfaceType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// TODO: apply shadowing rules.
|
||||
return embeddedIface.Methods, nil
|
||||
default:
|
||||
return p.parseGenericMethod(field, it, iface, pkg, tps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *fileParser) parseFunc(pkg string, f *ast.FuncType, tps map[string]model.Type) (inParam []*model.Parameter, variadic *model.Parameter, outParam []*model.Parameter, err error) {
|
||||
if f.Params != nil {
|
||||
regParams := f.Params.List
|
||||
if isVariadic(f) {
|
||||
n := len(regParams)
|
||||
varParams := regParams[n-1:]
|
||||
regParams = regParams[:n-1]
|
||||
vp, err := p.parseFieldList(pkg, varParams, tps)
|
||||
if err != nil {
|
||||
return nil, nil, nil, p.errorf(varParams[0].Pos(), "failed parsing variadic argument: %v", err)
|
||||
}
|
||||
variadic = vp[0]
|
||||
}
|
||||
inParam, err = p.parseFieldList(pkg, regParams, tps)
|
||||
if err != nil {
|
||||
return nil, nil, nil, p.errorf(f.Pos(), "failed parsing arguments: %v", err)
|
||||
}
|
||||
}
|
||||
if f.Results != nil {
|
||||
outParam, err = p.parseFieldList(pkg, f.Results.List, tps)
|
||||
if err != nil {
|
||||
return nil, nil, nil, p.errorf(f.Pos(), "failed parsing returns: %v", err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *fileParser) parseFieldList(pkg string, fields []*ast.Field, tps map[string]model.Type) ([]*model.Parameter, error) {
|
||||
nf := 0
|
||||
for _, f := range fields {
|
||||
nn := len(f.Names)
|
||||
if nn == 0 {
|
||||
nn = 1 // anonymous parameter
|
||||
}
|
||||
nf += nn
|
||||
}
|
||||
if nf == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
ps := make([]*model.Parameter, nf)
|
||||
i := 0 // destination index
|
||||
for _, f := range fields {
|
||||
t, err := p.parseType(pkg, f.Type, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(f.Names) == 0 {
|
||||
// anonymous arg
|
||||
ps[i] = &model.Parameter{Type: t}
|
||||
i++
|
||||
continue
|
||||
}
|
||||
for _, name := range f.Names {
|
||||
ps[i] = &model.Parameter{Name: name.Name, Type: t}
|
||||
i++
|
||||
}
|
||||
}
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func (p *fileParser) parseType(pkg string, typ ast.Expr, tps map[string]model.Type) (model.Type, error) {
|
||||
switch v := typ.(type) {
|
||||
case *ast.ArrayType:
|
||||
ln := -1
|
||||
if v.Len != nil {
|
||||
value, err := p.parseArrayLength(v.Len)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ln, err = strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return nil, p.errorf(v.Len.Pos(), "bad array size: %v", err)
|
||||
}
|
||||
}
|
||||
t, err := p.parseType(pkg, v.Elt, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.ArrayType{Len: ln, Type: t}, nil
|
||||
case *ast.ChanType:
|
||||
t, err := p.parseType(pkg, v.Value, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var dir model.ChanDir
|
||||
if v.Dir == ast.SEND {
|
||||
dir = model.SendDir
|
||||
}
|
||||
if v.Dir == ast.RECV {
|
||||
dir = model.RecvDir
|
||||
}
|
||||
return &model.ChanType{Dir: dir, Type: t}, nil
|
||||
case *ast.Ellipsis:
|
||||
// assume we're parsing a variadic argument
|
||||
return p.parseType(pkg, v.Elt, tps)
|
||||
case *ast.FuncType:
|
||||
in, variadic, out, err := p.parseFunc(pkg, v, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.FuncType{In: in, Out: out, Variadic: variadic}, nil
|
||||
case *ast.Ident:
|
||||
it, ok := tps[v.Name]
|
||||
if v.IsExported() && !ok {
|
||||
// `pkg` may be an aliased imported pkg
|
||||
// if so, patch the import w/ the fully qualified import
|
||||
maybeImportedPkg, ok := p.imports[pkg]
|
||||
if ok {
|
||||
pkg = maybeImportedPkg.Path()
|
||||
}
|
||||
// assume type in this package
|
||||
return &model.NamedType{Package: pkg, Type: v.Name}, nil
|
||||
}
|
||||
if ok && it != nil {
|
||||
return it, nil
|
||||
}
|
||||
// assume predeclared type
|
||||
return model.PredeclaredType(v.Name), nil
|
||||
case *ast.InterfaceType:
|
||||
if v.Methods != nil && len(v.Methods.List) > 0 {
|
||||
return nil, p.errorf(v.Pos(), "can't handle non-empty unnamed interface types")
|
||||
}
|
||||
return model.PredeclaredType("any"), nil
|
||||
case *ast.MapType:
|
||||
key, err := p.parseType(pkg, v.Key, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value, err := p.parseType(pkg, v.Value, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.MapType{Key: key, Value: value}, nil
|
||||
case *ast.SelectorExpr:
|
||||
pkgName := v.X.(*ast.Ident).String()
|
||||
pkg, ok := p.imports[pkgName]
|
||||
if !ok {
|
||||
return nil, p.errorf(v.Pos(), "unknown package %q", pkgName)
|
||||
}
|
||||
return &model.NamedType{Package: pkg.Path(), Type: v.Sel.String()}, nil
|
||||
case *ast.StarExpr:
|
||||
t, err := p.parseType(pkg, v.X, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.PointerType{Type: t}, nil
|
||||
case *ast.StructType:
|
||||
if v.Fields != nil && len(v.Fields.List) > 0 {
|
||||
return nil, p.errorf(v.Pos(), "can't handle non-empty unnamed struct types")
|
||||
}
|
||||
return model.PredeclaredType("struct{}"), nil
|
||||
case *ast.ParenExpr:
|
||||
return p.parseType(pkg, v.X, tps)
|
||||
default:
|
||||
mt, err := p.parseGenericType(pkg, typ, tps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mt == nil {
|
||||
break
|
||||
}
|
||||
return mt, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("don't know how to parse type %T", typ)
|
||||
}
|
||||
|
||||
func (p *fileParser) parseArrayLength(expr ast.Expr) (string, error) {
|
||||
switch val := expr.(type) {
|
||||
case (*ast.BasicLit):
|
||||
return val.Value, nil
|
||||
case (*ast.Ident):
|
||||
// when the length is a const defined locally
|
||||
return val.Obj.Decl.(*ast.ValueSpec).Values[0].(*ast.BasicLit).Value, nil
|
||||
case (*ast.SelectorExpr):
|
||||
// when the length is a const defined in an external package
|
||||
usedPkg, err := importer.Default().Import(fmt.Sprintf("%s", val.X))
|
||||
if err != nil {
|
||||
return "", p.errorf(expr.Pos(), "unknown package in array length: %v", err)
|
||||
}
|
||||
ev, err := types.Eval(token.NewFileSet(), usedPkg, token.NoPos, val.Sel.Name)
|
||||
if err != nil {
|
||||
return "", p.errorf(expr.Pos(), "unknown constant in array length: %v", err)
|
||||
}
|
||||
return ev.Value.String(), nil
|
||||
case (*ast.ParenExpr):
|
||||
return p.parseArrayLength(val.X)
|
||||
case (*ast.BinaryExpr):
|
||||
x, err := p.parseArrayLength(val.X)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
y, err := p.parseArrayLength(val.Y)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
biExpr := fmt.Sprintf("%s%v%s", x, val.Op, y)
|
||||
tv, err := types.Eval(token.NewFileSet(), nil, token.NoPos, biExpr)
|
||||
if err != nil {
|
||||
return "", p.errorf(expr.Pos(), "invalid expression in array length: %v", err)
|
||||
}
|
||||
return tv.Value.String(), nil
|
||||
default:
|
||||
return "", p.errorf(expr.Pos(), "invalid expression in array length: %v", val)
|
||||
}
|
||||
}
|
||||
|
||||
// importsOfFile returns a map of package name to import path
|
||||
// of the imports in file.
|
||||
func importsOfFile(file *ast.File) (normalImports map[string]importedPackage, dotImports []string) {
|
||||
var importPaths []string
|
||||
for _, is := range file.Imports {
|
||||
if is.Name != nil {
|
||||
continue
|
||||
}
|
||||
importPath := is.Path.Value[1 : len(is.Path.Value)-1] // remove quotes
|
||||
importPaths = append(importPaths, importPath)
|
||||
}
|
||||
packagesName := createPackageMap(importPaths)
|
||||
normalImports = make(map[string]importedPackage)
|
||||
dotImports = make([]string, 0)
|
||||
for _, is := range file.Imports {
|
||||
var pkgName string
|
||||
importPath := is.Path.Value[1 : len(is.Path.Value)-1] // remove quotes
|
||||
|
||||
if is.Name != nil {
|
||||
// Named imports are always certain.
|
||||
if is.Name.Name == "_" {
|
||||
continue
|
||||
}
|
||||
pkgName = is.Name.Name
|
||||
} else {
|
||||
pkg, ok := packagesName[importPath]
|
||||
if !ok {
|
||||
// Fallback to import path suffix. Note that this is uncertain.
|
||||
_, last := path.Split(importPath)
|
||||
// If the last path component has dots, the first dot-delimited
|
||||
// field is used as the name.
|
||||
pkgName = strings.SplitN(last, ".", 2)[0]
|
||||
} else {
|
||||
pkgName = pkg
|
||||
}
|
||||
}
|
||||
|
||||
if pkgName == "." {
|
||||
dotImports = append(dotImports, importPath)
|
||||
} else {
|
||||
if pkg, ok := normalImports[pkgName]; ok {
|
||||
switch p := pkg.(type) {
|
||||
case duplicateImport:
|
||||
normalImports[pkgName] = duplicateImport{
|
||||
name: p.name,
|
||||
duplicates: append([]string{importPath}, p.duplicates...),
|
||||
}
|
||||
case importedPkg:
|
||||
normalImports[pkgName] = duplicateImport{
|
||||
name: pkgName,
|
||||
duplicates: []string{p.path, importPath},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
normalImports[pkgName] = importedPkg{path: importPath}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type namedInterface struct {
|
||||
name *ast.Ident
|
||||
it *ast.InterfaceType
|
||||
typeParams []*ast.Field
|
||||
embeddedInstTypeParams []ast.Expr
|
||||
instTypes []model.Type
|
||||
}
|
||||
|
||||
// Create an iterator over all interfaces in file.
|
||||
func iterInterfaces(file *ast.File) <-chan *namedInterface {
|
||||
ch := make(chan *namedInterface)
|
||||
go func() {
|
||||
for _, decl := range file.Decls {
|
||||
gd, ok := decl.(*ast.GenDecl)
|
||||
if !ok || gd.Tok != token.TYPE {
|
||||
continue
|
||||
}
|
||||
for _, spec := range gd.Specs {
|
||||
ts, ok := spec.(*ast.TypeSpec)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
it, ok := ts.Type.(*ast.InterfaceType)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ch <- &namedInterface{name: ts.Name, it: it, typeParams: getTypeSpecTypeParams(ts)}
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
// isVariadic returns whether the function is variadic.
|
||||
func isVariadic(f *ast.FuncType) bool {
|
||||
nargs := len(f.Params.List)
|
||||
if nargs == 0 {
|
||||
return false
|
||||
}
|
||||
_, ok := f.Params.List[nargs-1].Type.(*ast.Ellipsis)
|
||||
return ok
|
||||
}
|
||||
|
||||
// packageNameOfDir get package import path via dir
|
||||
func packageNameOfDir(srcDir string) (string, error) {
|
||||
files, err := os.ReadDir(srcDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var goFilePath string
|
||||
for _, file := range files {
|
||||
if !file.IsDir() && strings.HasSuffix(file.Name(), ".go") {
|
||||
goFilePath = file.Name()
|
||||
break
|
||||
}
|
||||
}
|
||||
if goFilePath == "" {
|
||||
return "", fmt.Errorf("go source file not found %s", srcDir)
|
||||
}
|
||||
|
||||
packageImport, err := parsePackageImport(srcDir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return packageImport, nil
|
||||
}
|
||||
|
||||
var errOutsideGoPath = errors.New("source directory is outside GOPATH")
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
func printModuleVersion() {
|
||||
if bi, exists := debug.ReadBuildInfo(); exists {
|
||||
fmt.Println(bi.Main.Version)
|
||||
} else {
|
||||
log.Printf("No version information found. Make sure to use " +
|
||||
"GO111MODULE=on when running 'go get' in order to use specific " +
|
||||
"version of the binary.")
|
||||
}
|
||||
}
|
||||
-184
@@ -1,184 +0,0 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Module file printer.
|
||||
|
||||
package modfile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Format returns a go.mod file as a byte slice, formatted in standard style.
|
||||
func Format(f *FileSyntax) []byte {
|
||||
pr := &printer{}
|
||||
pr.file(f)
|
||||
|
||||
// remove trailing blank lines
|
||||
b := pr.Bytes()
|
||||
for len(b) > 0 && b[len(b)-1] == '\n' && (len(b) == 1 || b[len(b)-2] == '\n') {
|
||||
b = b[:len(b)-1]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// A printer collects the state during printing of a file or expression.
|
||||
type printer struct {
|
||||
bytes.Buffer // output buffer
|
||||
comment []Comment // pending end-of-line comments
|
||||
margin int // left margin (indent), a number of tabs
|
||||
}
|
||||
|
||||
// printf prints to the buffer.
|
||||
func (p *printer) printf(format string, args ...interface{}) {
|
||||
fmt.Fprintf(p, format, args...)
|
||||
}
|
||||
|
||||
// indent returns the position on the current line, in bytes, 0-indexed.
|
||||
func (p *printer) indent() int {
|
||||
b := p.Bytes()
|
||||
n := 0
|
||||
for n < len(b) && b[len(b)-1-n] != '\n' {
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// newline ends the current line, flushing end-of-line comments.
|
||||
func (p *printer) newline() {
|
||||
if len(p.comment) > 0 {
|
||||
p.printf(" ")
|
||||
for i, com := range p.comment {
|
||||
if i > 0 {
|
||||
p.trim()
|
||||
p.printf("\n")
|
||||
for i := 0; i < p.margin; i++ {
|
||||
p.printf("\t")
|
||||
}
|
||||
}
|
||||
p.printf("%s", strings.TrimSpace(com.Token))
|
||||
}
|
||||
p.comment = p.comment[:0]
|
||||
}
|
||||
|
||||
p.trim()
|
||||
if b := p.Bytes(); len(b) == 0 || (len(b) >= 2 && b[len(b)-1] == '\n' && b[len(b)-2] == '\n') {
|
||||
// skip the blank line at top of file or after a blank line
|
||||
} else {
|
||||
p.printf("\n")
|
||||
}
|
||||
for i := 0; i < p.margin; i++ {
|
||||
p.printf("\t")
|
||||
}
|
||||
}
|
||||
|
||||
// trim removes trailing spaces and tabs from the current line.
|
||||
func (p *printer) trim() {
|
||||
// Remove trailing spaces and tabs from line we're about to end.
|
||||
b := p.Bytes()
|
||||
n := len(b)
|
||||
for n > 0 && (b[n-1] == '\t' || b[n-1] == ' ') {
|
||||
n--
|
||||
}
|
||||
p.Truncate(n)
|
||||
}
|
||||
|
||||
// file formats the given file into the print buffer.
|
||||
func (p *printer) file(f *FileSyntax) {
|
||||
for _, com := range f.Before {
|
||||
p.printf("%s", strings.TrimSpace(com.Token))
|
||||
p.newline()
|
||||
}
|
||||
|
||||
for i, stmt := range f.Stmt {
|
||||
switch x := stmt.(type) {
|
||||
case *CommentBlock:
|
||||
// comments already handled
|
||||
p.expr(x)
|
||||
|
||||
default:
|
||||
p.expr(x)
|
||||
p.newline()
|
||||
}
|
||||
|
||||
for _, com := range stmt.Comment().After {
|
||||
p.printf("%s", strings.TrimSpace(com.Token))
|
||||
p.newline()
|
||||
}
|
||||
|
||||
if i+1 < len(f.Stmt) {
|
||||
p.newline()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *printer) expr(x Expr) {
|
||||
// Emit line-comments preceding this expression.
|
||||
if before := x.Comment().Before; len(before) > 0 {
|
||||
// Want to print a line comment.
|
||||
// Line comments must be at the current margin.
|
||||
p.trim()
|
||||
if p.indent() > 0 {
|
||||
// There's other text on the line. Start a new line.
|
||||
p.printf("\n")
|
||||
}
|
||||
// Re-indent to margin.
|
||||
for i := 0; i < p.margin; i++ {
|
||||
p.printf("\t")
|
||||
}
|
||||
for _, com := range before {
|
||||
p.printf("%s", strings.TrimSpace(com.Token))
|
||||
p.newline()
|
||||
}
|
||||
}
|
||||
|
||||
switch x := x.(type) {
|
||||
default:
|
||||
panic(fmt.Errorf("printer: unexpected type %T", x))
|
||||
|
||||
case *CommentBlock:
|
||||
// done
|
||||
|
||||
case *LParen:
|
||||
p.printf("(")
|
||||
case *RParen:
|
||||
p.printf(")")
|
||||
|
||||
case *Line:
|
||||
p.tokens(x.Token)
|
||||
|
||||
case *LineBlock:
|
||||
p.tokens(x.Token)
|
||||
p.printf(" ")
|
||||
p.expr(&x.LParen)
|
||||
p.margin++
|
||||
for _, l := range x.Line {
|
||||
p.newline()
|
||||
p.expr(l)
|
||||
}
|
||||
p.margin--
|
||||
p.newline()
|
||||
p.expr(&x.RParen)
|
||||
}
|
||||
|
||||
// Queue end-of-line comments for printing when we
|
||||
// reach the end of the line.
|
||||
p.comment = append(p.comment, x.Comment().Suffix...)
|
||||
}
|
||||
|
||||
func (p *printer) tokens(tokens []string) {
|
||||
sep := ""
|
||||
for _, t := range tokens {
|
||||
if t == "," || t == ")" || t == "]" || t == "}" {
|
||||
sep = ""
|
||||
}
|
||||
p.printf("%s%s", sep, t)
|
||||
sep = " "
|
||||
if t == "(" || t == "[" || t == "{" {
|
||||
sep = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
-964
@@ -1,964 +0,0 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package modfile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// A Position describes an arbitrary source position in a file, including the
|
||||
// file, line, column, and byte offset.
|
||||
type Position struct {
|
||||
Line int // line in input (starting at 1)
|
||||
LineRune int // rune in line (starting at 1)
|
||||
Byte int // byte in input (starting at 0)
|
||||
}
|
||||
|
||||
// add returns the position at the end of s, assuming it starts at p.
|
||||
func (p Position) add(s string) Position {
|
||||
p.Byte += len(s)
|
||||
if n := strings.Count(s, "\n"); n > 0 {
|
||||
p.Line += n
|
||||
s = s[strings.LastIndex(s, "\n")+1:]
|
||||
p.LineRune = 1
|
||||
}
|
||||
p.LineRune += utf8.RuneCountInString(s)
|
||||
return p
|
||||
}
|
||||
|
||||
// An Expr represents an input element.
|
||||
type Expr interface {
|
||||
// Span returns the start and end position of the expression,
|
||||
// excluding leading or trailing comments.
|
||||
Span() (start, end Position)
|
||||
|
||||
// Comment returns the comments attached to the expression.
|
||||
// This method would normally be named 'Comments' but that
|
||||
// would interfere with embedding a type of the same name.
|
||||
Comment() *Comments
|
||||
}
|
||||
|
||||
// A Comment represents a single // comment.
|
||||
type Comment struct {
|
||||
Start Position
|
||||
Token string // without trailing newline
|
||||
Suffix bool // an end of line (not whole line) comment
|
||||
}
|
||||
|
||||
// Comments collects the comments associated with an expression.
|
||||
type Comments struct {
|
||||
Before []Comment // whole-line comments before this expression
|
||||
Suffix []Comment // end-of-line comments after this expression
|
||||
|
||||
// For top-level expressions only, After lists whole-line
|
||||
// comments following the expression.
|
||||
After []Comment
|
||||
}
|
||||
|
||||
// Comment returns the receiver. This isn't useful by itself, but
|
||||
// a [Comments] struct is embedded into all the expression
|
||||
// implementation types, and this gives each of those a Comment
|
||||
// method to satisfy the Expr interface.
|
||||
func (c *Comments) Comment() *Comments {
|
||||
return c
|
||||
}
|
||||
|
||||
// A FileSyntax represents an entire go.mod file.
|
||||
type FileSyntax struct {
|
||||
Name string // file path
|
||||
Comments
|
||||
Stmt []Expr
|
||||
}
|
||||
|
||||
func (x *FileSyntax) Span() (start, end Position) {
|
||||
if len(x.Stmt) == 0 {
|
||||
return
|
||||
}
|
||||
start, _ = x.Stmt[0].Span()
|
||||
_, end = x.Stmt[len(x.Stmt)-1].Span()
|
||||
return start, end
|
||||
}
|
||||
|
||||
// addLine adds a line containing the given tokens to the file.
|
||||
//
|
||||
// If the first token of the hint matches the first token of the
|
||||
// line, the new line is added at the end of the block containing hint,
|
||||
// extracting hint into a new block if it is not yet in one.
|
||||
//
|
||||
// If the hint is non-nil buts its first token does not match,
|
||||
// the new line is added after the block containing hint
|
||||
// (or hint itself, if not in a block).
|
||||
//
|
||||
// If no hint is provided, addLine appends the line to the end of
|
||||
// the last block with a matching first token,
|
||||
// or to the end of the file if no such block exists.
|
||||
func (x *FileSyntax) addLine(hint Expr, tokens ...string) *Line {
|
||||
if hint == nil {
|
||||
// If no hint given, add to the last statement of the given type.
|
||||
Loop:
|
||||
for i := len(x.Stmt) - 1; i >= 0; i-- {
|
||||
stmt := x.Stmt[i]
|
||||
switch stmt := stmt.(type) {
|
||||
case *Line:
|
||||
if stmt.Token != nil && stmt.Token[0] == tokens[0] {
|
||||
hint = stmt
|
||||
break Loop
|
||||
}
|
||||
case *LineBlock:
|
||||
if stmt.Token[0] == tokens[0] {
|
||||
hint = stmt
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newLineAfter := func(i int) *Line {
|
||||
new := &Line{Token: tokens}
|
||||
if i == len(x.Stmt) {
|
||||
x.Stmt = append(x.Stmt, new)
|
||||
} else {
|
||||
x.Stmt = append(x.Stmt, nil)
|
||||
copy(x.Stmt[i+2:], x.Stmt[i+1:])
|
||||
x.Stmt[i+1] = new
|
||||
}
|
||||
return new
|
||||
}
|
||||
|
||||
if hint != nil {
|
||||
for i, stmt := range x.Stmt {
|
||||
switch stmt := stmt.(type) {
|
||||
case *Line:
|
||||
if stmt == hint {
|
||||
if stmt.Token == nil || stmt.Token[0] != tokens[0] {
|
||||
return newLineAfter(i)
|
||||
}
|
||||
|
||||
// Convert line to line block.
|
||||
stmt.InBlock = true
|
||||
block := &LineBlock{Token: stmt.Token[:1], Line: []*Line{stmt}}
|
||||
stmt.Token = stmt.Token[1:]
|
||||
x.Stmt[i] = block
|
||||
new := &Line{Token: tokens[1:], InBlock: true}
|
||||
block.Line = append(block.Line, new)
|
||||
return new
|
||||
}
|
||||
|
||||
case *LineBlock:
|
||||
if stmt == hint {
|
||||
if stmt.Token[0] != tokens[0] {
|
||||
return newLineAfter(i)
|
||||
}
|
||||
|
||||
new := &Line{Token: tokens[1:], InBlock: true}
|
||||
stmt.Line = append(stmt.Line, new)
|
||||
return new
|
||||
}
|
||||
|
||||
for j, line := range stmt.Line {
|
||||
if line == hint {
|
||||
if stmt.Token[0] != tokens[0] {
|
||||
return newLineAfter(i)
|
||||
}
|
||||
|
||||
// Add new line after hint within the block.
|
||||
stmt.Line = append(stmt.Line, nil)
|
||||
copy(stmt.Line[j+2:], stmt.Line[j+1:])
|
||||
new := &Line{Token: tokens[1:], InBlock: true}
|
||||
stmt.Line[j+1] = new
|
||||
return new
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new := &Line{Token: tokens}
|
||||
x.Stmt = append(x.Stmt, new)
|
||||
return new
|
||||
}
|
||||
|
||||
func (x *FileSyntax) updateLine(line *Line, tokens ...string) {
|
||||
if line.InBlock {
|
||||
tokens = tokens[1:]
|
||||
}
|
||||
line.Token = tokens
|
||||
}
|
||||
|
||||
// markRemoved modifies line so that it (and its end-of-line comment, if any)
|
||||
// will be dropped by (*FileSyntax).Cleanup.
|
||||
func (line *Line) markRemoved() {
|
||||
line.Token = nil
|
||||
line.Comments.Suffix = nil
|
||||
}
|
||||
|
||||
// Cleanup cleans up the file syntax x after any edit operations.
|
||||
// To avoid quadratic behavior, (*Line).markRemoved marks the line as dead
|
||||
// by setting line.Token = nil but does not remove it from the slice
|
||||
// in which it appears. After edits have all been indicated,
|
||||
// calling Cleanup cleans out the dead lines.
|
||||
func (x *FileSyntax) Cleanup() {
|
||||
w := 0
|
||||
for _, stmt := range x.Stmt {
|
||||
switch stmt := stmt.(type) {
|
||||
case *Line:
|
||||
if stmt.Token == nil {
|
||||
continue
|
||||
}
|
||||
case *LineBlock:
|
||||
ww := 0
|
||||
for _, line := range stmt.Line {
|
||||
if line.Token != nil {
|
||||
stmt.Line[ww] = line
|
||||
ww++
|
||||
}
|
||||
}
|
||||
if ww == 0 {
|
||||
continue
|
||||
}
|
||||
if ww == 1 && len(stmt.RParen.Comments.Before) == 0 {
|
||||
// Collapse block into single line but keep the Line reference used by the
|
||||
// parsed File structure.
|
||||
*stmt.Line[0] = Line{
|
||||
Comments: Comments{
|
||||
Before: commentsAdd(stmt.Before, stmt.Line[0].Before),
|
||||
Suffix: commentsAdd(stmt.Line[0].Suffix, stmt.Suffix),
|
||||
After: commentsAdd(stmt.Line[0].After, stmt.After),
|
||||
},
|
||||
Token: stringsAdd(stmt.Token, stmt.Line[0].Token),
|
||||
}
|
||||
x.Stmt[w] = stmt.Line[0]
|
||||
w++
|
||||
continue
|
||||
}
|
||||
stmt.Line = stmt.Line[:ww]
|
||||
}
|
||||
x.Stmt[w] = stmt
|
||||
w++
|
||||
}
|
||||
x.Stmt = x.Stmt[:w]
|
||||
}
|
||||
|
||||
func commentsAdd(x, y []Comment) []Comment {
|
||||
return append(x[:len(x):len(x)], y...)
|
||||
}
|
||||
|
||||
func stringsAdd(x, y []string) []string {
|
||||
return append(x[:len(x):len(x)], y...)
|
||||
}
|
||||
|
||||
// A CommentBlock represents a top-level block of comments separate
|
||||
// from any rule.
|
||||
type CommentBlock struct {
|
||||
Comments
|
||||
Start Position
|
||||
}
|
||||
|
||||
func (x *CommentBlock) Span() (start, end Position) {
|
||||
return x.Start, x.Start
|
||||
}
|
||||
|
||||
// A Line is a single line of tokens.
|
||||
type Line struct {
|
||||
Comments
|
||||
Start Position
|
||||
Token []string
|
||||
InBlock bool
|
||||
End Position
|
||||
}
|
||||
|
||||
func (x *Line) Span() (start, end Position) {
|
||||
return x.Start, x.End
|
||||
}
|
||||
|
||||
// A LineBlock is a factored block of lines, like
|
||||
//
|
||||
// require (
|
||||
// "x"
|
||||
// "y"
|
||||
// )
|
||||
type LineBlock struct {
|
||||
Comments
|
||||
Start Position
|
||||
LParen LParen
|
||||
Token []string
|
||||
Line []*Line
|
||||
RParen RParen
|
||||
}
|
||||
|
||||
func (x *LineBlock) Span() (start, end Position) {
|
||||
return x.Start, x.RParen.Pos.add(")")
|
||||
}
|
||||
|
||||
// An LParen represents the beginning of a parenthesized line block.
|
||||
// It is a place to store suffix comments.
|
||||
type LParen struct {
|
||||
Comments
|
||||
Pos Position
|
||||
}
|
||||
|
||||
func (x *LParen) Span() (start, end Position) {
|
||||
return x.Pos, x.Pos.add(")")
|
||||
}
|
||||
|
||||
// An RParen represents the end of a parenthesized line block.
|
||||
// It is a place to store whole-line (before) comments.
|
||||
type RParen struct {
|
||||
Comments
|
||||
Pos Position
|
||||
}
|
||||
|
||||
func (x *RParen) Span() (start, end Position) {
|
||||
return x.Pos, x.Pos.add(")")
|
||||
}
|
||||
|
||||
// An input represents a single input file being parsed.
|
||||
type input struct {
|
||||
// Lexing state.
|
||||
filename string // name of input file, for errors
|
||||
complete []byte // entire input
|
||||
remaining []byte // remaining input
|
||||
tokenStart []byte // token being scanned to end of input
|
||||
token token // next token to be returned by lex, peek
|
||||
pos Position // current input position
|
||||
comments []Comment // accumulated comments
|
||||
|
||||
// Parser state.
|
||||
file *FileSyntax // returned top-level syntax tree
|
||||
parseErrors ErrorList // errors encountered during parsing
|
||||
|
||||
// Comment assignment state.
|
||||
pre []Expr // all expressions, in preorder traversal
|
||||
post []Expr // all expressions, in postorder traversal
|
||||
}
|
||||
|
||||
func newInput(filename string, data []byte) *input {
|
||||
return &input{
|
||||
filename: filename,
|
||||
complete: data,
|
||||
remaining: data,
|
||||
pos: Position{Line: 1, LineRune: 1, Byte: 0},
|
||||
}
|
||||
}
|
||||
|
||||
// parse parses the input file.
|
||||
func parse(file string, data []byte) (f *FileSyntax, err error) {
|
||||
// The parser panics for both routine errors like syntax errors
|
||||
// and for programmer bugs like array index errors.
|
||||
// Turn both into error returns. Catching bug panics is
|
||||
// especially important when processing many files.
|
||||
in := newInput(file, data)
|
||||
defer func() {
|
||||
if e := recover(); e != nil && e != &in.parseErrors {
|
||||
in.parseErrors = append(in.parseErrors, Error{
|
||||
Filename: in.filename,
|
||||
Pos: in.pos,
|
||||
Err: fmt.Errorf("internal error: %v", e),
|
||||
})
|
||||
}
|
||||
if err == nil && len(in.parseErrors) > 0 {
|
||||
err = in.parseErrors
|
||||
}
|
||||
}()
|
||||
|
||||
// Prime the lexer by reading in the first token. It will be available
|
||||
// in the next peek() or lex() call.
|
||||
in.readToken()
|
||||
|
||||
// Invoke the parser.
|
||||
in.parseFile()
|
||||
if len(in.parseErrors) > 0 {
|
||||
return nil, in.parseErrors
|
||||
}
|
||||
in.file.Name = in.filename
|
||||
|
||||
// Assign comments to nearby syntax.
|
||||
in.assignComments()
|
||||
|
||||
return in.file, nil
|
||||
}
|
||||
|
||||
// Error is called to report an error.
|
||||
// Error does not return: it panics.
|
||||
func (in *input) Error(s string) {
|
||||
in.parseErrors = append(in.parseErrors, Error{
|
||||
Filename: in.filename,
|
||||
Pos: in.pos,
|
||||
Err: errors.New(s),
|
||||
})
|
||||
panic(&in.parseErrors)
|
||||
}
|
||||
|
||||
// eof reports whether the input has reached end of file.
|
||||
func (in *input) eof() bool {
|
||||
return len(in.remaining) == 0
|
||||
}
|
||||
|
||||
// peekRune returns the next rune in the input without consuming it.
|
||||
func (in *input) peekRune() int {
|
||||
if len(in.remaining) == 0 {
|
||||
return 0
|
||||
}
|
||||
r, _ := utf8.DecodeRune(in.remaining)
|
||||
return int(r)
|
||||
}
|
||||
|
||||
// peekPrefix reports whether the remaining input begins with the given prefix.
|
||||
func (in *input) peekPrefix(prefix string) bool {
|
||||
// This is like bytes.HasPrefix(in.remaining, []byte(prefix))
|
||||
// but without the allocation of the []byte copy of prefix.
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if i >= len(in.remaining) || in.remaining[i] != prefix[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// readRune consumes and returns the next rune in the input.
|
||||
func (in *input) readRune() int {
|
||||
if len(in.remaining) == 0 {
|
||||
in.Error("internal lexer error: readRune at EOF")
|
||||
}
|
||||
r, size := utf8.DecodeRune(in.remaining)
|
||||
in.remaining = in.remaining[size:]
|
||||
if r == '\n' {
|
||||
in.pos.Line++
|
||||
in.pos.LineRune = 1
|
||||
} else {
|
||||
in.pos.LineRune++
|
||||
}
|
||||
in.pos.Byte += size
|
||||
return int(r)
|
||||
}
|
||||
|
||||
type token struct {
|
||||
kind tokenKind
|
||||
pos Position
|
||||
endPos Position
|
||||
text string
|
||||
}
|
||||
|
||||
type tokenKind int
|
||||
|
||||
const (
|
||||
_EOF tokenKind = -(iota + 1)
|
||||
_EOLCOMMENT
|
||||
_IDENT
|
||||
_STRING
|
||||
_COMMENT
|
||||
|
||||
// newlines and punctuation tokens are allowed as ASCII codes.
|
||||
)
|
||||
|
||||
func (k tokenKind) isComment() bool {
|
||||
return k == _COMMENT || k == _EOLCOMMENT
|
||||
}
|
||||
|
||||
// isEOL returns whether a token terminates a line.
|
||||
func (k tokenKind) isEOL() bool {
|
||||
return k == _EOF || k == _EOLCOMMENT || k == '\n'
|
||||
}
|
||||
|
||||
// startToken marks the beginning of the next input token.
|
||||
// It must be followed by a call to endToken, once the token's text has
|
||||
// been consumed using readRune.
|
||||
func (in *input) startToken() {
|
||||
in.tokenStart = in.remaining
|
||||
in.token.text = ""
|
||||
in.token.pos = in.pos
|
||||
}
|
||||
|
||||
// endToken marks the end of an input token.
|
||||
// It records the actual token string in tok.text.
|
||||
// A single trailing newline (LF or CRLF) will be removed from comment tokens.
|
||||
func (in *input) endToken(kind tokenKind) {
|
||||
in.token.kind = kind
|
||||
text := string(in.tokenStart[:len(in.tokenStart)-len(in.remaining)])
|
||||
if kind.isComment() {
|
||||
if strings.HasSuffix(text, "\r\n") {
|
||||
text = text[:len(text)-2]
|
||||
} else {
|
||||
text = strings.TrimSuffix(text, "\n")
|
||||
}
|
||||
}
|
||||
in.token.text = text
|
||||
in.token.endPos = in.pos
|
||||
}
|
||||
|
||||
// peek returns the kind of the next token returned by lex.
|
||||
func (in *input) peek() tokenKind {
|
||||
return in.token.kind
|
||||
}
|
||||
|
||||
// lex is called from the parser to obtain the next input token.
|
||||
func (in *input) lex() token {
|
||||
tok := in.token
|
||||
in.readToken()
|
||||
return tok
|
||||
}
|
||||
|
||||
// readToken lexes the next token from the text and stores it in in.token.
|
||||
func (in *input) readToken() {
|
||||
// Skip past spaces, stopping at non-space or EOF.
|
||||
for !in.eof() {
|
||||
c := in.peekRune()
|
||||
if c == ' ' || c == '\t' || c == '\r' {
|
||||
in.readRune()
|
||||
continue
|
||||
}
|
||||
|
||||
// Comment runs to end of line.
|
||||
if in.peekPrefix("//") {
|
||||
in.startToken()
|
||||
|
||||
// Is this comment the only thing on its line?
|
||||
// Find the last \n before this // and see if it's all
|
||||
// spaces from there to here.
|
||||
i := bytes.LastIndex(in.complete[:in.pos.Byte], []byte("\n"))
|
||||
suffix := len(bytes.TrimSpace(in.complete[i+1:in.pos.Byte])) > 0
|
||||
in.readRune()
|
||||
in.readRune()
|
||||
|
||||
// Consume comment.
|
||||
for len(in.remaining) > 0 && in.readRune() != '\n' {
|
||||
}
|
||||
|
||||
// If we are at top level (not in a statement), hand the comment to
|
||||
// the parser as a _COMMENT token. The grammar is written
|
||||
// to handle top-level comments itself.
|
||||
if !suffix {
|
||||
in.endToken(_COMMENT)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, save comment for later attachment to syntax tree.
|
||||
in.endToken(_EOLCOMMENT)
|
||||
in.comments = append(in.comments, Comment{in.token.pos, in.token.text, suffix})
|
||||
return
|
||||
}
|
||||
|
||||
if in.peekPrefix("/*") {
|
||||
in.Error("mod files must use // comments (not /* */ comments)")
|
||||
}
|
||||
|
||||
// Found non-space non-comment.
|
||||
break
|
||||
}
|
||||
|
||||
// Found the beginning of the next token.
|
||||
in.startToken()
|
||||
|
||||
// End of file.
|
||||
if in.eof() {
|
||||
in.endToken(_EOF)
|
||||
return
|
||||
}
|
||||
|
||||
// Punctuation tokens.
|
||||
switch c := in.peekRune(); c {
|
||||
case '\n', '(', ')', '[', ']', '{', '}', ',':
|
||||
in.readRune()
|
||||
in.endToken(tokenKind(c))
|
||||
return
|
||||
|
||||
case '"', '`': // quoted string
|
||||
quote := c
|
||||
in.readRune()
|
||||
for {
|
||||
if in.eof() {
|
||||
in.pos = in.token.pos
|
||||
in.Error("unexpected EOF in string")
|
||||
}
|
||||
if in.peekRune() == '\n' {
|
||||
in.Error("unexpected newline in string")
|
||||
}
|
||||
c := in.readRune()
|
||||
if c == quote {
|
||||
break
|
||||
}
|
||||
if c == '\\' && quote != '`' {
|
||||
if in.eof() {
|
||||
in.pos = in.token.pos
|
||||
in.Error("unexpected EOF in string")
|
||||
}
|
||||
in.readRune()
|
||||
}
|
||||
}
|
||||
in.endToken(_STRING)
|
||||
return
|
||||
}
|
||||
|
||||
// Checked all punctuation. Must be identifier token.
|
||||
if c := in.peekRune(); !isIdent(c) {
|
||||
in.Error(fmt.Sprintf("unexpected input character %#q", c))
|
||||
}
|
||||
|
||||
// Scan over identifier.
|
||||
for isIdent(in.peekRune()) {
|
||||
if in.peekPrefix("//") {
|
||||
break
|
||||
}
|
||||
if in.peekPrefix("/*") {
|
||||
in.Error("mod files must use // comments (not /* */ comments)")
|
||||
}
|
||||
in.readRune()
|
||||
}
|
||||
in.endToken(_IDENT)
|
||||
}
|
||||
|
||||
// isIdent reports whether c is an identifier rune.
|
||||
// We treat most printable runes as identifier runes, except for a handful of
|
||||
// ASCII punctuation characters.
|
||||
func isIdent(c int) bool {
|
||||
switch r := rune(c); r {
|
||||
case ' ', '(', ')', '[', ']', '{', '}', ',':
|
||||
return false
|
||||
default:
|
||||
return !unicode.IsSpace(r) && unicode.IsPrint(r)
|
||||
}
|
||||
}
|
||||
|
||||
// Comment assignment.
|
||||
// We build two lists of all subexpressions, preorder and postorder.
|
||||
// The preorder list is ordered by start location, with outer expressions first.
|
||||
// The postorder list is ordered by end location, with outer expressions last.
|
||||
// We use the preorder list to assign each whole-line comment to the syntax
|
||||
// immediately following it, and we use the postorder list to assign each
|
||||
// end-of-line comment to the syntax immediately preceding it.
|
||||
|
||||
// order walks the expression adding it and its subexpressions to the
|
||||
// preorder and postorder lists.
|
||||
func (in *input) order(x Expr) {
|
||||
if x != nil {
|
||||
in.pre = append(in.pre, x)
|
||||
}
|
||||
switch x := x.(type) {
|
||||
default:
|
||||
panic(fmt.Errorf("order: unexpected type %T", x))
|
||||
case nil:
|
||||
// nothing
|
||||
case *LParen, *RParen:
|
||||
// nothing
|
||||
case *CommentBlock:
|
||||
// nothing
|
||||
case *Line:
|
||||
// nothing
|
||||
case *FileSyntax:
|
||||
for _, stmt := range x.Stmt {
|
||||
in.order(stmt)
|
||||
}
|
||||
case *LineBlock:
|
||||
in.order(&x.LParen)
|
||||
for _, l := range x.Line {
|
||||
in.order(l)
|
||||
}
|
||||
in.order(&x.RParen)
|
||||
}
|
||||
if x != nil {
|
||||
in.post = append(in.post, x)
|
||||
}
|
||||
}
|
||||
|
||||
// assignComments attaches comments to nearby syntax.
|
||||
func (in *input) assignComments() {
|
||||
const debug = false
|
||||
|
||||
// Generate preorder and postorder lists.
|
||||
in.order(in.file)
|
||||
|
||||
// Split into whole-line comments and suffix comments.
|
||||
var line, suffix []Comment
|
||||
for _, com := range in.comments {
|
||||
if com.Suffix {
|
||||
suffix = append(suffix, com)
|
||||
} else {
|
||||
line = append(line, com)
|
||||
}
|
||||
}
|
||||
|
||||
if debug {
|
||||
for _, c := range line {
|
||||
fmt.Fprintf(os.Stderr, "LINE %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
|
||||
}
|
||||
}
|
||||
|
||||
// Assign line comments to syntax immediately following.
|
||||
for _, x := range in.pre {
|
||||
start, _ := x.Span()
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "pre %T :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte)
|
||||
}
|
||||
xcom := x.Comment()
|
||||
for len(line) > 0 && start.Byte >= line[0].Start.Byte {
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "ASSIGN LINE %q #%d\n", line[0].Token, line[0].Start.Byte)
|
||||
}
|
||||
xcom.Before = append(xcom.Before, line[0])
|
||||
line = line[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// Remaining line comments go at end of file.
|
||||
in.file.After = append(in.file.After, line...)
|
||||
|
||||
if debug {
|
||||
for _, c := range suffix {
|
||||
fmt.Fprintf(os.Stderr, "SUFFIX %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
|
||||
}
|
||||
}
|
||||
|
||||
// Assign suffix comments to syntax immediately before.
|
||||
for i := len(in.post) - 1; i >= 0; i-- {
|
||||
x := in.post[i]
|
||||
|
||||
start, end := x.Span()
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "post %T :%d:%d #%d :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte, end.Line, end.LineRune, end.Byte)
|
||||
}
|
||||
|
||||
// Do not assign suffix comments to end of line block or whole file.
|
||||
// Instead assign them to the last element inside.
|
||||
switch x.(type) {
|
||||
case *FileSyntax:
|
||||
continue
|
||||
}
|
||||
|
||||
// Do not assign suffix comments to something that starts
|
||||
// on an earlier line, so that in
|
||||
//
|
||||
// x ( y
|
||||
// z ) // comment
|
||||
//
|
||||
// we assign the comment to z and not to x ( ... ).
|
||||
if start.Line != end.Line {
|
||||
continue
|
||||
}
|
||||
xcom := x.Comment()
|
||||
for len(suffix) > 0 && end.Byte <= suffix[len(suffix)-1].Start.Byte {
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "ASSIGN SUFFIX %q #%d\n", suffix[len(suffix)-1].Token, suffix[len(suffix)-1].Start.Byte)
|
||||
}
|
||||
xcom.Suffix = append(xcom.Suffix, suffix[len(suffix)-1])
|
||||
suffix = suffix[:len(suffix)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// We assigned suffix comments in reverse.
|
||||
// If multiple suffix comments were appended to the same
|
||||
// expression node, they are now in reverse. Fix that.
|
||||
for _, x := range in.post {
|
||||
reverseComments(x.Comment().Suffix)
|
||||
}
|
||||
|
||||
// Remaining suffix comments go at beginning of file.
|
||||
in.file.Before = append(in.file.Before, suffix...)
|
||||
}
|
||||
|
||||
// reverseComments reverses the []Comment list.
|
||||
func reverseComments(list []Comment) {
|
||||
for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
|
||||
list[i], list[j] = list[j], list[i]
|
||||
}
|
||||
}
|
||||
|
||||
func (in *input) parseFile() {
|
||||
in.file = new(FileSyntax)
|
||||
var cb *CommentBlock
|
||||
for {
|
||||
switch in.peek() {
|
||||
case '\n':
|
||||
in.lex()
|
||||
if cb != nil {
|
||||
in.file.Stmt = append(in.file.Stmt, cb)
|
||||
cb = nil
|
||||
}
|
||||
case _COMMENT:
|
||||
tok := in.lex()
|
||||
if cb == nil {
|
||||
cb = &CommentBlock{Start: tok.pos}
|
||||
}
|
||||
com := cb.Comment()
|
||||
com.Before = append(com.Before, Comment{Start: tok.pos, Token: tok.text})
|
||||
case _EOF:
|
||||
if cb != nil {
|
||||
in.file.Stmt = append(in.file.Stmt, cb)
|
||||
}
|
||||
return
|
||||
default:
|
||||
in.parseStmt()
|
||||
if cb != nil {
|
||||
in.file.Stmt[len(in.file.Stmt)-1].Comment().Before = cb.Before
|
||||
cb = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (in *input) parseStmt() {
|
||||
tok := in.lex()
|
||||
start := tok.pos
|
||||
end := tok.endPos
|
||||
tokens := []string{tok.text}
|
||||
for {
|
||||
tok := in.lex()
|
||||
switch {
|
||||
case tok.kind.isEOL():
|
||||
in.file.Stmt = append(in.file.Stmt, &Line{
|
||||
Start: start,
|
||||
Token: tokens,
|
||||
End: end,
|
||||
})
|
||||
return
|
||||
|
||||
case tok.kind == '(':
|
||||
if next := in.peek(); next.isEOL() {
|
||||
// Start of block: no more tokens on this line.
|
||||
in.file.Stmt = append(in.file.Stmt, in.parseLineBlock(start, tokens, tok))
|
||||
return
|
||||
} else if next == ')' {
|
||||
rparen := in.lex()
|
||||
if in.peek().isEOL() {
|
||||
// Empty block.
|
||||
in.lex()
|
||||
in.file.Stmt = append(in.file.Stmt, &LineBlock{
|
||||
Start: start,
|
||||
Token: tokens,
|
||||
LParen: LParen{Pos: tok.pos},
|
||||
RParen: RParen{Pos: rparen.pos},
|
||||
})
|
||||
return
|
||||
}
|
||||
// '( )' in the middle of the line, not a block.
|
||||
tokens = append(tokens, tok.text, rparen.text)
|
||||
} else {
|
||||
// '(' in the middle of the line, not a block.
|
||||
tokens = append(tokens, tok.text)
|
||||
}
|
||||
|
||||
default:
|
||||
tokens = append(tokens, tok.text)
|
||||
end = tok.endPos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (in *input) parseLineBlock(start Position, token []string, lparen token) *LineBlock {
|
||||
x := &LineBlock{
|
||||
Start: start,
|
||||
Token: token,
|
||||
LParen: LParen{Pos: lparen.pos},
|
||||
}
|
||||
var comments []Comment
|
||||
for {
|
||||
switch in.peek() {
|
||||
case _EOLCOMMENT:
|
||||
// Suffix comment, will be attached later by assignComments.
|
||||
in.lex()
|
||||
case '\n':
|
||||
// Blank line. Add an empty comment to preserve it.
|
||||
in.lex()
|
||||
if len(comments) == 0 && len(x.Line) > 0 || len(comments) > 0 && comments[len(comments)-1].Token != "" {
|
||||
comments = append(comments, Comment{})
|
||||
}
|
||||
case _COMMENT:
|
||||
tok := in.lex()
|
||||
comments = append(comments, Comment{Start: tok.pos, Token: tok.text})
|
||||
case _EOF:
|
||||
in.Error(fmt.Sprintf("syntax error (unterminated block started at %s:%d:%d)", in.filename, x.Start.Line, x.Start.LineRune))
|
||||
case ')':
|
||||
rparen := in.lex()
|
||||
// Don't preserve blank lines (denoted by a single empty comment, added above)
|
||||
// at the end of the block.
|
||||
if len(comments) == 1 && comments[0] == (Comment{}) {
|
||||
comments = nil
|
||||
}
|
||||
x.RParen.Before = comments
|
||||
x.RParen.Pos = rparen.pos
|
||||
if !in.peek().isEOL() {
|
||||
in.Error("syntax error (expected newline after closing paren)")
|
||||
}
|
||||
in.lex()
|
||||
return x
|
||||
default:
|
||||
l := in.parseLine()
|
||||
x.Line = append(x.Line, l)
|
||||
l.Comment().Before = comments
|
||||
comments = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (in *input) parseLine() *Line {
|
||||
tok := in.lex()
|
||||
if tok.kind.isEOL() {
|
||||
in.Error("internal parse error: parseLine at end of line")
|
||||
}
|
||||
start := tok.pos
|
||||
end := tok.endPos
|
||||
tokens := []string{tok.text}
|
||||
for {
|
||||
tok := in.lex()
|
||||
if tok.kind.isEOL() {
|
||||
return &Line{
|
||||
Start: start,
|
||||
Token: tokens,
|
||||
End: end,
|
||||
InBlock: true,
|
||||
}
|
||||
}
|
||||
tokens = append(tokens, tok.text)
|
||||
end = tok.endPos
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
slashSlash = []byte("//")
|
||||
moduleStr = []byte("module")
|
||||
)
|
||||
|
||||
// ModulePath returns the module path from the gomod file text.
|
||||
// If it cannot find a module path, it returns an empty string.
|
||||
// It is tolerant of unrelated problems in the go.mod file.
|
||||
func ModulePath(mod []byte) string {
|
||||
for len(mod) > 0 {
|
||||
line := mod
|
||||
mod = nil
|
||||
if i := bytes.IndexByte(line, '\n'); i >= 0 {
|
||||
line, mod = line[:i], line[i+1:]
|
||||
}
|
||||
if i := bytes.Index(line, slashSlash); i >= 0 {
|
||||
line = line[:i]
|
||||
}
|
||||
line = bytes.TrimSpace(line)
|
||||
if !bytes.HasPrefix(line, moduleStr) {
|
||||
continue
|
||||
}
|
||||
line = line[len(moduleStr):]
|
||||
n := len(line)
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == n || len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if line[0] == '"' || line[0] == '`' {
|
||||
p, err := strconv.Unquote(string(line))
|
||||
if err != nil {
|
||||
return "" // malformed quoted string or multiline module path
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
return string(line)
|
||||
}
|
||||
return "" // missing module path
|
||||
}
|
||||
-1904
File diff suppressed because it is too large
Load Diff
-333
@@ -1,333 +0,0 @@
|
||||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package modfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A WorkFile is the parsed, interpreted form of a go.work file.
|
||||
type WorkFile struct {
|
||||
Go *Go
|
||||
Toolchain *Toolchain
|
||||
Godebug []*Godebug
|
||||
Use []*Use
|
||||
Replace []*Replace
|
||||
|
||||
Syntax *FileSyntax
|
||||
}
|
||||
|
||||
// A Use is a single directory statement.
|
||||
type Use struct {
|
||||
Path string // Use path of module.
|
||||
ModulePath string // Module path in the comment.
|
||||
Syntax *Line
|
||||
}
|
||||
|
||||
// ParseWork parses and returns a go.work file.
|
||||
//
|
||||
// file is the name of the file, used in positions and errors.
|
||||
//
|
||||
// data is the content of the file.
|
||||
//
|
||||
// fix is an optional function that canonicalizes module versions.
|
||||
// If fix is nil, all module versions must be canonical ([module.CanonicalVersion]
|
||||
// must return the same string).
|
||||
func ParseWork(file string, data []byte, fix VersionFixer) (*WorkFile, error) {
|
||||
fs, err := parse(file, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := &WorkFile{
|
||||
Syntax: fs,
|
||||
}
|
||||
var errs ErrorList
|
||||
|
||||
for _, x := range fs.Stmt {
|
||||
switch x := x.(type) {
|
||||
case *Line:
|
||||
f.add(&errs, x, x.Token[0], x.Token[1:], fix)
|
||||
|
||||
case *LineBlock:
|
||||
if len(x.Token) > 1 {
|
||||
errs = append(errs, Error{
|
||||
Filename: file,
|
||||
Pos: x.Start,
|
||||
Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
|
||||
})
|
||||
continue
|
||||
}
|
||||
switch x.Token[0] {
|
||||
default:
|
||||
errs = append(errs, Error{
|
||||
Filename: file,
|
||||
Pos: x.Start,
|
||||
Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
|
||||
})
|
||||
continue
|
||||
case "godebug", "use", "replace":
|
||||
for _, l := range x.Line {
|
||||
f.add(&errs, l, x.Token[0], l.Token, fix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Cleanup cleans up the file f after any edit operations.
|
||||
// To avoid quadratic behavior, modifications like [WorkFile.DropRequire]
|
||||
// clear the entry but do not remove it from the slice.
|
||||
// Cleanup cleans out all the cleared entries.
|
||||
func (f *WorkFile) Cleanup() {
|
||||
w := 0
|
||||
for _, r := range f.Use {
|
||||
if r.Path != "" {
|
||||
f.Use[w] = r
|
||||
w++
|
||||
}
|
||||
}
|
||||
f.Use = f.Use[:w]
|
||||
|
||||
w = 0
|
||||
for _, r := range f.Replace {
|
||||
if r.Old.Path != "" {
|
||||
f.Replace[w] = r
|
||||
w++
|
||||
}
|
||||
}
|
||||
f.Replace = f.Replace[:w]
|
||||
|
||||
f.Syntax.Cleanup()
|
||||
}
|
||||
|
||||
func (f *WorkFile) AddGoStmt(version string) error {
|
||||
if !GoVersionRE.MatchString(version) {
|
||||
return fmt.Errorf("invalid language version %q", version)
|
||||
}
|
||||
if f.Go == nil {
|
||||
stmt := &Line{Token: []string{"go", version}}
|
||||
f.Go = &Go{
|
||||
Version: version,
|
||||
Syntax: stmt,
|
||||
}
|
||||
// Find the first non-comment-only block and add
|
||||
// the go statement before it. That will keep file comments at the top.
|
||||
i := 0
|
||||
for i = 0; i < len(f.Syntax.Stmt); i++ {
|
||||
if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...)
|
||||
} else {
|
||||
f.Go.Version = version
|
||||
f.Syntax.updateLine(f.Go.Syntax, "go", version)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *WorkFile) AddToolchainStmt(name string) error {
|
||||
if !ToolchainRE.MatchString(name) {
|
||||
return fmt.Errorf("invalid toolchain name %q", name)
|
||||
}
|
||||
if f.Toolchain == nil {
|
||||
stmt := &Line{Token: []string{"toolchain", name}}
|
||||
f.Toolchain = &Toolchain{
|
||||
Name: name,
|
||||
Syntax: stmt,
|
||||
}
|
||||
// Find the go line and add the toolchain line after it.
|
||||
// Or else find the first non-comment-only block and add
|
||||
// the toolchain line before it. That will keep file comments at the top.
|
||||
i := 0
|
||||
for i = 0; i < len(f.Syntax.Stmt); i++ {
|
||||
if line, ok := f.Syntax.Stmt[i].(*Line); ok && len(line.Token) > 0 && line.Token[0] == "go" {
|
||||
i++
|
||||
goto Found
|
||||
}
|
||||
}
|
||||
for i = 0; i < len(f.Syntax.Stmt); i++ {
|
||||
if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
Found:
|
||||
f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...)
|
||||
} else {
|
||||
f.Toolchain.Name = name
|
||||
f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DropGoStmt deletes the go statement from the file.
|
||||
func (f *WorkFile) DropGoStmt() {
|
||||
if f.Go != nil {
|
||||
f.Go.Syntax.markRemoved()
|
||||
f.Go = nil
|
||||
}
|
||||
}
|
||||
|
||||
// DropToolchainStmt deletes the toolchain statement from the file.
|
||||
func (f *WorkFile) DropToolchainStmt() {
|
||||
if f.Toolchain != nil {
|
||||
f.Toolchain.Syntax.markRemoved()
|
||||
f.Toolchain = nil
|
||||
}
|
||||
}
|
||||
|
||||
// AddGodebug sets the first godebug line for key to value,
|
||||
// preserving any existing comments for that line and removing all
|
||||
// other godebug lines for key.
|
||||
//
|
||||
// If no line currently exists for key, AddGodebug adds a new line
|
||||
// at the end of the last godebug block.
|
||||
func (f *WorkFile) AddGodebug(key, value string) error {
|
||||
need := true
|
||||
for _, g := range f.Godebug {
|
||||
if g.Key == key {
|
||||
if need {
|
||||
g.Value = value
|
||||
f.Syntax.updateLine(g.Syntax, "godebug", key+"="+value)
|
||||
need = false
|
||||
} else {
|
||||
g.Syntax.markRemoved()
|
||||
*g = Godebug{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if need {
|
||||
f.addNewGodebug(key, value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// addNewGodebug adds a new godebug key=value line at the end
|
||||
// of the last godebug block, regardless of any existing godebug lines for key.
|
||||
func (f *WorkFile) addNewGodebug(key, value string) {
|
||||
line := f.Syntax.addLine(nil, "godebug", key+"="+value)
|
||||
g := &Godebug{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Syntax: line,
|
||||
}
|
||||
f.Godebug = append(f.Godebug, g)
|
||||
}
|
||||
|
||||
func (f *WorkFile) DropGodebug(key string) error {
|
||||
for _, g := range f.Godebug {
|
||||
if g.Key == key {
|
||||
g.Syntax.markRemoved()
|
||||
*g = Godebug{}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *WorkFile) AddUse(diskPath, modulePath string) error {
|
||||
need := true
|
||||
for _, d := range f.Use {
|
||||
if d.Path == diskPath {
|
||||
if need {
|
||||
d.ModulePath = modulePath
|
||||
f.Syntax.updateLine(d.Syntax, "use", AutoQuote(diskPath))
|
||||
need = false
|
||||
} else {
|
||||
d.Syntax.markRemoved()
|
||||
*d = Use{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if need {
|
||||
f.AddNewUse(diskPath, modulePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *WorkFile) AddNewUse(diskPath, modulePath string) {
|
||||
line := f.Syntax.addLine(nil, "use", AutoQuote(diskPath))
|
||||
f.Use = append(f.Use, &Use{Path: diskPath, ModulePath: modulePath, Syntax: line})
|
||||
}
|
||||
|
||||
func (f *WorkFile) SetUse(dirs []*Use) {
|
||||
need := make(map[string]string)
|
||||
for _, d := range dirs {
|
||||
need[d.Path] = d.ModulePath
|
||||
}
|
||||
|
||||
for _, d := range f.Use {
|
||||
if modulePath, ok := need[d.Path]; ok {
|
||||
d.ModulePath = modulePath
|
||||
} else {
|
||||
d.Syntax.markRemoved()
|
||||
*d = Use{}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(#45713): Add module path to comment.
|
||||
|
||||
for diskPath, modulePath := range need {
|
||||
f.AddNewUse(diskPath, modulePath)
|
||||
}
|
||||
f.SortBlocks()
|
||||
}
|
||||
|
||||
func (f *WorkFile) DropUse(path string) error {
|
||||
for _, d := range f.Use {
|
||||
if d.Path == path {
|
||||
d.Syntax.markRemoved()
|
||||
*d = Use{}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *WorkFile) AddReplace(oldPath, oldVers, newPath, newVers string) error {
|
||||
return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers)
|
||||
}
|
||||
|
||||
func (f *WorkFile) DropReplace(oldPath, oldVers string) error {
|
||||
for _, r := range f.Replace {
|
||||
if r.Old.Path == oldPath && r.Old.Version == oldVers {
|
||||
r.Syntax.markRemoved()
|
||||
*r = Replace{}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *WorkFile) SortBlocks() {
|
||||
f.removeDups() // otherwise sorting is unsafe
|
||||
|
||||
for _, stmt := range f.Syntax.Stmt {
|
||||
block, ok := stmt.(*LineBlock)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
slices.SortStableFunc(block.Line, compareLine)
|
||||
}
|
||||
}
|
||||
|
||||
// removeDups removes duplicate replace directives.
|
||||
//
|
||||
// Later replace directives take priority.
|
||||
//
|
||||
// require directives are not de-duplicated. That's left up to higher-level
|
||||
// logic (MVS).
|
||||
//
|
||||
// retract directives are not de-duplicated since comments are
|
||||
// meaningful, and versions may be retracted multiple times.
|
||||
func (f *WorkFile) removeDups() {
|
||||
removeDups(f.Syntax, nil, &f.Replace, nil, nil)
|
||||
}
|
||||
Vendored
+3
-5
@@ -86,14 +86,15 @@ github.com/powerman/deepequal
|
||||
# github.com/quic-go/qpack v0.5.1
|
||||
## explicit; go 1.22
|
||||
github.com/quic-go/qpack
|
||||
# github.com/quic-go/quic-go v0.54.1
|
||||
## explicit; go 1.23
|
||||
# github.com/quic-go/quic-go v0.55.0
|
||||
## explicit; go 1.24
|
||||
github.com/quic-go/quic-go
|
||||
github.com/quic-go/quic-go/http3
|
||||
github.com/quic-go/quic-go/internal/ackhandler
|
||||
github.com/quic-go/quic-go/internal/congestion
|
||||
github.com/quic-go/quic-go/internal/flowcontrol
|
||||
github.com/quic-go/quic-go/internal/handshake
|
||||
github.com/quic-go/quic-go/internal/monotime
|
||||
github.com/quic-go/quic-go/internal/protocol
|
||||
github.com/quic-go/quic-go/internal/qerr
|
||||
github.com/quic-go/quic-go/internal/utils
|
||||
@@ -108,8 +109,6 @@ github.com/smartystreets/goconvey/convey/gotest
|
||||
github.com/smartystreets/goconvey/convey/reporting
|
||||
# go.uber.org/mock v0.6.0
|
||||
## explicit; go 1.23.0
|
||||
go.uber.org/mock/mockgen
|
||||
go.uber.org/mock/mockgen/model
|
||||
# golang.org/x/crypto v0.42.0
|
||||
## explicit; go 1.24.0
|
||||
golang.org/x/crypto/blake2b
|
||||
@@ -127,7 +126,6 @@ golang.org/x/crypto/salsa20/salsa
|
||||
# golang.org/x/mod v0.28.0
|
||||
## explicit; go 1.24.0
|
||||
golang.org/x/mod/internal/lazyregexp
|
||||
golang.org/x/mod/modfile
|
||||
golang.org/x/mod/module
|
||||
golang.org/x/mod/semver
|
||||
# golang.org/x/net v0.44.0
|
||||
|
||||
Reference in New Issue
Block a user