Update quic-go

This commit is contained in:
Frank Denis
2025-10-03 23:46:10 +02:00
parent c5b762767f
commit 5c5b44cb30
82 changed files with 1145 additions and 7256 deletions
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
@@ -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
View File
@@ -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
View File
@@ -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
}
@@ -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:
@@ -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 {
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()
@@ -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
}
@@ -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
View File
@@ -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
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,7 +0,0 @@
//go:build tools
package quic
import (
_ "go.uber.org/mock/mockgen"
)
-12
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
-333
View File
@@ -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)
}
+3 -5
View File
@@ -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