From 5c5b44cb308243fdcfceaa0cfa71025743b566b2 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Fri, 3 Oct 2025 23:46:10 +0200 Subject: [PATCH] Update quic-go --- go.mod | 2 +- go.sum | 4 +- vendor/github.com/quic-go/quic-go/codecov.yml | 1 + .../quic-go/quic-go/conn_id_generator.go | 15 +- .../github.com/quic-go/quic-go/connection.go | 261 ++- .../quic-go/quic-go/connection_timer.go | 54 - vendor/github.com/quic-go/quic-go/framer.go | 8 +- .../quic-go/quic-go/http3/frames.go | 2 +- .../quic-go/quic-go/http3/mockgen.go | 6 +- .../quic-go/quic-go/http3/server.go | 2 +- .../quic-go/quic-go/http3/transport.go | 28 +- .../quic-go/internal/ackhandler/ackhandler.go | 3 +- .../quic-go/internal/ackhandler/ecn.go | 4 +- .../quic-go/internal/ackhandler/interfaces.go | 31 +- .../ackhandler/lost_packet_tracker.go | 73 + .../quic-go/internal/ackhandler/mockgen.go | 4 +- .../quic-go/internal/ackhandler/packet.go | 18 +- .../ackhandler/received_packet_handler.go | 12 +- .../ackhandler/received_packet_history.go | 44 +- .../ackhandler/received_packet_tracker.go | 40 +- .../ackhandler/sent_packet_handler.go | 241 ++- .../ackhandler/sent_packet_history.go | 125 +- .../quic-go/internal/congestion/clock.go | 10 +- .../quic-go/internal/congestion/cubic.go | 11 +- .../internal/congestion/cubic_sender.go | 25 +- .../quic-go/internal/congestion/interface.go | 11 +- .../quic-go/internal/congestion/pacer.go | 13 +- .../flowcontrol/base_flow_controller.go | 9 +- .../flowcontrol/connection_flow_controller.go | 8 +- .../quic-go/internal/flowcontrol/interface.go | 11 +- .../flowcontrol/stream_flow_controller.go | 6 +- .../quic-go/internal/handshake/interface.go | 4 +- .../internal/handshake/updatable_aead.go | 14 +- .../quic-go/quic-go/internal/monotime/time.go | 90 + .../internal/protocol/connection_id.go | 6 +- .../quic-go/internal/protocol/protocol.go | 3 + .../quic-go/internal/utils/connstats.go | 14 + .../quic-go/quic-go/internal/utils/log.go | 14 +- .../quic-go/internal/utils/rtt_stats.go | 77 +- .../quic-go/quic-go/internal/utils/timer.go | 57 - .../internal/wire/ack_frequency_frame.go | 65 + .../quic-go/internal/wire/frame_parser.go | 11 +- .../quic-go/internal/wire/frame_type.go | 6 +- .../internal/wire/immediate_ack_frame.go | 18 + .../quic-go/quic-go/internal/wire/pool.go | 2 +- .../internal/wire/transport_parameters.go | 31 +- .../quic-go/logging/connection_tracer.go | 1 + .../logging/connection_tracer_multiplexer.go | 7 + .../quic-go/quic-go/logging/frame.go | 6 +- vendor/github.com/quic-go/quic-go/mockgen.go | 30 +- .../quic-go/quic-go/mtu_discoverer.go | 21 +- vendor/github.com/quic-go/quic-go/oss-fuzz.sh | 6 +- .../quic-go/quic-go/packet_packer.go | 36 +- .../quic-go/quic-go/packet_unpacker.go | 6 +- .../quic-go/quic-go/path_manager.go | 5 +- .../quic-go/quic-go/path_manager_outgoing.go | 3 + .../quic-go/quic-go/quicvarint/io.go | 7 +- .../quic-go/quic-go/receive_stream.go | 28 +- .../github.com/quic-go/quic-go/send_stream.go | 44 +- vendor/github.com/quic-go/quic-go/server.go | 16 +- vendor/github.com/quic-go/quic-go/stream.go | 7 +- .../github.com/quic-go/quic-go/streams_map.go | 10 +- vendor/github.com/quic-go/quic-go/sys_conn.go | 3 +- .../quic-go/quic-go/sys_conn_oob.go | 4 +- vendor/github.com/quic-go/quic-go/tools.go | 7 - vendor/go.uber.org/mock/AUTHORS | 12 - vendor/go.uber.org/mock/LICENSE | 202 -- vendor/go.uber.org/mock/mockgen/archive.go | 55 - vendor/go.uber.org/mock/mockgen/deprecated.go | 41 - vendor/go.uber.org/mock/mockgen/generic.go | 103 - vendor/go.uber.org/mock/mockgen/gob.go | 21 - vendor/go.uber.org/mock/mockgen/mockgen.go | 945 -------- .../go.uber.org/mock/mockgen/model/model.go | 533 ----- .../mock/mockgen/model/model_gotypes.go | 160 -- .../go.uber.org/mock/mockgen/package_mode.go | 359 ---- vendor/go.uber.org/mock/mockgen/parse.go | 805 ------- vendor/go.uber.org/mock/mockgen/version.go | 31 - vendor/golang.org/x/mod/modfile/print.go | 184 -- vendor/golang.org/x/mod/modfile/read.go | 964 --------- vendor/golang.org/x/mod/modfile/rule.go | 1904 ----------------- vendor/golang.org/x/mod/modfile/work.go | 333 --- vendor/modules.txt | 8 +- 82 files changed, 1145 insertions(+), 7256 deletions(-) delete mode 100644 vendor/github.com/quic-go/quic-go/connection_timer.go create mode 100644 vendor/github.com/quic-go/quic-go/internal/ackhandler/lost_packet_tracker.go create mode 100644 vendor/github.com/quic-go/quic-go/internal/monotime/time.go create mode 100644 vendor/github.com/quic-go/quic-go/internal/utils/connstats.go delete mode 100644 vendor/github.com/quic-go/quic-go/internal/utils/timer.go create mode 100644 vendor/github.com/quic-go/quic-go/internal/wire/ack_frequency_frame.go create mode 100644 vendor/github.com/quic-go/quic-go/internal/wire/immediate_ack_frame.go delete mode 100644 vendor/github.com/quic-go/quic-go/tools.go delete mode 100644 vendor/go.uber.org/mock/AUTHORS delete mode 100644 vendor/go.uber.org/mock/LICENSE delete mode 100644 vendor/go.uber.org/mock/mockgen/archive.go delete mode 100644 vendor/go.uber.org/mock/mockgen/deprecated.go delete mode 100644 vendor/go.uber.org/mock/mockgen/generic.go delete mode 100644 vendor/go.uber.org/mock/mockgen/gob.go delete mode 100644 vendor/go.uber.org/mock/mockgen/mockgen.go delete mode 100644 vendor/go.uber.org/mock/mockgen/model/model.go delete mode 100644 vendor/go.uber.org/mock/mockgen/model/model_gotypes.go delete mode 100644 vendor/go.uber.org/mock/mockgen/package_mode.go delete mode 100644 vendor/go.uber.org/mock/mockgen/parse.go delete mode 100644 vendor/go.uber.org/mock/mockgen/version.go delete mode 100644 vendor/golang.org/x/mod/modfile/print.go delete mode 100644 vendor/golang.org/x/mod/modfile/read.go delete mode 100644 vendor/golang.org/x/mod/modfile/rule.go delete mode 100644 vendor/golang.org/x/mod/modfile/work.go diff --git a/go.mod b/go.mod index aad9303b..13240025 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 85275785..f9e25fce 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/vendor/github.com/quic-go/quic-go/codecov.yml b/vendor/github.com/quic-go/quic-go/codecov.yml index 86ee596e..e5a166ef 100644 --- a/vendor/github.com/quic-go/quic-go/codecov.yml +++ b/vendor/github.com/quic-go/quic-go/codecov.yml @@ -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/ diff --git a/vendor/github.com/quic-go/quic-go/conn_id_generator.go b/vendor/github.com/quic-go/quic-go/conn_id_generator.go index f1a0c92c..c2b35450 100644 --- a/vendor/github.com/quic-go/quic-go/conn_id_generator.go +++ b/vendor/github.com/quic-go/quic-go/conn_id_generator.go @@ -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 } diff --git a/vendor/github.com/quic-go/quic-go/connection.go b/vendor/github.com/quic-go/quic-go/connection.go index 8d5af6f2..568086a1 100644 --- a/vendor/github.com/quic-go/quic-go/connection.go +++ b/vendor/github.com/quic-go/quic-go/connection.go @@ -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() } diff --git a/vendor/github.com/quic-go/quic-go/connection_timer.go b/vendor/github.com/quic-go/quic-go/connection_timer.go deleted file mode 100644 index 0fdc8cb8..00000000 --- a/vendor/github.com/quic-go/quic-go/connection_timer.go +++ /dev/null @@ -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() -} diff --git a/vendor/github.com/quic-go/quic-go/framer.go b/vendor/github.com/quic-go/quic-go/framer.go index a331202d..ca23f0aa 100644 --- a/vendor/github.com/quic-go/quic-go/framer.go +++ b/vendor/github.com/quic-go/quic-go/framer.go @@ -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 diff --git a/vendor/github.com/quic-go/quic-go/http3/frames.go b/vendor/github.com/quic-go/quic-go/http3/frames.go index e5149cb2..6de67262 100644 --- a/vendor/github.com/quic-go/quic-go/http3/frames.go +++ b/vendor/github.com/quic-go/quic-go/http3/frames.go @@ -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") diff --git a/vendor/github.com/quic-go/quic-go/http3/mockgen.go b/vendor/github.com/quic-go/quic-go/http3/mockgen.go index 72c85701..38a6f021 100644 --- a/vendor/github.com/quic-go/quic-go/http3/mockgen.go +++ b/vendor/github.com/quic-go/quic-go/http3/mockgen.go @@ -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" diff --git a/vendor/github.com/quic-go/quic-go/http3/server.go b/vendor/github.com/quic-go/quic-go/http3/server.go index 7e002a09..cb53c44a 100644 --- a/vendor/github.com/quic-go/quic-go/http3/server.go +++ b/vendor/github.com/quic-go/quic-go/http3/server.go @@ -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() diff --git a/vendor/github.com/quic-go/quic-go/http3/transport.go b/vendor/github.com/quic-go/quic-go/http3/transport.go index 4085faca..4be4fbdb 100644 --- a/vendor/github.com/quic-go/quic-go/http3/transport.go +++ b/vendor/github.com/quic-go/quic-go/http3/transport.go @@ -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 } diff --git a/vendor/github.com/quic-go/quic-go/internal/ackhandler/ackhandler.go b/vendor/github.com/quic-go/quic-go/internal/ackhandler/ackhandler.go index 6f890b4d..f1bac327 100644 --- a/vendor/github.com/quic-go/quic-go/internal/ackhandler/ackhandler.go +++ b/vendor/github.com/quic-go/quic-go/internal/ackhandler/ackhandler.go @@ -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) } diff --git a/vendor/github.com/quic-go/quic-go/internal/ackhandler/ecn.go b/vendor/github.com/quic-go/quic-go/internal/ackhandler/ecn.go index 68415ac6..1b462a60 100644 --- a/vendor/github.com/quic-go/quic-go/internal/ackhandler/ecn.go +++ b/vendor/github.com/quic-go/quic-go/internal/ackhandler/ecn.go @@ -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 } diff --git a/vendor/github.com/quic-go/quic-go/internal/ackhandler/interfaces.go b/vendor/github.com/quic-go/quic-go/internal/ackhandler/interfaces.go index 5fcce44d..9ee9da7d 100644 --- a/vendor/github.com/quic-go/quic-go/internal/ackhandler/interfaces.go +++ b/vendor/github.com/quic-go/quic-go/internal/ackhandler/interfaces.go @@ -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 } diff --git a/vendor/github.com/quic-go/quic-go/internal/ackhandler/lost_packet_tracker.go b/vendor/github.com/quic-go/quic-go/internal/ackhandler/lost_packet_tracker.go new file mode 100644 index 00000000..40665734 --- /dev/null +++ b/vendor/github.com/quic-go/quic-go/internal/ackhandler/lost_packet_tracker.go @@ -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) +} diff --git a/vendor/github.com/quic-go/quic-go/internal/ackhandler/mockgen.go b/vendor/github.com/quic-go/quic-go/internal/ackhandler/mockgen.go index 0031e6b1..323d9fde 100644 --- a/vendor/github.com/quic-go/quic-go/internal/ackhandler/mockgen.go +++ b/vendor/github.com/quic-go/quic-go/internal/ackhandler/mockgen.go @@ -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 diff --git a/vendor/github.com/quic-go/quic-go/internal/ackhandler/packet.go b/vendor/github.com/quic-go/quic-go/internal/ackhandler/packet.go index c634939a..f0500e9f 100644 --- a/vendor/github.com/quic-go/quic-go/internal/ackhandler/packet.go +++ b/vendor/github.com/quic-go/quic-go/internal/ackhandler/packet.go @@ -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 } diff --git a/vendor/github.com/quic-go/quic-go/internal/ackhandler/received_packet_handler.go b/vendor/github.com/quic-go/quic-go/internal/ackhandler/received_packet_handler.go index eda0826c..7180db85 100644 --- a/vendor/github.com/quic-go/quic-go/internal/ackhandler/received_packet_handler.go +++ b/vendor/github.com/quic-go/quic-go/internal/ackhandler/received_packet_handler.go @@ -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: diff --git a/vendor/github.com/quic-go/quic-go/internal/ackhandler/received_packet_history.go b/vendor/github.com/quic-go/quic-go/internal/ackhandler/received_packet_history.go index f9feae1d..d065b6e6 100644 --- a/vendor/github.com/quic-go/quic-go/internal/ackhandler/received_packet_history.go +++ b/vendor/github.com/quic-go/quic-go/internal/ackhandler/received_packet_history.go @@ -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 { diff --git a/vendor/github.com/quic-go/quic-go/internal/ackhandler/received_packet_tracker.go b/vendor/github.com/quic-go/quic-go/internal/ackhandler/received_packet_tracker.go index d1d26f4a..80f154ba 100644 --- a/vendor/github.com/quic-go/quic-go/internal/ackhandler/received_packet_tracker.go +++ b/vendor/github.com/quic-go/quic-go/internal/ackhandler/received_packet_tracker.go @@ -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 } diff --git a/vendor/github.com/quic-go/quic-go/internal/ackhandler/sent_packet_handler.go b/vendor/github.com/quic-go/quic-go/internal/ackhandler/sent_packet_handler.go index a53be7d7..11739e22 100644 --- a/vendor/github.com/quic-go/quic-go/internal/ackhandler/sent_packet_handler.go +++ b/vendor/github.com/quic-go/quic-go/internal/ackhandler/sent_packet_handler.go @@ -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, diff --git a/vendor/github.com/quic-go/quic-go/internal/ackhandler/sent_packet_history.go b/vendor/github.com/quic-go/quic-go/internal/ackhandler/sent_packet_history.go index 0aabc6d9..54b74cf7 100644 --- a/vendor/github.com/quic-go/quic-go/internal/ackhandler/sent_packet_history.go +++ b/vendor/github.com/quic-go/quic-go/internal/ackhandler/sent_packet_history.go @@ -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 +} diff --git a/vendor/github.com/quic-go/quic-go/internal/congestion/clock.go b/vendor/github.com/quic-go/quic-go/internal/congestion/clock.go index 405fae70..8315026f 100644 --- a/vendor/github.com/quic-go/quic-go/internal/congestion/clock.go +++ b/vendor/github.com/quic-go/quic-go/internal/congestion/clock.go @@ -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() } diff --git a/vendor/github.com/quic-go/quic-go/internal/congestion/cubic.go b/vendor/github.com/quic-go/quic-go/internal/congestion/cubic.go index b35d40d4..40655de6 100644 --- a/vendor/github.com/quic-go/quic-go/internal/congestion/cubic.go +++ b/vendor/github.com/quic-go/quic-go/internal/congestion/cubic.go @@ -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 diff --git a/vendor/github.com/quic-go/quic-go/internal/congestion/cubic_sender.go b/vendor/github.com/quic-go/quic-go/internal/congestion/cubic_sender.go index 075b08e0..1ae10390 100644 --- a/vendor/github.com/quic-go/quic-go/internal/congestion/cubic_sender.go +++ b/vendor/github.com/quic-go/quic-go/internal/congestion/cubic_sender.go @@ -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), + ) } } diff --git a/vendor/github.com/quic-go/quic-go/internal/congestion/interface.go b/vendor/github.com/quic-go/quic-go/internal/congestion/interface.go index 881f453b..996907c6 100644 --- a/vendor/github.com/quic-go/quic-go/internal/congestion/interface.go +++ b/vendor/github.com/quic-go/quic-go/internal/congestion/interface.go @@ -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) diff --git a/vendor/github.com/quic-go/quic-go/internal/congestion/pacer.go b/vendor/github.com/quic-go/quic-go/internal/congestion/pacer.go index 34d3d1d0..92757eed 100644 --- a/vendor/github.com/quic-go/quic-go/internal/congestion/pacer.go +++ b/vendor/github.com/quic-go/quic-go/internal/congestion/pacer.go @@ -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() diff --git a/vendor/github.com/quic-go/quic-go/internal/flowcontrol/base_flow_controller.go b/vendor/github.com/quic-go/quic-go/internal/flowcontrol/base_flow_controller.go index 950e5f72..04a9f98e 100644 --- a/vendor/github.com/quic-go/quic-go/internal/flowcontrol/base_flow_controller.go +++ b/vendor/github.com/quic-go/quic-go/internal/flowcontrol/base_flow_controller.go @@ -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 } diff --git a/vendor/github.com/quic-go/quic-go/internal/flowcontrol/connection_flow_controller.go b/vendor/github.com/quic-go/quic-go/internal/flowcontrol/connection_flow_controller.go index c550e75a..0c74dcc9 100644 --- a/vendor/github.com/quic-go/quic-go/internal/flowcontrol/connection_flow_controller.go +++ b/vendor/github.com/quic-go/quic-go/internal/flowcontrol/connection_flow_controller.go @@ -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() diff --git a/vendor/github.com/quic-go/quic-go/internal/flowcontrol/interface.go b/vendor/github.com/quic-go/quic-go/internal/flowcontrol/interface.go index 23cf30c5..e95c65d9 100644 --- a/vendor/github.com/quic-go/quic-go/internal/flowcontrol/interface.go +++ b/vendor/github.com/quic-go/quic-go/internal/flowcontrol/interface.go @@ -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 } diff --git a/vendor/github.com/quic-go/quic-go/internal/flowcontrol/stream_flow_controller.go b/vendor/github.com/quic-go/quic-go/internal/flowcontrol/stream_flow_controller.go index 968e9a5b..ccbfe9cd 100644 --- a/vendor/github.com/quic-go/quic-go/internal/flowcontrol/stream_flow_controller.go +++ b/vendor/github.com/quic-go/quic-go/internal/flowcontrol/stream_flow_controller.go @@ -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 diff --git a/vendor/github.com/quic-go/quic-go/internal/handshake/interface.go b/vendor/github.com/quic-go/quic-go/internal/handshake/interface.go index c3a59fcd..d9227339 100644 --- a/vendor/github.com/quic-go/quic-go/internal/handshake/interface.go +++ b/vendor/github.com/quic-go/quic-go/internal/handshake/interface.go @@ -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 diff --git a/vendor/github.com/quic-go/quic-go/internal/handshake/updatable_aead.go b/vendor/github.com/quic-go/quic-go/internal/handshake/updatable_aead.go index 415f85de..ef92ab1c 100644 --- a/vendor/github.com/quic-go/quic-go/internal/handshake/updatable_aead.go +++ b/vendor/github.com/quic-go/quic-go/internal/handshake/updatable_aead.go @@ -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) } diff --git a/vendor/github.com/quic-go/quic-go/internal/monotime/time.go b/vendor/github.com/quic-go/quic-go/internal/monotime/time.go new file mode 100644 index 00000000..eda61dc6 --- /dev/null +++ b/vendor/github.com/quic-go/quic-go/internal/monotime/time.go @@ -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()) +} diff --git a/vendor/github.com/quic-go/quic-go/internal/protocol/connection_id.go b/vendor/github.com/quic-go/quic-go/internal/protocol/connection_id.go index 77259b5f..ce3171fc 100644 --- a/vendor/github.com/quic-go/quic-go/internal/protocol/connection_id.go +++ b/vendor/github.com/quic-go/quic-go/internal/protocol/connection_id.go @@ -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 { diff --git a/vendor/github.com/quic-go/quic-go/internal/protocol/protocol.go b/vendor/github.com/quic-go/quic-go/internal/protocol/protocol.go index b1109ff4..a8813a66 100644 --- a/vendor/github.com/quic-go/quic-go/internal/protocol/protocol.go +++ b/vendor/github.com/quic-go/quic-go/internal/protocol/protocol.go @@ -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) diff --git a/vendor/github.com/quic-go/quic-go/internal/utils/connstats.go b/vendor/github.com/quic-go/quic-go/internal/utils/connstats.go new file mode 100644 index 00000000..19d88312 --- /dev/null +++ b/vendor/github.com/quic-go/quic-go/internal/utils/connstats.go @@ -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 +} diff --git a/vendor/github.com/quic-go/quic-go/internal/utils/log.go b/vendor/github.com/quic-go/quic-go/internal/utils/log.go index 89b52c0d..558803ef 100644 --- a/vendor/github.com/quic-go/quic-go/internal/utils/log.go +++ b/vendor/github.com/quic-go/quic-go/internal/utils/log.go @@ -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 { diff --git a/vendor/github.com/quic-go/quic-go/internal/utils/rtt_stats.go b/vendor/github.com/quic-go/quic-go/internal/utils/rtt_stats.go index cfa9d5b0..61cbef5c 100644 --- a/vendor/github.com/quic-go/quic-go/internal/utils/rtt_stats.go +++ b/vendor/github.com/quic-go/quic-go/internal/utils/rtt_stats.go @@ -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 +} diff --git a/vendor/github.com/quic-go/quic-go/internal/utils/timer.go b/vendor/github.com/quic-go/quic-go/internal/utils/timer.go deleted file mode 100644 index 361106c8..00000000 --- a/vendor/github.com/quic-go/quic-go/internal/utils/timer.go +++ /dev/null @@ -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() -} diff --git a/vendor/github.com/quic-go/quic-go/internal/wire/ack_frequency_frame.go b/vendor/github.com/quic-go/quic-go/internal/wire/ack_frequency_frame.go new file mode 100644 index 00000000..0735d70b --- /dev/null +++ b/vendor/github.com/quic-go/quic-go/internal/wire/ack_frequency_frame.go @@ -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))) +} diff --git a/vendor/github.com/quic-go/quic-go/internal/wire/frame_parser.go b/vendor/github.com/quic-go/quic-go/internal/wire/frame_parser.go index e92e29c8..afc3bedb 100644 --- a/vendor/github.com/quic-go/quic-go/internal/wire/frame_parser.go +++ b/vendor/github.com/quic-go/quic-go/internal/wire/frame_parser.go @@ -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 } diff --git a/vendor/github.com/quic-go/quic-go/internal/wire/frame_type.go b/vendor/github.com/quic-go/quic-go/internal/wire/frame_type.go index 0576657f..d3d086d9 100644 --- a/vendor/github.com/quic-go/quic-go/internal/wire/frame_type.go +++ b/vendor/github.com/quic-go/quic-go/internal/wire/frame_type.go @@ -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 diff --git a/vendor/github.com/quic-go/quic-go/internal/wire/immediate_ack_frame.go b/vendor/github.com/quic-go/quic-go/internal/wire/immediate_ack_frame.go new file mode 100644 index 00000000..aeff71b4 --- /dev/null +++ b/vendor/github.com/quic-go/quic-go/internal/wire/immediate_ack_frame.go @@ -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))) +} diff --git a/vendor/github.com/quic-go/quic-go/internal/wire/pool.go b/vendor/github.com/quic-go/quic-go/internal/wire/pool.go index 18ab4379..d656fad4 100644 --- a/vendor/github.com/quic-go/quic-go/internal/wire/pool.go +++ b/vendor/github.com/quic-go/quic-go/internal/wire/pool.go @@ -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, diff --git a/vendor/github.com/quic-go/quic-go/internal/wire/transport_parameters.go b/vendor/github.com/quic-go/quic-go/internal/wire/transport_parameters.go index 2437892a..ff101b6e 100644 --- a/vendor/github.com/quic-go/quic-go/internal/wire/transport_parameters.go +++ b/vendor/github.com/quic-go/quic-go/internal/wire/transport_parameters.go @@ -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...) } diff --git a/vendor/github.com/quic-go/quic-go/logging/connection_tracer.go b/vendor/github.com/quic-go/quic-go/logging/connection_tracer.go index f218e046..d6c2b48d 100644 --- a/vendor/github.com/quic-go/quic-go/logging/connection_tracer.go +++ b/vendor/github.com/quic-go/quic-go/logging/connection_tracer.go @@ -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) diff --git a/vendor/github.com/quic-go/quic-go/logging/connection_tracer_multiplexer.go b/vendor/github.com/quic-go/quic-go/logging/connection_tracer_multiplexer.go index 3a87058c..48ac1d38 100644 --- a/vendor/github.com/quic-go/quic-go/logging/connection_tracer_multiplexer.go +++ b/vendor/github.com/quic-go/quic-go/logging/connection_tracer_multiplexer.go @@ -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 { diff --git a/vendor/github.com/quic-go/quic-go/logging/frame.go b/vendor/github.com/quic-go/quic-go/logging/frame.go index 9a055db3..e5721eff 100644 --- a/vendor/github.com/quic-go/quic-go/logging/frame.go +++ b/vendor/github.com/quic-go/quic-go/logging/frame.go @@ -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. diff --git a/vendor/github.com/quic-go/quic-go/mockgen.go b/vendor/github.com/quic-go/quic-go/mockgen.go index 9e1f62f2..65160d0d 100644 --- a/vendor/github.com/quic-go/quic-go/mockgen.go +++ b/vendor/github.com/quic-go/quic-go/mockgen.go @@ -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" diff --git a/vendor/github.com/quic-go/quic-go/mtu_discoverer.go b/vendor/github.com/quic-go/quic-go/mtu_discoverer.go index 096eba14..244cfe17 100644 --- a/vendor/github.com/quic-go/quic-go/mtu_discoverer.go +++ b/vendor/github.com/quic-go/quic-go/mtu_discoverer.go @@ -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 diff --git a/vendor/github.com/quic-go/quic-go/oss-fuzz.sh b/vendor/github.com/quic-go/quic-go/oss-fuzz.sh index 92a57a2c..3884a10f 100644 --- a/vendor/github.com/quic-go/quic-go/oss-fuzz.sh +++ b/vendor/github.com/quic-go/quic-go/oss-fuzz.sh @@ -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 diff --git a/vendor/github.com/quic-go/quic-go/packet_packer.go b/vendor/github.com/quic-go/quic-go/packet_packer.go index 99228534..e3933da6 100644 --- a/vendor/github.com/quic-go/quic-go/packet_packer.go +++ b/vendor/github.com/quic-go/quic-go/packet_packer.go @@ -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 diff --git a/vendor/github.com/quic-go/quic-go/packet_unpacker.go b/vendor/github.com/quic-go/quic-go/packet_unpacker.go index 2f607fbd..0729636e 100644 --- a/vendor/github.com/quic-go/quic-go/packet_unpacker.go +++ b/vendor/github.com/quic-go/quic-go/packet_unpacker.go @@ -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 diff --git a/vendor/github.com/quic-go/quic-go/path_manager.go b/vendor/github.com/quic-go/quic-go/path_manager.go index 34096654..dd188ea1 100644 --- a/vendor/github.com/quic-go/quic-go/path_manager.go +++ b/vendor/github.com/quic-go/quic-go/path_manager.go @@ -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) { diff --git a/vendor/github.com/quic-go/quic-go/path_manager_outgoing.go b/vendor/github.com/quic-go/quic-go/path_manager_outgoing.go index e8c065ff..78f68eea 100644 --- a/vendor/github.com/quic-go/quic-go/path_manager_outgoing.go +++ b/vendor/github.com/quic-go/quic-go/path_manager_outgoing.go @@ -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), diff --git a/vendor/github.com/quic-go/quic-go/quicvarint/io.go b/vendor/github.com/quic-go/quic-go/quicvarint/io.go index 27def409..5c345364 100644 --- a/vendor/github.com/quic-go/quic-go/quicvarint/io.go +++ b/vendor/github.com/quic-go/quic-go/quicvarint/io.go @@ -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 } diff --git a/vendor/github.com/quic-go/quic-go/receive_stream.go b/vendor/github.com/quic-go/quic-go/receive_stream.go index 61f4cdf4..b875ecde 100644 --- a/vendor/github.com/quic-go/quic-go/receive_stream.go +++ b/vendor/github.com/quic-go/quic-go/receive_stream.go @@ -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 diff --git a/vendor/github.com/quic-go/quic-go/send_stream.go b/vendor/github.com/quic-go/quic-go/send_stream.go index d5ce777a..723f9d5f 100644 --- a/vendor/github.com/quic-go/quic-go/send_stream.go +++ b/vendor/github.com/quic-go/quic-go/send_stream.go @@ -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 } diff --git a/vendor/github.com/quic-go/quic-go/server.go b/vendor/github.com/quic-go/quic-go/server.go index f6bef2d8..142de36a 100644 --- a/vendor/github.com/quic-go/quic-go/server.go +++ b/vendor/github.com/quic-go/quic-go/server.go @@ -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) { diff --git a/vendor/github.com/quic-go/quic-go/stream.go b/vendor/github.com/quic-go/quic-go/stream.go index 64bcc838..7b5b8d09 100644 --- a/vendor/github.com/quic-go/quic-go/stream.go +++ b/vendor/github.com/quic-go/quic-go/stream.go @@ -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 diff --git a/vendor/github.com/quic-go/quic-go/streams_map.go b/vendor/github.com/quic-go/quic-go/streams_map.go index 14be5ad0..92023ac2 100644 --- a/vendor/github.com/quic-go/quic-go/streams_map.go +++ b/vendor/github.com/quic-go/quic-go/streams_map.go @@ -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 diff --git a/vendor/github.com/quic-go/quic-go/sys_conn.go b/vendor/github.com/quic-go/quic-go/sys_conn.go index 6e768dcf..ce35de88 100644 --- a/vendor/github.com/quic-go/quic-go/sys_conn.go +++ b/vendor/github.com/quic-go/quic-go/sys_conn.go @@ -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 diff --git a/vendor/github.com/quic-go/quic-go/sys_conn_oob.go b/vendor/github.com/quic-go/quic-go/sys_conn_oob.go index e8534708..0aa69cda 100644 --- a/vendor/github.com/quic-go/quic-go/sys_conn_oob.go +++ b/vendor/github.com/quic-go/quic-go/sys_conn_oob.go @@ -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, } diff --git a/vendor/github.com/quic-go/quic-go/tools.go b/vendor/github.com/quic-go/quic-go/tools.go deleted file mode 100644 index 8181df6d..00000000 --- a/vendor/github.com/quic-go/quic-go/tools.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build tools - -package quic - -import ( - _ "go.uber.org/mock/mockgen" -) diff --git a/vendor/go.uber.org/mock/AUTHORS b/vendor/go.uber.org/mock/AUTHORS deleted file mode 100644 index 660b8ccc..00000000 --- a/vendor/go.uber.org/mock/AUTHORS +++ /dev/null @@ -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 -# The email address is not required for organizations. - -# Please keep the list sorted. - -Alex Reece -Google Inc. diff --git a/vendor/go.uber.org/mock/LICENSE b/vendor/go.uber.org/mock/LICENSE deleted file mode 100644 index d6456956..00000000 --- a/vendor/go.uber.org/mock/LICENSE +++ /dev/null @@ -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. diff --git a/vendor/go.uber.org/mock/mockgen/archive.go b/vendor/go.uber.org/mock/mockgen/archive.go deleted file mode 100644 index bc80ad11..00000000 --- a/vendor/go.uber.org/mock/mockgen/archive.go +++ /dev/null @@ -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 -} diff --git a/vendor/go.uber.org/mock/mockgen/deprecated.go b/vendor/go.uber.org/mock/mockgen/deprecated.go deleted file mode 100644 index 0b45a2e3..00000000 --- a/vendor/go.uber.org/mock/mockgen/deprecated.go +++ /dev/null @@ -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) -} diff --git a/vendor/go.uber.org/mock/mockgen/generic.go b/vendor/go.uber.org/mock/mockgen/generic.go deleted file mode 100644 index c2289c2a..00000000 --- a/vendor/go.uber.org/mock/mockgen/generic.go +++ /dev/null @@ -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") diff --git a/vendor/go.uber.org/mock/mockgen/gob.go b/vendor/go.uber.org/mock/mockgen/gob.go deleted file mode 100644 index b5ab0661..00000000 --- a/vendor/go.uber.org/mock/mockgen/gob.go +++ /dev/null @@ -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 -} diff --git a/vendor/go.uber.org/mock/mockgen/mockgen.go b/vendor/go.uber.org/mock/mockgen/mockgen.go deleted file mode 100644 index d3d92ba1..00000000 --- a/vendor/go.uber.org/mock/mockgen/mockgen.go +++ /dev/null @@ -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 ") - 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 -} diff --git a/vendor/go.uber.org/mock/mockgen/model/model.go b/vendor/go.uber.org/mock/mockgen/model/model.go deleted file mode 100644 index 853dbf2d..00000000 --- a/vendor/go.uber.org/mock/mockgen/model/model.go +++ /dev/null @@ -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"), - }, - }, - }, - }, -} diff --git a/vendor/go.uber.org/mock/mockgen/model/model_gotypes.go b/vendor/go.uber.org/mock/mockgen/model/model_gotypes.go deleted file mode 100644 index 4596c3d2..00000000 --- a/vendor/go.uber.org/mock/mockgen/model/model_gotypes.go +++ /dev/null @@ -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) -} diff --git a/vendor/go.uber.org/mock/mockgen/package_mode.go b/vendor/go.uber.org/mock/mockgen/package_mode.go deleted file mode 100644 index 29fab7f8..00000000 --- a/vendor/go.uber.org/mock/mockgen/package_mode.go +++ /dev/null @@ -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 -} diff --git a/vendor/go.uber.org/mock/mockgen/parse.go b/vendor/go.uber.org/mock/mockgen/parse.go deleted file mode 100644 index f43321c3..00000000 --- a/vendor/go.uber.org/mock/mockgen/parse.go +++ /dev/null @@ -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") diff --git a/vendor/go.uber.org/mock/mockgen/version.go b/vendor/go.uber.org/mock/mockgen/version.go deleted file mode 100644 index 6db160ac..00000000 --- a/vendor/go.uber.org/mock/mockgen/version.go +++ /dev/null @@ -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.") - } -} diff --git a/vendor/golang.org/x/mod/modfile/print.go b/vendor/golang.org/x/mod/modfile/print.go deleted file mode 100644 index 2a0123d4..00000000 --- a/vendor/golang.org/x/mod/modfile/print.go +++ /dev/null @@ -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 = "" - } - } -} diff --git a/vendor/golang.org/x/mod/modfile/read.go b/vendor/golang.org/x/mod/modfile/read.go deleted file mode 100644 index 2d748680..00000000 --- a/vendor/golang.org/x/mod/modfile/read.go +++ /dev/null @@ -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 -} diff --git a/vendor/golang.org/x/mod/modfile/rule.go b/vendor/golang.org/x/mod/modfile/rule.go deleted file mode 100644 index a86ee4fd..00000000 --- a/vendor/golang.org/x/mod/modfile/rule.go +++ /dev/null @@ -1,1904 +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 implements a parser and formatter for go.mod files. -// -// The go.mod syntax is described in -// https://pkg.go.dev/cmd/go/#hdr-The_go_mod_file. -// -// The [Parse] and [ParseLax] functions both parse a go.mod file and return an -// abstract syntax tree. ParseLax ignores unknown statements and may be used to -// parse go.mod files that may have been developed with newer versions of Go. -// -// The [File] struct returned by Parse and ParseLax represent an abstract -// go.mod file. File has several methods like [File.AddNewRequire] and -// [File.DropReplace] that can be used to programmatically edit a file. -// -// The [Format] function formats a File back to a byte slice which can be -// written to a file. -package modfile - -import ( - "cmp" - "errors" - "fmt" - "path/filepath" - "slices" - "strconv" - "strings" - "unicode" - - "golang.org/x/mod/internal/lazyregexp" - "golang.org/x/mod/module" - "golang.org/x/mod/semver" -) - -// A File is the parsed, interpreted form of a go.mod file. -type File struct { - Module *Module - Go *Go - Toolchain *Toolchain - Godebug []*Godebug - Require []*Require - Exclude []*Exclude - Replace []*Replace - Retract []*Retract - Tool []*Tool - Ignore []*Ignore - - Syntax *FileSyntax -} - -// A Module is the module statement. -type Module struct { - Mod module.Version - Deprecated string - Syntax *Line -} - -// A Go is the go statement. -type Go struct { - Version string // "1.23" - Syntax *Line -} - -// A Toolchain is the toolchain statement. -type Toolchain struct { - Name string // "go1.21rc1" - Syntax *Line -} - -// A Godebug is a single godebug key=value statement. -type Godebug struct { - Key string - Value string - Syntax *Line -} - -// An Exclude is a single exclude statement. -type Exclude struct { - Mod module.Version - Syntax *Line -} - -// A Replace is a single replace statement. -type Replace struct { - Old module.Version - New module.Version - Syntax *Line -} - -// A Retract is a single retract statement. -type Retract struct { - VersionInterval - Rationale string - Syntax *Line -} - -// A Tool is a single tool statement. -type Tool struct { - Path string - Syntax *Line -} - -// An Ignore is a single ignore statement. -type Ignore struct { - Path string - Syntax *Line -} - -// A VersionInterval represents a range of versions with upper and lower bounds. -// Intervals are closed: both bounds are included. When Low is equal to High, -// the interval may refer to a single version ('v1.2.3') or an interval -// ('[v1.2.3, v1.2.3]'); both have the same representation. -type VersionInterval struct { - Low, High string -} - -// A Require is a single require statement. -type Require struct { - Mod module.Version - Indirect bool // has "// indirect" comment - Syntax *Line -} - -func (r *Require) markRemoved() { - r.Syntax.markRemoved() - *r = Require{} -} - -func (r *Require) setVersion(v string) { - r.Mod.Version = v - - if line := r.Syntax; len(line.Token) > 0 { - if line.InBlock { - // If the line is preceded by an empty line, remove it; see - // https://golang.org/issue/33779. - if len(line.Comments.Before) == 1 && len(line.Comments.Before[0].Token) == 0 { - line.Comments.Before = line.Comments.Before[:0] - } - if len(line.Token) >= 2 { // example.com v1.2.3 - line.Token[1] = v - } - } else { - if len(line.Token) >= 3 { // require example.com v1.2.3 - line.Token[2] = v - } - } - } -} - -// setIndirect sets line to have (or not have) a "// indirect" comment. -func (r *Require) setIndirect(indirect bool) { - r.Indirect = indirect - line := r.Syntax - if isIndirect(line) == indirect { - return - } - if indirect { - // Adding comment. - if len(line.Suffix) == 0 { - // New comment. - line.Suffix = []Comment{{Token: "// indirect", Suffix: true}} - return - } - - com := &line.Suffix[0] - text := strings.TrimSpace(strings.TrimPrefix(com.Token, string(slashSlash))) - if text == "" { - // Empty comment. - com.Token = "// indirect" - return - } - - // Insert at beginning of existing comment. - com.Token = "// indirect; " + text - return - } - - // Removing comment. - f := strings.TrimSpace(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash))) - if f == "indirect" { - // Remove whole comment. - line.Suffix = nil - return - } - - // Remove comment prefix. - com := &line.Suffix[0] - i := strings.Index(com.Token, "indirect;") - com.Token = "//" + com.Token[i+len("indirect;"):] -} - -// isIndirect reports whether line has a "// indirect" comment, -// meaning it is in go.mod only for its effect on indirect dependencies, -// so that it can be dropped entirely once the effective version of the -// indirect dependency reaches the given minimum version. -func isIndirect(line *Line) bool { - if len(line.Suffix) == 0 { - return false - } - f := strings.Fields(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash))) - return (len(f) == 1 && f[0] == "indirect" || len(f) > 1 && f[0] == "indirect;") -} - -func (f *File) AddModuleStmt(path string) error { - if f.Syntax == nil { - f.Syntax = new(FileSyntax) - } - if f.Module == nil { - f.Module = &Module{ - Mod: module.Version{Path: path}, - Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)), - } - } else { - f.Module.Mod.Path = path - f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path)) - } - return nil -} - -func (f *File) AddComment(text string) { - if f.Syntax == nil { - f.Syntax = new(FileSyntax) - } - f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{ - Comments: Comments{ - Before: []Comment{ - { - Token: text, - }, - }, - }, - }) -} - -type VersionFixer func(path, version string) (string, error) - -// errDontFix is returned by a VersionFixer to indicate the version should be -// left alone, even if it's not canonical. -var dontFixRetract VersionFixer = func(_, vers string) (string, error) { - return vers, nil -} - -// Parse parses and returns a go.mod 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 Parse(file string, data []byte, fix VersionFixer) (*File, error) { - return parseToFile(file, data, fix, true) -} - -// ParseLax is like Parse but ignores unknown statements. -// It is used when parsing go.mod files other than the main module, -// under the theory that most statement types we add in the future will -// only apply in the main module, like exclude and replace, -// and so we get better gradual deployments if old go commands -// simply ignore those statements when found in go.mod files -// in dependencies. -func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) { - return parseToFile(file, data, fix, false) -} - -func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (parsed *File, err error) { - fs, err := parse(file, data) - if err != nil { - return nil, err - } - f := &File{ - Syntax: fs, - } - var errs ErrorList - - // fix versions in retract directives after the file is parsed. - // We need the module path to fix versions, and it might be at the end. - defer func() { - oldLen := len(errs) - f.fixRetract(fix, &errs) - if len(errs) > oldLen { - parsed, err = nil, errs - } - }() - - for _, x := range fs.Stmt { - switch x := x.(type) { - case *Line: - f.add(&errs, nil, x, x.Token[0], x.Token[1:], fix, strict) - - case *LineBlock: - if len(x.Token) > 1 { - if strict { - 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: - if strict { - errs = append(errs, Error{ - Filename: file, - Pos: x.Start, - Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")), - }) - } - continue - case "module", "godebug", "require", "exclude", "replace", "retract", "tool", "ignore": - for _, l := range x.Line { - f.add(&errs, x, l, x.Token[0], l.Token, fix, strict) - } - } - } - } - - if len(errs) > 0 { - return nil, errs - } - return f, nil -} - -var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?([a-z]+[0-9]+)?$`) -var laxGoVersionRE = lazyregexp.New(`^v?(([1-9][0-9]*)\.(0|[1-9][0-9]*))([^0-9].*)$`) - -// Toolchains must be named beginning with `go1`, -// like "go1.20.3" or "go1.20.3-gccgo". As a special case, "default" is also permitted. -// Note that this regexp is a much looser condition than go/version.IsValid, -// for forward compatibility. -// (This code has to be work to identify new toolchains even if we tweak the syntax in the future.) -var ToolchainRE = lazyregexp.New(`^default$|^go1($|\.)`) - -func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, args []string, fix VersionFixer, strict bool) { - // If strict is false, this module is a dependency. - // We ignore all unknown directives as well as main-module-only - // directives like replace and exclude. It will work better for - // forward compatibility if we can depend on modules that have unknown - // statements (presumed relevant only when acting as the main module) - // and simply ignore those statements. - if !strict { - switch verb { - case "go", "module", "retract", "require", "ignore": - // want these even for dependency go.mods - default: - return - } - } - - wrapModPathError := func(modPath string, err error) { - *errs = append(*errs, Error{ - Filename: f.Syntax.Name, - Pos: line.Start, - ModPath: modPath, - Verb: verb, - Err: err, - }) - } - wrapError := func(err error) { - *errs = append(*errs, Error{ - Filename: f.Syntax.Name, - Pos: line.Start, - Err: err, - }) - } - errorf := func(format string, args ...interface{}) { - wrapError(fmt.Errorf(format, args...)) - } - - switch verb { - default: - errorf("unknown directive: %s", verb) - - case "go": - if f.Go != nil { - errorf("repeated go statement") - return - } - if len(args) != 1 { - errorf("go directive expects exactly one argument") - return - } else if !GoVersionRE.MatchString(args[0]) { - fixed := false - if !strict { - if m := laxGoVersionRE.FindStringSubmatch(args[0]); m != nil { - args[0] = m[1] - fixed = true - } - } - if !fixed { - errorf("invalid go version '%s': must match format 1.23.0", args[0]) - return - } - } - - f.Go = &Go{Syntax: line} - f.Go.Version = args[0] - - case "toolchain": - if f.Toolchain != nil { - errorf("repeated toolchain statement") - return - } - if len(args) != 1 { - errorf("toolchain directive expects exactly one argument") - return - } else if !ToolchainRE.MatchString(args[0]) { - errorf("invalid toolchain version '%s': must match format go1.23.0 or default", args[0]) - return - } - f.Toolchain = &Toolchain{Syntax: line} - f.Toolchain.Name = args[0] - - case "module": - if f.Module != nil { - errorf("repeated module statement") - return - } - deprecated := parseDeprecation(block, line) - f.Module = &Module{ - Syntax: line, - Deprecated: deprecated, - } - if len(args) != 1 { - errorf("usage: module module/path") - return - } - s, err := parseString(&args[0]) - if err != nil { - errorf("invalid quoted string: %v", err) - return - } - f.Module.Mod = module.Version{Path: s} - - case "godebug": - if len(args) != 1 || strings.ContainsAny(args[0], "\"`',") { - errorf("usage: godebug key=value") - return - } - key, value, ok := strings.Cut(args[0], "=") - if !ok { - errorf("usage: godebug key=value") - return - } - f.Godebug = append(f.Godebug, &Godebug{ - Key: key, - Value: value, - Syntax: line, - }) - - case "require", "exclude": - if len(args) != 2 { - errorf("usage: %s module/path v1.2.3", verb) - return - } - s, err := parseString(&args[0]) - if err != nil { - errorf("invalid quoted string: %v", err) - return - } - v, err := parseVersion(verb, s, &args[1], fix) - if err != nil { - wrapError(err) - return - } - pathMajor, err := modulePathMajor(s) - if err != nil { - wrapError(err) - return - } - if err := module.CheckPathMajor(v, pathMajor); err != nil { - wrapModPathError(s, err) - return - } - if verb == "require" { - f.Require = append(f.Require, &Require{ - Mod: module.Version{Path: s, Version: v}, - Syntax: line, - Indirect: isIndirect(line), - }) - } else { - f.Exclude = append(f.Exclude, &Exclude{ - Mod: module.Version{Path: s, Version: v}, - Syntax: line, - }) - } - - case "replace": - replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix) - if wrappederr != nil { - *errs = append(*errs, *wrappederr) - return - } - f.Replace = append(f.Replace, replace) - - case "retract": - rationale := parseDirectiveComment(block, line) - vi, err := parseVersionInterval(verb, "", &args, dontFixRetract) - if err != nil { - if strict { - wrapError(err) - return - } else { - // Only report errors parsing intervals in the main module. We may - // support additional syntax in the future, such as open and half-open - // intervals. Those can't be supported now, because they break the - // go.mod parser, even in lax mode. - return - } - } - if len(args) > 0 && strict { - // In the future, there may be additional information after the version. - errorf("unexpected token after version: %q", args[0]) - return - } - retract := &Retract{ - VersionInterval: vi, - Rationale: rationale, - Syntax: line, - } - f.Retract = append(f.Retract, retract) - - case "tool": - if len(args) != 1 { - errorf("tool directive expects exactly one argument") - return - } - s, err := parseString(&args[0]) - if err != nil { - errorf("invalid quoted string: %v", err) - return - } - f.Tool = append(f.Tool, &Tool{ - Path: s, - Syntax: line, - }) - - case "ignore": - if len(args) != 1 { - errorf("ignore directive expects exactly one argument") - return - } - s, err := parseString(&args[0]) - if err != nil { - errorf("invalid quoted string: %v", err) - return - } - f.Ignore = append(f.Ignore, &Ignore{ - Path: s, - Syntax: line, - }) - } -} - -func parseReplace(filename string, line *Line, verb string, args []string, fix VersionFixer) (*Replace, *Error) { - wrapModPathError := func(modPath string, err error) *Error { - return &Error{ - Filename: filename, - Pos: line.Start, - ModPath: modPath, - Verb: verb, - Err: err, - } - } - wrapError := func(err error) *Error { - return &Error{ - Filename: filename, - Pos: line.Start, - Err: err, - } - } - errorf := func(format string, args ...interface{}) *Error { - return wrapError(fmt.Errorf(format, args...)) - } - - arrow := 2 - if len(args) >= 2 && args[1] == "=>" { - arrow = 1 - } - if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" { - return nil, errorf("usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory", verb, verb) - } - s, err := parseString(&args[0]) - if err != nil { - return nil, errorf("invalid quoted string: %v", err) - } - pathMajor, err := modulePathMajor(s) - if err != nil { - return nil, wrapModPathError(s, err) - - } - var v string - if arrow == 2 { - v, err = parseVersion(verb, s, &args[1], fix) - if err != nil { - return nil, wrapError(err) - } - if err := module.CheckPathMajor(v, pathMajor); err != nil { - return nil, wrapModPathError(s, err) - } - } - ns, err := parseString(&args[arrow+1]) - if err != nil { - return nil, errorf("invalid quoted string: %v", err) - } - nv := "" - if len(args) == arrow+2 { - if !IsDirectoryPath(ns) { - if strings.Contains(ns, "@") { - return nil, errorf("replacement module must match format 'path version', not 'path@version'") - } - return nil, errorf("replacement module without version must be directory path (rooted or starting with . or ..)") - } - if filepath.Separator == '/' && strings.Contains(ns, `\`) { - return nil, errorf("replacement directory appears to be Windows path (on a non-windows system)") - } - } - if len(args) == arrow+3 { - nv, err = parseVersion(verb, ns, &args[arrow+2], fix) - if err != nil { - return nil, wrapError(err) - } - if IsDirectoryPath(ns) { - return nil, errorf("replacement module directory path %q cannot have version", ns) - } - } - return &Replace{ - Old: module.Version{Path: s, Version: v}, - New: module.Version{Path: ns, Version: nv}, - Syntax: line, - }, nil -} - -// fixRetract applies fix to each retract directive in f, appending any errors -// to errs. -// -// Most versions are fixed as we parse the file, but for retract directives, -// the relevant module path is the one specified with the module directive, -// and that might appear at the end of the file (or not at all). -func (f *File) fixRetract(fix VersionFixer, errs *ErrorList) { - if fix == nil { - return - } - path := "" - if f.Module != nil { - path = f.Module.Mod.Path - } - var r *Retract - wrapError := func(err error) { - *errs = append(*errs, Error{ - Filename: f.Syntax.Name, - Pos: r.Syntax.Start, - Err: err, - }) - } - - for _, r = range f.Retract { - if path == "" { - wrapError(errors.New("no module directive found, so retract cannot be used")) - return // only print the first one of these - } - - args := r.Syntax.Token - if args[0] == "retract" { - args = args[1:] - } - vi, err := parseVersionInterval("retract", path, &args, fix) - if err != nil { - wrapError(err) - } - r.VersionInterval = vi - } -} - -func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string, fix VersionFixer) { - wrapError := func(err error) { - *errs = append(*errs, Error{ - Filename: f.Syntax.Name, - Pos: line.Start, - Err: err, - }) - } - errorf := func(format string, args ...interface{}) { - wrapError(fmt.Errorf(format, args...)) - } - - switch verb { - default: - errorf("unknown directive: %s", verb) - - case "go": - if f.Go != nil { - errorf("repeated go statement") - return - } - if len(args) != 1 { - errorf("go directive expects exactly one argument") - return - } else if !GoVersionRE.MatchString(args[0]) { - errorf("invalid go version '%s': must match format 1.23.0", args[0]) - return - } - - f.Go = &Go{Syntax: line} - f.Go.Version = args[0] - - case "toolchain": - if f.Toolchain != nil { - errorf("repeated toolchain statement") - return - } - if len(args) != 1 { - errorf("toolchain directive expects exactly one argument") - return - } else if !ToolchainRE.MatchString(args[0]) { - errorf("invalid toolchain version '%s': must match format go1.23.0 or default", args[0]) - return - } - - f.Toolchain = &Toolchain{Syntax: line} - f.Toolchain.Name = args[0] - - case "godebug": - if len(args) != 1 || strings.ContainsAny(args[0], "\"`',") { - errorf("usage: godebug key=value") - return - } - key, value, ok := strings.Cut(args[0], "=") - if !ok { - errorf("usage: godebug key=value") - return - } - f.Godebug = append(f.Godebug, &Godebug{ - Key: key, - Value: value, - Syntax: line, - }) - - case "use": - if len(args) != 1 { - errorf("usage: %s local/dir", verb) - return - } - s, err := parseString(&args[0]) - if err != nil { - errorf("invalid quoted string: %v", err) - return - } - f.Use = append(f.Use, &Use{ - Path: s, - Syntax: line, - }) - - case "replace": - replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix) - if wrappederr != nil { - *errs = append(*errs, *wrappederr) - return - } - f.Replace = append(f.Replace, replace) - } -} - -// IsDirectoryPath reports whether the given path should be interpreted as a directory path. -// Just like on the go command line, relative paths starting with a '.' or '..' path component -// and rooted paths are directory paths; the rest are module paths. -func IsDirectoryPath(ns string) bool { - // Because go.mod files can move from one system to another, - // we check all known path syntaxes, both Unix and Windows. - return ns == "." || strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, `.\`) || - ns == ".." || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, `..\`) || - strings.HasPrefix(ns, "/") || strings.HasPrefix(ns, `\`) || - len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':' -} - -// MustQuote reports whether s must be quoted in order to appear as -// a single token in a go.mod line. -func MustQuote(s string) bool { - for _, r := range s { - switch r { - case ' ', '"', '\'', '`': - return true - - case '(', ')', '[', ']', '{', '}', ',': - if len(s) > 1 { - return true - } - - default: - if !unicode.IsPrint(r) { - return true - } - } - } - return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*") -} - -// AutoQuote returns s or, if quoting is required for s to appear in a go.mod, -// the quotation of s. -func AutoQuote(s string) string { - if MustQuote(s) { - return strconv.Quote(s) - } - return s -} - -func parseVersionInterval(verb string, path string, args *[]string, fix VersionFixer) (VersionInterval, error) { - toks := *args - if len(toks) == 0 || toks[0] == "(" { - return VersionInterval{}, fmt.Errorf("expected '[' or version") - } - if toks[0] != "[" { - v, err := parseVersion(verb, path, &toks[0], fix) - if err != nil { - return VersionInterval{}, err - } - *args = toks[1:] - return VersionInterval{Low: v, High: v}, nil - } - toks = toks[1:] - - if len(toks) == 0 { - return VersionInterval{}, fmt.Errorf("expected version after '['") - } - low, err := parseVersion(verb, path, &toks[0], fix) - if err != nil { - return VersionInterval{}, err - } - toks = toks[1:] - - if len(toks) == 0 || toks[0] != "," { - return VersionInterval{}, fmt.Errorf("expected ',' after version") - } - toks = toks[1:] - - if len(toks) == 0 { - return VersionInterval{}, fmt.Errorf("expected version after ','") - } - high, err := parseVersion(verb, path, &toks[0], fix) - if err != nil { - return VersionInterval{}, err - } - toks = toks[1:] - - if len(toks) == 0 || toks[0] != "]" { - return VersionInterval{}, fmt.Errorf("expected ']' after version") - } - toks = toks[1:] - - *args = toks - return VersionInterval{Low: low, High: high}, nil -} - -func parseString(s *string) (string, error) { - t := *s - if strings.HasPrefix(t, `"`) { - var err error - if t, err = strconv.Unquote(t); err != nil { - return "", err - } - } else if strings.ContainsAny(t, "\"'`") { - // Other quotes are reserved both for possible future expansion - // and to avoid confusion. For example if someone types 'x' - // we want that to be a syntax error and not a literal x in literal quotation marks. - return "", fmt.Errorf("unquoted string cannot contain quote") - } - *s = AutoQuote(t) - return t, nil -} - -var deprecatedRE = lazyregexp.New(`(?s)(?:^|\n\n)Deprecated: *(.*?)(?:$|\n\n)`) - -// parseDeprecation extracts the text of comments on a "module" directive and -// extracts a deprecation message from that. -// -// A deprecation message is contained in a paragraph within a block of comments -// that starts with "Deprecated:" (case sensitive). The message runs until the -// end of the paragraph and does not include the "Deprecated:" prefix. If the -// comment block has multiple paragraphs that start with "Deprecated:", -// parseDeprecation returns the message from the first. -func parseDeprecation(block *LineBlock, line *Line) string { - text := parseDirectiveComment(block, line) - m := deprecatedRE.FindStringSubmatch(text) - if m == nil { - return "" - } - return m[1] -} - -// parseDirectiveComment extracts the text of comments on a directive. -// If the directive's line does not have comments and is part of a block that -// does have comments, the block's comments are used. -func parseDirectiveComment(block *LineBlock, line *Line) string { - comments := line.Comment() - if block != nil && len(comments.Before) == 0 && len(comments.Suffix) == 0 { - comments = block.Comment() - } - groups := [][]Comment{comments.Before, comments.Suffix} - var lines []string - for _, g := range groups { - for _, c := range g { - if !strings.HasPrefix(c.Token, "//") { - continue // blank line - } - lines = append(lines, strings.TrimSpace(strings.TrimPrefix(c.Token, "//"))) - } - } - return strings.Join(lines, "\n") -} - -type ErrorList []Error - -func (e ErrorList) Error() string { - errStrs := make([]string, len(e)) - for i, err := range e { - errStrs[i] = err.Error() - } - return strings.Join(errStrs, "\n") -} - -type Error struct { - Filename string - Pos Position - Verb string - ModPath string - Err error -} - -func (e *Error) Error() string { - var pos string - if e.Pos.LineRune > 1 { - // Don't print LineRune if it's 1 (beginning of line). - // It's always 1 except in scanner errors, which are rare. - pos = fmt.Sprintf("%s:%d:%d: ", e.Filename, e.Pos.Line, e.Pos.LineRune) - } else if e.Pos.Line > 0 { - pos = fmt.Sprintf("%s:%d: ", e.Filename, e.Pos.Line) - } else if e.Filename != "" { - pos = fmt.Sprintf("%s: ", e.Filename) - } - - var directive string - if e.ModPath != "" { - directive = fmt.Sprintf("%s %s: ", e.Verb, e.ModPath) - } else if e.Verb != "" { - directive = fmt.Sprintf("%s: ", e.Verb) - } - - return pos + directive + e.Err.Error() -} - -func (e *Error) Unwrap() error { return e.Err } - -func parseVersion(verb string, path string, s *string, fix VersionFixer) (string, error) { - t, err := parseString(s) - if err != nil { - return "", &Error{ - Verb: verb, - ModPath: path, - Err: &module.InvalidVersionError{ - Version: *s, - Err: err, - }, - } - } - if fix != nil { - fixed, err := fix(path, t) - if err != nil { - if err, ok := err.(*module.ModuleError); ok { - return "", &Error{ - Verb: verb, - ModPath: path, - Err: err.Err, - } - } - return "", err - } - t = fixed - } else { - cv := module.CanonicalVersion(t) - if cv == "" { - return "", &Error{ - Verb: verb, - ModPath: path, - Err: &module.InvalidVersionError{ - Version: t, - Err: errors.New("must be of the form v1.2.3"), - }, - } - } - t = cv - } - *s = t - return *s, nil -} - -func modulePathMajor(path string) (string, error) { - _, major, ok := module.SplitPathVersion(path) - if !ok { - return "", fmt.Errorf("invalid module path") - } - return major, nil -} - -func (f *File) Format() ([]byte, error) { - return Format(f.Syntax), nil -} - -// Cleanup cleans up the file f after any edit operations. -// To avoid quadratic behavior, modifications like [File.DropRequire] -// clear the entry but do not remove it from the slice. -// Cleanup cleans out all the cleared entries. -func (f *File) Cleanup() { - w := 0 - for _, g := range f.Godebug { - if g.Key != "" { - f.Godebug[w] = g - w++ - } - } - f.Godebug = f.Godebug[:w] - - w = 0 - for _, r := range f.Require { - if r.Mod.Path != "" { - f.Require[w] = r - w++ - } - } - f.Require = f.Require[:w] - - w = 0 - for _, x := range f.Exclude { - if x.Mod.Path != "" { - f.Exclude[w] = x - w++ - } - } - f.Exclude = f.Exclude[:w] - - w = 0 - for _, r := range f.Replace { - if r.Old.Path != "" { - f.Replace[w] = r - w++ - } - } - f.Replace = f.Replace[:w] - - w = 0 - for _, r := range f.Retract { - if r.Low != "" || r.High != "" { - f.Retract[w] = r - w++ - } - } - f.Retract = f.Retract[:w] - - f.Syntax.Cleanup() -} - -func (f *File) AddGoStmt(version string) error { - if !GoVersionRE.MatchString(version) { - return fmt.Errorf("invalid language version %q", version) - } - if f.Go == nil { - var hint Expr - if f.Module != nil && f.Module.Syntax != nil { - hint = f.Module.Syntax - } else if f.Syntax == nil { - f.Syntax = new(FileSyntax) - } - f.Go = &Go{ - Version: version, - Syntax: f.Syntax.addLine(hint, "go", version), - } - } else { - f.Go.Version = version - f.Syntax.updateLine(f.Go.Syntax, "go", version) - } - return nil -} - -// DropGoStmt deletes the go statement from the file. -func (f *File) DropGoStmt() { - if f.Go != nil { - f.Go.Syntax.markRemoved() - f.Go = nil - } -} - -// DropToolchainStmt deletes the toolchain statement from the file. -func (f *File) DropToolchainStmt() { - if f.Toolchain != nil { - f.Toolchain.Syntax.markRemoved() - f.Toolchain = nil - } -} - -func (f *File) AddToolchainStmt(name string) error { - if !ToolchainRE.MatchString(name) { - return fmt.Errorf("invalid toolchain name %q", name) - } - if f.Toolchain == nil { - var hint Expr - if f.Go != nil && f.Go.Syntax != nil { - hint = f.Go.Syntax - } else if f.Module != nil && f.Module.Syntax != nil { - hint = f.Module.Syntax - } - f.Toolchain = &Toolchain{ - Name: name, - Syntax: f.Syntax.addLine(hint, "toolchain", name), - } - } else { - f.Toolchain.Name = name - f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name) - } - return 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 *File) 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 *File) 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) -} - -// AddRequire sets the first require line for path to version vers, -// preserving any existing comments for that line and removing all -// other lines for path. -// -// If no line currently exists for path, AddRequire adds a new line -// at the end of the last require block. -func (f *File) AddRequire(path, vers string) error { - need := true - for _, r := range f.Require { - if r.Mod.Path == path { - if need { - r.Mod.Version = vers - f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers) - need = false - } else { - r.Syntax.markRemoved() - *r = Require{} - } - } - } - - if need { - f.AddNewRequire(path, vers, false) - } - return nil -} - -// AddNewRequire adds a new require line for path at version vers at the end of -// the last require block, regardless of any existing require lines for path. -func (f *File) AddNewRequire(path, vers string, indirect bool) { - line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers) - r := &Require{ - Mod: module.Version{Path: path, Version: vers}, - Syntax: line, - } - r.setIndirect(indirect) - f.Require = append(f.Require, r) -} - -// SetRequire updates the requirements of f to contain exactly req, preserving -// the existing block structure and line comment contents (except for 'indirect' -// markings) for the first requirement on each named module path. -// -// The Syntax field is ignored for the requirements in req. -// -// Any requirements not already present in the file are added to the block -// containing the last require line. -// -// The requirements in req must specify at most one distinct version for each -// module path. -// -// If any existing requirements may be removed, the caller should call -// [File.Cleanup] after all edits are complete. -func (f *File) SetRequire(req []*Require) { - type elem struct { - version string - indirect bool - } - need := make(map[string]elem) - for _, r := range req { - if prev, dup := need[r.Mod.Path]; dup && prev.version != r.Mod.Version { - panic(fmt.Errorf("SetRequire called with conflicting versions for path %s (%s and %s)", r.Mod.Path, prev.version, r.Mod.Version)) - } - need[r.Mod.Path] = elem{r.Mod.Version, r.Indirect} - } - - // Update or delete the existing Require entries to preserve - // only the first for each module path in req. - for _, r := range f.Require { - e, ok := need[r.Mod.Path] - if ok { - r.setVersion(e.version) - r.setIndirect(e.indirect) - } else { - r.markRemoved() - } - delete(need, r.Mod.Path) - } - - // Add new entries in the last block of the file for any paths that weren't - // already present. - // - // This step is nondeterministic, but the final result will be deterministic - // because we will sort the block. - for path, e := range need { - f.AddNewRequire(path, e.version, e.indirect) - } - - f.SortBlocks() -} - -// SetRequireSeparateIndirect updates the requirements of f to contain the given -// requirements. Comment contents (except for 'indirect' markings) are retained -// from the first existing requirement for each module path. Like SetRequire, -// SetRequireSeparateIndirect adds requirements for new paths in req, -// updates the version and "// indirect" comment on existing requirements, -// and deletes requirements on paths not in req. Existing duplicate requirements -// are deleted. -// -// As its name suggests, SetRequireSeparateIndirect puts direct and indirect -// requirements into two separate blocks, one containing only direct -// requirements, and the other containing only indirect requirements. -// SetRequireSeparateIndirect may move requirements between these two blocks -// when their indirect markings change. However, SetRequireSeparateIndirect -// won't move requirements from other blocks, especially blocks with comments. -// -// If the file initially has one uncommented block of requirements, -// SetRequireSeparateIndirect will split it into a direct-only and indirect-only -// block. This aids in the transition to separate blocks. -func (f *File) SetRequireSeparateIndirect(req []*Require) { - // hasComments returns whether a line or block has comments - // other than "indirect". - hasComments := func(c Comments) bool { - return len(c.Before) > 0 || len(c.After) > 0 || len(c.Suffix) > 1 || - (len(c.Suffix) == 1 && - strings.TrimSpace(strings.TrimPrefix(c.Suffix[0].Token, string(slashSlash))) != "indirect") - } - - // moveReq adds r to block. If r was in another block, moveReq deletes - // it from that block and transfers its comments. - moveReq := func(r *Require, block *LineBlock) { - var line *Line - if r.Syntax == nil { - line = &Line{Token: []string{AutoQuote(r.Mod.Path), r.Mod.Version}} - r.Syntax = line - if r.Indirect { - r.setIndirect(true) - } - } else { - line = new(Line) - *line = *r.Syntax - if !line.InBlock && len(line.Token) > 0 && line.Token[0] == "require" { - line.Token = line.Token[1:] - } - r.Syntax.Token = nil // Cleanup will delete the old line. - r.Syntax = line - } - line.InBlock = true - block.Line = append(block.Line, line) - } - - // Examine existing require lines and blocks. - var ( - // We may insert new requirements into the last uncommented - // direct-only and indirect-only blocks. We may also move requirements - // to the opposite block if their indirect markings change. - lastDirectIndex = -1 - lastIndirectIndex = -1 - - // If there are no direct-only or indirect-only blocks, a new block may - // be inserted after the last require line or block. - lastRequireIndex = -1 - - // If there's only one require line or block, and it's uncommented, - // we'll move its requirements to the direct-only or indirect-only blocks. - requireLineOrBlockCount = 0 - - // Track the block each requirement belongs to (if any) so we can - // move them later. - lineToBlock = make(map[*Line]*LineBlock) - ) - for i, stmt := range f.Syntax.Stmt { - switch stmt := stmt.(type) { - case *Line: - if len(stmt.Token) == 0 || stmt.Token[0] != "require" { - continue - } - lastRequireIndex = i - requireLineOrBlockCount++ - if !hasComments(stmt.Comments) { - if isIndirect(stmt) { - lastIndirectIndex = i - } else { - lastDirectIndex = i - } - } - - case *LineBlock: - if len(stmt.Token) == 0 || stmt.Token[0] != "require" { - continue - } - lastRequireIndex = i - requireLineOrBlockCount++ - allDirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments) - allIndirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments) - for _, line := range stmt.Line { - lineToBlock[line] = stmt - if hasComments(line.Comments) { - allDirect = false - allIndirect = false - } else if isIndirect(line) { - allDirect = false - } else { - allIndirect = false - } - } - if allDirect { - lastDirectIndex = i - } - if allIndirect { - lastIndirectIndex = i - } - } - } - - oneFlatUncommentedBlock := requireLineOrBlockCount == 1 && - !hasComments(*f.Syntax.Stmt[lastRequireIndex].Comment()) - - // Create direct and indirect blocks if needed. Convert lines into blocks - // if needed. If we end up with an empty block or a one-line block, - // Cleanup will delete it or convert it to a line later. - insertBlock := func(i int) *LineBlock { - block := &LineBlock{Token: []string{"require"}} - f.Syntax.Stmt = append(f.Syntax.Stmt, nil) - copy(f.Syntax.Stmt[i+1:], f.Syntax.Stmt[i:]) - f.Syntax.Stmt[i] = block - return block - } - - ensureBlock := func(i int) *LineBlock { - switch stmt := f.Syntax.Stmt[i].(type) { - case *LineBlock: - return stmt - case *Line: - block := &LineBlock{ - Token: []string{"require"}, - Line: []*Line{stmt}, - } - stmt.Token = stmt.Token[1:] // remove "require" - stmt.InBlock = true - f.Syntax.Stmt[i] = block - return block - default: - panic(fmt.Sprintf("unexpected statement: %v", stmt)) - } - } - - var lastDirectBlock *LineBlock - if lastDirectIndex < 0 { - if lastIndirectIndex >= 0 { - lastDirectIndex = lastIndirectIndex - lastIndirectIndex++ - } else if lastRequireIndex >= 0 { - lastDirectIndex = lastRequireIndex + 1 - } else { - lastDirectIndex = len(f.Syntax.Stmt) - } - lastDirectBlock = insertBlock(lastDirectIndex) - } else { - lastDirectBlock = ensureBlock(lastDirectIndex) - } - - var lastIndirectBlock *LineBlock - if lastIndirectIndex < 0 { - lastIndirectIndex = lastDirectIndex + 1 - lastIndirectBlock = insertBlock(lastIndirectIndex) - } else { - lastIndirectBlock = ensureBlock(lastIndirectIndex) - } - - // Delete requirements we don't want anymore. - // Update versions and indirect comments on requirements we want to keep. - // If a requirement is in last{Direct,Indirect}Block with the wrong - // indirect marking after this, or if the requirement is in an single - // uncommented mixed block (oneFlatUncommentedBlock), move it to the - // correct block. - // - // Some blocks may be empty after this. Cleanup will remove them. - need := make(map[string]*Require) - for _, r := range req { - need[r.Mod.Path] = r - } - have := make(map[string]*Require) - for _, r := range f.Require { - path := r.Mod.Path - if need[path] == nil || have[path] != nil { - // Requirement not needed, or duplicate requirement. Delete. - r.markRemoved() - continue - } - have[r.Mod.Path] = r - r.setVersion(need[path].Mod.Version) - r.setIndirect(need[path].Indirect) - if need[path].Indirect && - (oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastDirectBlock) { - moveReq(r, lastIndirectBlock) - } else if !need[path].Indirect && - (oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastIndirectBlock) { - moveReq(r, lastDirectBlock) - } - } - - // Add new requirements. - for path, r := range need { - if have[path] == nil { - if r.Indirect { - moveReq(r, lastIndirectBlock) - } else { - moveReq(r, lastDirectBlock) - } - f.Require = append(f.Require, r) - } - } - - f.SortBlocks() -} - -func (f *File) DropGodebug(key string) error { - for _, g := range f.Godebug { - if g.Key == key { - g.Syntax.markRemoved() - *g = Godebug{} - } - } - return nil -} - -func (f *File) DropRequire(path string) error { - for _, r := range f.Require { - if r.Mod.Path == path { - r.Syntax.markRemoved() - *r = Require{} - } - } - return nil -} - -// AddExclude adds a exclude statement to the mod file. Errors if the provided -// version is not a canonical version string -func (f *File) AddExclude(path, vers string) error { - if err := checkCanonicalVersion(path, vers); err != nil { - return err - } - - var hint *Line - for _, x := range f.Exclude { - if x.Mod.Path == path && x.Mod.Version == vers { - return nil - } - if x.Mod.Path == path { - hint = x.Syntax - } - } - - f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)}) - return nil -} - -func (f *File) DropExclude(path, vers string) error { - for _, x := range f.Exclude { - if x.Mod.Path == path && x.Mod.Version == vers { - x.Syntax.markRemoved() - *x = Exclude{} - } - } - return nil -} - -func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error { - return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers) -} - -func addReplace(syntax *FileSyntax, replace *[]*Replace, oldPath, oldVers, newPath, newVers string) error { - need := true - old := module.Version{Path: oldPath, Version: oldVers} - new := module.Version{Path: newPath, Version: newVers} - tokens := []string{"replace", AutoQuote(oldPath)} - if oldVers != "" { - tokens = append(tokens, oldVers) - } - tokens = append(tokens, "=>", AutoQuote(newPath)) - if newVers != "" { - tokens = append(tokens, newVers) - } - - var hint *Line - for _, r := range *replace { - if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) { - if need { - // Found replacement for old; update to use new. - r.New = new - syntax.updateLine(r.Syntax, tokens...) - need = false - continue - } - // Already added; delete other replacements for same. - r.Syntax.markRemoved() - *r = Replace{} - } - if r.Old.Path == oldPath { - hint = r.Syntax - } - } - if need { - *replace = append(*replace, &Replace{Old: old, New: new, Syntax: syntax.addLine(hint, tokens...)}) - } - return nil -} - -func (f *File) 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 -} - -// AddRetract adds a retract statement to the mod file. Errors if the provided -// version interval does not consist of canonical version strings -func (f *File) AddRetract(vi VersionInterval, rationale string) error { - var path string - if f.Module != nil { - path = f.Module.Mod.Path - } - if err := checkCanonicalVersion(path, vi.High); err != nil { - return err - } - if err := checkCanonicalVersion(path, vi.Low); err != nil { - return err - } - - r := &Retract{ - VersionInterval: vi, - } - if vi.Low == vi.High { - r.Syntax = f.Syntax.addLine(nil, "retract", AutoQuote(vi.Low)) - } else { - r.Syntax = f.Syntax.addLine(nil, "retract", "[", AutoQuote(vi.Low), ",", AutoQuote(vi.High), "]") - } - if rationale != "" { - for _, line := range strings.Split(rationale, "\n") { - com := Comment{Token: "// " + line} - r.Syntax.Comment().Before = append(r.Syntax.Comment().Before, com) - } - } - return nil -} - -func (f *File) DropRetract(vi VersionInterval) error { - for _, r := range f.Retract { - if r.VersionInterval == vi { - r.Syntax.markRemoved() - *r = Retract{} - } - } - return nil -} - -// AddTool adds a new tool directive with the given path. -// It does nothing if the tool line already exists. -func (f *File) AddTool(path string) error { - for _, t := range f.Tool { - if t.Path == path { - return nil - } - } - - f.Tool = append(f.Tool, &Tool{ - Path: path, - Syntax: f.Syntax.addLine(nil, "tool", path), - }) - - f.SortBlocks() - return nil -} - -// RemoveTool removes a tool directive with the given path. -// It does nothing if no such tool directive exists. -func (f *File) DropTool(path string) error { - for _, t := range f.Tool { - if t.Path == path { - t.Syntax.markRemoved() - *t = Tool{} - } - } - return nil -} - -// AddIgnore adds a new ignore directive with the given path. -// It does nothing if the ignore line already exists. -func (f *File) AddIgnore(path string) error { - for _, t := range f.Ignore { - if t.Path == path { - return nil - } - } - - f.Ignore = append(f.Ignore, &Ignore{ - Path: path, - Syntax: f.Syntax.addLine(nil, "ignore", path), - }) - - f.SortBlocks() - return nil -} - -// DropIgnore removes a ignore directive with the given path. -// It does nothing if no such ignore directive exists. -func (f *File) DropIgnore(path string) error { - for _, t := range f.Ignore { - if t.Path == path { - t.Syntax.markRemoved() - *t = Ignore{} - } - } - return nil -} - -func (f *File) SortBlocks() { - f.removeDups() // otherwise sorting is unsafe - - // semanticSortForExcludeVersionV is the Go version (plus leading "v") at which - // lines in exclude blocks start to use semantic sort instead of lexicographic sort. - // See go.dev/issue/60028. - const semanticSortForExcludeVersionV = "v1.21" - useSemanticSortForExclude := f.Go != nil && semver.Compare("v"+f.Go.Version, semanticSortForExcludeVersionV) >= 0 - - for _, stmt := range f.Syntax.Stmt { - block, ok := stmt.(*LineBlock) - if !ok { - continue - } - less := compareLine - if block.Token[0] == "exclude" && useSemanticSortForExclude { - less = compareLineExclude - } else if block.Token[0] == "retract" { - less = compareLineRetract - } - slices.SortStableFunc(block.Line, less) - } -} - -// removeDups removes duplicate exclude, replace and tool directives. -// -// Earlier exclude and tool directives take priority. -// -// 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 *File) removeDups() { - removeDups(f.Syntax, &f.Exclude, &f.Replace, &f.Tool, &f.Ignore) -} - -func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace, tool *[]*Tool, ignore *[]*Ignore) { - kill := make(map[*Line]bool) - - // Remove duplicate excludes. - if exclude != nil { - haveExclude := make(map[module.Version]bool) - for _, x := range *exclude { - if haveExclude[x.Mod] { - kill[x.Syntax] = true - continue - } - haveExclude[x.Mod] = true - } - var excl []*Exclude - for _, x := range *exclude { - if !kill[x.Syntax] { - excl = append(excl, x) - } - } - *exclude = excl - } - - // Remove duplicate replacements. - // Later replacements take priority over earlier ones. - haveReplace := make(map[module.Version]bool) - for i := len(*replace) - 1; i >= 0; i-- { - x := (*replace)[i] - if haveReplace[x.Old] { - kill[x.Syntax] = true - continue - } - haveReplace[x.Old] = true - } - var repl []*Replace - for _, x := range *replace { - if !kill[x.Syntax] { - repl = append(repl, x) - } - } - *replace = repl - - if tool != nil { - haveTool := make(map[string]bool) - for _, t := range *tool { - if haveTool[t.Path] { - kill[t.Syntax] = true - continue - } - haveTool[t.Path] = true - } - var newTool []*Tool - for _, t := range *tool { - if !kill[t.Syntax] { - newTool = append(newTool, t) - } - } - *tool = newTool - } - - if ignore != nil { - haveIgnore := make(map[string]bool) - for _, i := range *ignore { - if haveIgnore[i.Path] { - kill[i.Syntax] = true - continue - } - haveIgnore[i.Path] = true - } - var newIgnore []*Ignore - for _, i := range *ignore { - if !kill[i.Syntax] { - newIgnore = append(newIgnore, i) - } - } - *ignore = newIgnore - } - - // Duplicate require and retract directives are not removed. - - // Drop killed statements from the syntax tree. - var stmts []Expr - for _, stmt := range syntax.Stmt { - switch stmt := stmt.(type) { - case *Line: - if kill[stmt] { - continue - } - case *LineBlock: - var lines []*Line - for _, line := range stmt.Line { - if !kill[line] { - lines = append(lines, line) - } - } - stmt.Line = lines - if len(lines) == 0 { - continue - } - } - stmts = append(stmts, stmt) - } - syntax.Stmt = stmts -} - -// compareLine compares li and lj. It sorts lexicographically without assigning -// any special meaning to tokens. -func compareLine(li, lj *Line) int { - for k := 0; k < len(li.Token) && k < len(lj.Token); k++ { - if li.Token[k] != lj.Token[k] { - return cmp.Compare(li.Token[k], lj.Token[k]) - } - } - return cmp.Compare(len(li.Token), len(lj.Token)) -} - -// compareLineExclude compares li and lj for lines in an "exclude" block. -func compareLineExclude(li, lj *Line) int { - if len(li.Token) != 2 || len(lj.Token) != 2 { - // Not a known exclude specification. - // Fall back to sorting lexicographically. - return compareLine(li, lj) - } - // An exclude specification has two tokens: ModulePath and Version. - // Compare module path by string order and version by semver rules. - if pi, pj := li.Token[0], lj.Token[0]; pi != pj { - return cmp.Compare(pi, pj) - } - return semver.Compare(li.Token[1], lj.Token[1]) -} - -// compareLineRetract compares li and lj for lines in a "retract" block. -// It treats each line as a version interval. Single versions are compared as -// if they were intervals with the same low and high version. -// Intervals are sorted in descending order, first by low version, then by -// high version, using [semver.Compare]. -func compareLineRetract(li, lj *Line) int { - interval := func(l *Line) VersionInterval { - if len(l.Token) == 1 { - return VersionInterval{Low: l.Token[0], High: l.Token[0]} - } else if len(l.Token) == 5 && l.Token[0] == "[" && l.Token[2] == "," && l.Token[4] == "]" { - return VersionInterval{Low: l.Token[1], High: l.Token[3]} - } else { - // Line in unknown format. Treat as an invalid version. - return VersionInterval{} - } - } - vii := interval(li) - vij := interval(lj) - if cmp := semver.Compare(vii.Low, vij.Low); cmp != 0 { - return -cmp - } - return -semver.Compare(vii.High, vij.High) -} - -// checkCanonicalVersion returns a non-nil error if vers is not a canonical -// version string or does not match the major version of path. -// -// If path is non-empty, the error text suggests a format with a major version -// corresponding to the path. -func checkCanonicalVersion(path, vers string) error { - _, pathMajor, pathMajorOk := module.SplitPathVersion(path) - - if vers == "" || vers != module.CanonicalVersion(vers) { - if pathMajor == "" { - return &module.InvalidVersionError{ - Version: vers, - Err: fmt.Errorf("must be of the form v1.2.3"), - } - } - return &module.InvalidVersionError{ - Version: vers, - Err: fmt.Errorf("must be of the form %s.2.3", module.PathMajorPrefix(pathMajor)), - } - } - - if pathMajorOk { - if err := module.CheckPathMajor(vers, pathMajor); err != nil { - if pathMajor == "" { - // In this context, the user probably wrote "v2.3.4" when they meant - // "v2.3.4+incompatible". Suggest that instead of "v0 or v1". - return &module.InvalidVersionError{ - Version: vers, - Err: fmt.Errorf("should be %s+incompatible (or module %s/%v)", vers, path, semver.Major(vers)), - } - } - return err - } - } - - return nil -} diff --git a/vendor/golang.org/x/mod/modfile/work.go b/vendor/golang.org/x/mod/modfile/work.go deleted file mode 100644 index 09df5ea3..00000000 --- a/vendor/golang.org/x/mod/modfile/work.go +++ /dev/null @@ -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) -} diff --git a/vendor/modules.txt b/vendor/modules.txt index e814ec7e..59ad61c7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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