From 3fb913df45f1a288660f7e4c104ddc1b42cc4cb2 Mon Sep 17 00:00:00 2001 From: Akash More Date: Thu, 9 Jul 2026 18:56:51 +0530 Subject: [PATCH] CVE-2026-40898: bumping quic-go to v0.59.1 --- go.mod | 2 +- go.sum | 4 +- .../github.com/quic-go/quic-go/.golangci.yml | 2 + vendor/github.com/quic-go/quic-go/README.md | 12 +- vendor/github.com/quic-go/quic-go/SECURITY.md | 17 +- vendor/github.com/quic-go/quic-go/codecov.yml | 2 - .../quic-go/quic-go/conn_id_generator.go | 7 - .../github.com/quic-go/quic-go/connection.go | 669 ++++-- .../quic-go/quic-go/connection_logging.go | 295 ++- .../quic-go/quic-go/frame_sorter.go | 37 + .../github.com/quic-go/quic-go/interface.go | 43 +- .../quic-go/internal/ackhandler/ackhandler.go | 25 - .../quic-go/internal/ackhandler/ecn.go | 94 +- .../quic-go/internal/ackhandler/interfaces.go | 16 +- .../quic-go/internal/ackhandler/mockgen.go | 3 - .../quic-go/internal/ackhandler/packet.go | 10 +- .../ackhandler/received_packet_handler.go | 32 +- .../ackhandler/received_packet_tracker.go | 9 +- .../ackhandler/sent_packet_handler.go | 296 ++- .../ackhandler/sent_packet_history.go | 21 +- .../quic-go/internal/congestion/bandwidth.go | 3 - .../internal/congestion/cubic_sender.go | 43 +- .../quic-go/internal/congestion/pacer.go | 31 +- .../quic-go/internal/handshake/aead.go | 2 +- .../internal/handshake/cipher_suite.go | 8 +- .../internal/handshake/crypto_setup.go | 137 +- .../internal/handshake/header_protector.go | 6 +- .../internal/handshake/updatable_aead.go | 70 +- .../quic-go/internal/protocol/params.go | 11 +- .../quic-go/internal/utils/rtt_stats.go | 41 +- .../quic-go/internal/wire/ack_frame.go | 69 +- .../quic-go/logging/connection_tracer.go | 45 - .../logging/connection_tracer_multiplexer.go | 243 --- .../quic-go/quic-go/logging/frame.go | 70 - .../quic-go/logging/generate_multiplexer.go | 161 -- .../quic-go/quic-go/logging/interface.go | 111 - .../quic-go/quic-go/logging/multiplexer.tmpl | 21 - .../quic-go/quic-go/logging/packet_header.go | 24 - .../quic-go/quic-go/logging/tracer.go | 14 - .../quic-go/logging/tracer_multiplexer.go | 51 - .../quic-go/quic-go/logging/types.go | 130 -- .../quic-go/quic-go/mtu_discoverer.go | 16 +- .../quic-go/quic-go/packet_packer.go | 42 +- .../github.com/quic-go/quic-go/qlog/event.go | 849 ++++++++ .../github.com/quic-go/quic-go/qlog/frame.go | 481 +++++ .../quic-go/quic-go/qlog/packet_header.go | 96 + .../quic-go/quic-go/qlog/qlog_dir.go | 61 + .../github.com/quic-go/quic-go/qlog/types.go | 304 +++ .../quic-go/qlogwriter/jsontext/encoder.go | 324 +++ .../quic-go/quic-go/qlogwriter/trace.go | 124 ++ .../quic-go/quic-go/qlogwriter/writer.go | 229 ++ .../quic-go/quic-go/quicvarint/io.go | 27 +- .../quic-go/quic-go/quicvarint/varint.go | 17 +- .../quic-go/quic-go/receive_stream.go | 153 +- vendor/github.com/quic-go/quic-go/server.go | 246 ++- vendor/github.com/quic-go/quic-go/stream.go | 17 + .../github.com/quic-go/quic-go/transport.go | 52 +- .../x/mod/internal/lazyregexp/lazyre.go | 78 - vendor/golang.org/x/mod/module/module.go | 840 -------- vendor/golang.org/x/mod/module/pseudo.go | 250 --- .../x/tools/go/ast/astutil/enclosing.go | 663 ------ .../x/tools/go/ast/astutil/imports.go | 487 ----- .../x/tools/go/ast/astutil/rewrite.go | 490 ----- .../golang.org/x/tools/go/ast/astutil/util.go | 13 - vendor/golang.org/x/tools/imports/forward.go | 77 - .../x/tools/internal/gopathwalk/walk.go | 336 --- .../x/tools/internal/imports/fix.go | 1896 ----------------- .../x/tools/internal/imports/imports.go | 359 ---- .../x/tools/internal/imports/mod.go | 841 -------- .../x/tools/internal/imports/mod_cache.go | 331 --- .../x/tools/internal/imports/sortimports.go | 313 --- .../x/tools/internal/imports/source.go | 63 - .../x/tools/internal/imports/source_env.go | 129 -- .../tools/internal/imports/source_modindex.go | 100 - .../x/tools/internal/modindex/directories.go | 131 -- .../x/tools/internal/modindex/index.go | 292 --- .../x/tools/internal/modindex/lookup.go | 184 -- .../x/tools/internal/modindex/modindex.go | 119 -- .../x/tools/internal/modindex/symbols.go | 244 --- vendor/modules.txt | 13 +- 80 files changed, 4276 insertions(+), 9898 deletions(-) delete mode 100644 vendor/github.com/quic-go/quic-go/internal/ackhandler/ackhandler.go delete mode 100644 vendor/github.com/quic-go/quic-go/logging/connection_tracer.go delete mode 100644 vendor/github.com/quic-go/quic-go/logging/connection_tracer_multiplexer.go delete mode 100644 vendor/github.com/quic-go/quic-go/logging/frame.go delete mode 100644 vendor/github.com/quic-go/quic-go/logging/generate_multiplexer.go delete mode 100644 vendor/github.com/quic-go/quic-go/logging/interface.go delete mode 100644 vendor/github.com/quic-go/quic-go/logging/multiplexer.tmpl delete mode 100644 vendor/github.com/quic-go/quic-go/logging/packet_header.go delete mode 100644 vendor/github.com/quic-go/quic-go/logging/tracer.go delete mode 100644 vendor/github.com/quic-go/quic-go/logging/tracer_multiplexer.go delete mode 100644 vendor/github.com/quic-go/quic-go/logging/types.go create mode 100644 vendor/github.com/quic-go/quic-go/qlog/event.go create mode 100644 vendor/github.com/quic-go/quic-go/qlog/frame.go create mode 100644 vendor/github.com/quic-go/quic-go/qlog/packet_header.go create mode 100644 vendor/github.com/quic-go/quic-go/qlog/qlog_dir.go create mode 100644 vendor/github.com/quic-go/quic-go/qlog/types.go create mode 100644 vendor/github.com/quic-go/quic-go/qlogwriter/jsontext/encoder.go create mode 100644 vendor/github.com/quic-go/quic-go/qlogwriter/trace.go create mode 100644 vendor/github.com/quic-go/quic-go/qlogwriter/writer.go delete mode 100644 vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go delete mode 100644 vendor/golang.org/x/mod/module/module.go delete mode 100644 vendor/golang.org/x/mod/module/pseudo.go delete mode 100644 vendor/golang.org/x/tools/go/ast/astutil/enclosing.go delete mode 100644 vendor/golang.org/x/tools/go/ast/astutil/imports.go delete mode 100644 vendor/golang.org/x/tools/go/ast/astutil/rewrite.go delete mode 100644 vendor/golang.org/x/tools/go/ast/astutil/util.go delete mode 100644 vendor/golang.org/x/tools/imports/forward.go delete mode 100644 vendor/golang.org/x/tools/internal/gopathwalk/walk.go delete mode 100644 vendor/golang.org/x/tools/internal/imports/fix.go delete mode 100644 vendor/golang.org/x/tools/internal/imports/imports.go delete mode 100644 vendor/golang.org/x/tools/internal/imports/mod.go delete mode 100644 vendor/golang.org/x/tools/internal/imports/mod_cache.go delete mode 100644 vendor/golang.org/x/tools/internal/imports/sortimports.go delete mode 100644 vendor/golang.org/x/tools/internal/imports/source.go delete mode 100644 vendor/golang.org/x/tools/internal/imports/source_env.go delete mode 100644 vendor/golang.org/x/tools/internal/imports/source_modindex.go delete mode 100644 vendor/golang.org/x/tools/internal/modindex/directories.go delete mode 100644 vendor/golang.org/x/tools/internal/modindex/index.go delete mode 100644 vendor/golang.org/x/tools/internal/modindex/lookup.go delete mode 100644 vendor/golang.org/x/tools/internal/modindex/modindex.go delete mode 100644 vendor/golang.org/x/tools/internal/modindex/symbols.go diff --git a/go.mod b/go.mod index 8146f7560f..2f74baa2ef 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/prometheus/client_golang v1.23.0 github.com/prometheus/client_model v0.6.2 github.com/prometheus/common v0.66.1 - github.com/quic-go/quic-go v0.55.0 + github.com/quic-go/quic-go v0.59.1 go.etcd.io/etcd/api/v3 v3.6.5 go.etcd.io/etcd/client/v3 v3.6.5 go.uber.org/automaxprocs v1.6.0 diff --git a/go.sum b/go.sum index ddbb4dc8b4..e6d93d5bc4 100644 --- a/go.sum +++ b/go.sum @@ -321,8 +321,8 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= -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/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic= +github.com/quic-go/quic-go v0.59.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 h1:4+LEVOB87y175cLJC/mbsgKmoDOjrBldtXvioEy96WY= github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3/go.mod h1:vl5+MqJ1nBINuSsUI2mGgH79UweUT/B5Fy8857PqyyI= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= diff --git a/vendor/github.com/quic-go/quic-go/.golangci.yml b/vendor/github.com/quic-go/quic-go/.golangci.yml index 4a97047baf..cd2a3f87f5 100644 --- a/vendor/github.com/quic-go/quic-go/.golangci.yml +++ b/vendor/github.com/quic-go/quic-go/.golangci.yml @@ -53,6 +53,8 @@ linters: deny: - pkg: 'github.com/quic-go/quic-go/internal' desc: 'no dependency on quic-go/internal' + allow: + - 'github.com/quic-go/quic-go/internal/synctest' misspell: ignore-rules: - ect diff --git a/vendor/github.com/quic-go/quic-go/README.md b/vendor/github.com/quic-go/quic-go/README.md index 246c330490..85751dbe70 100644 --- a/vendor/github.com/quic-go/quic-go/README.md +++ b/vendor/github.com/quic-go/quic-go/README.md @@ -1,11 +1,14 @@ +
+ +
+ # A QUIC implementation in pure Go - [![Documentation](https://img.shields.io/badge/docs-quic--go.net-red?style=flat)](https://quic-go.net/docs/) [![PkgGoDev](https://pkg.go.dev/badge/github.com/quic-go/quic-go)](https://pkg.go.dev/github.com/quic-go/quic-go) [![Code Coverage](https://img.shields.io/codecov/c/github/quic-go/quic-go/master.svg?style=flat-square)](https://codecov.io/gh/quic-go/quic-go/) -[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/quic-go.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:quic-go) +[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/quic-go.svg)](https://issues.oss-fuzz.com/issues?q=quic-go) quic-go is an implementation of the QUIC protocol ([RFC 9000](https://datatracker.ietf.org/doc/html/rfc9000), [RFC 9001](https://datatracker.ietf.org/doc/html/rfc9001), [RFC 9002](https://datatracker.ietf.org/doc/html/rfc9002)) in Go. It has support for HTTP/3 ([RFC 9114](https://datatracker.ietf.org/doc/html/rfc9114)), including QPACK ([RFC 9204](https://datatracker.ietf.org/doc/html/rfc9204)) and HTTP Datagrams ([RFC 9297](https://datatracker.ietf.org/doc/html/rfc9297)). @@ -34,6 +37,7 @@ Detailed documentation can be found on [quic-go.net](https://quic-go.net/docs/). | [gost](https://github.com/go-gost/gost) | A simple security tunnel written in Go | ![GitHub Repo stars](https://img.shields.io/github/stars/go-gost/gost?style=flat-square) | | [Hysteria](https://github.com/apernet/hysteria) | A powerful, lightning fast and censorship resistant proxy | ![GitHub Repo stars](https://img.shields.io/github/stars/apernet/hysteria?style=flat-square) | | [Mercure](https://github.com/dunglas/mercure) | An open, easy, fast, reliable and battery-efficient solution for real-time communications | ![GitHub Repo stars](https://img.shields.io/github/stars/dunglas/mercure?style=flat-square) | +| [nodepass](https://github.com/yosebyte/nodepass) | A secure, efficient TCP/UDP tunneling solution that delivers fast, reliable access across network restrictions using pre-established TCP/QUIC connections | ![GitHub Repo stars](https://img.shields.io/github/stars/yosebyte/nodepass?style=flat-square) | | [OONI Probe](https://github.com/ooni/probe-cli) | Next generation OONI Probe. Library and CLI tool. | ![GitHub Repo stars](https://img.shields.io/github/stars/ooni/probe-cli?style=flat-square) | | [reverst](https://github.com/flipt-io/reverst) | Reverse Tunnels in Go over HTTP/3 and QUIC | ![GitHub Repo stars](https://img.shields.io/github/stars/flipt-io/reverst?style=flat-square) | | [RoadRunner](https://github.com/roadrunner-server/roadrunner) | High-performance PHP application server, process manager written in Go and powered with plugins | ![GitHub Repo stars](https://img.shields.io/github/stars/roadrunner-server/roadrunner?style=flat-square) | @@ -51,3 +55,7 @@ quic-go always aims to support the latest two Go releases. ## Contributing We are always happy to welcome new contributors! We have a number of self-contained issues that are suitable for first-time contributors, they are tagged with [help wanted](https://github.com/quic-go/quic-go/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22). If you have any questions, please feel free to reach out by opening an issue or leaving a comment. + +## License + +The code is licensed under the MIT license. The logo and brand assets are excluded from the MIT license. See [assets/LICENSE.md](https://github.com/quic-go/quic-go/tree/master/assets/LICENSE.md) for the full usage policy and details. diff --git a/vendor/github.com/quic-go/quic-go/SECURITY.md b/vendor/github.com/quic-go/quic-go/SECURITY.md index c24c08f863..79fe1f568f 100644 --- a/vendor/github.com/quic-go/quic-go/SECURITY.md +++ b/vendor/github.com/quic-go/quic-go/SECURITY.md @@ -1,19 +1,14 @@ # Security Policy -quic-go still in development. This means that there may be problems in our protocols, -or there may be mistakes in our implementations. -We take security vulnerabilities very seriously. If you discover a security issue, -please bring it to our attention right away! +quic-go is an implementation of the QUIC protocol and related standards. No software is perfect, and we take reports of potential security issues very seriously. ## Reporting a Vulnerability -If you find a vulnerability that may affect live deployments -- for example, by exposing -a remote execution exploit -- please [**report privately**](https://github.com/quic-go/quic-go/security/advisories/new). -Please **DO NOT file a public issue**. +If you discover a vulnerability that could affect production deployments (e.g., a remotely exploitable issue), please report it [**privately**](https://github.com/quic-go/quic-go/security/advisories/new). +Please **DO NOT file a public issue** for exploitable vulnerabilities. -If the issue is an implementation weakness that cannot be immediately exploited or -something not yet deployed, just discuss it openly. +If the issue is theoretical, non-exploitable, or related to an experimental feature, you may discuss it openly by filing a regular issue. -## Reporting a non security bug +## Reporting a non-security bug -For non-security bugs, please simply file a GitHub [issue](https://github.com/quic-go/quic-go/issues/new). +For bugs, feature requests, or other non-security concerns, please open a GitHub [issue](https://github.com/quic-go/quic-go/issues/new). diff --git a/vendor/github.com/quic-go/quic-go/codecov.yml b/vendor/github.com/quic-go/quic-go/codecov.yml index e5a166ef3e..58c94e9bdd 100644 --- a/vendor/github.com/quic-go/quic-go/codecov.yml +++ b/vendor/github.com/quic-go/quic-go/codecov.yml @@ -9,8 +9,6 @@ coverage: - internal/utils/linkedlist/linkedlist.go - internal/testdata - internal/synctest - - logging/connection_tracer_multiplexer.go - - logging/tracer_multiplexer.go - testutils/ - fuzzing/ - metrics/ 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 c2b3545052..133932a6a5 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 @@ -157,13 +157,6 @@ func (m *connIDGenerator) SetHandshakeComplete(connIDExpiry monotime.Time) { } } -func (m *connIDGenerator) NextRetireTime() monotime.Time { - if len(m.connIDsToRetire) == 0 { - return 0 - } - return m.connIDsToRetire[0].t -} - 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 568086a111..1cb396fcc2 100644 --- a/vendor/github.com/quic-go/quic-go/connection.go +++ b/vendor/github.com/quic-go/quic-go/connection.go @@ -23,7 +23,8 @@ import ( "github.com/quic-go/quic-go/internal/utils" "github.com/quic-go/quic-go/internal/utils/ringbuffer" "github.com/quic-go/quic-go/internal/wire" - "github.com/quic-go/quic-go/logging" + "github.com/quic-go/quic-go/qlog" + "github.com/quic-go/quic-go/qlogwriter" ) type unpacker interface { @@ -56,6 +57,11 @@ type receivedPacket struct { info packetInfo // only valid if the contained IP address is valid } +type receivedPacketWithDatagramID struct { + receivedPacket + datagramID qlog.DatagramID +} + func (p *receivedPacket) Size() protocol.ByteCount { return protocol.ByteCount(len(p.data)) } func (p *receivedPacket) Clone() *receivedPacket { @@ -93,9 +99,6 @@ func (e *errCloseForRecreating) Error() string { 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 ( @@ -182,8 +185,8 @@ type Conn struct { ctxCancel context.CancelCauseFunc handshakeCompleteChan chan struct{} - undecryptablePackets []receivedPacket // undecryptable packets, waiting for a change in encryption level - undecryptablePacketsToProcess []receivedPacket + undecryptablePackets []receivedPacketWithDatagramID // undecryptable packets, waiting for a change in encryption level + undecryptablePacketsToProcess []receivedPacketWithDatagramID earlyConnReadyChan chan struct{} sentFirstPacket bool @@ -220,9 +223,10 @@ type Conn struct { connStateMutex sync.Mutex connState ConnectionState - logID string - tracer *logging.ConnectionTracer - logger utils.Logger + logID string + qlogTrace qlogwriter.Trace + qlogger qlogwriter.Recorder + logger utils.Logger } var _ streamSender = &Conn{} @@ -259,7 +263,7 @@ var newConnection = func( tokenGenerator *handshake.TokenGenerator, clientAddressValidated bool, rtt time.Duration, - tracer *logging.ConnectionTracer, + qlogTrace qlogwriter.Trace, logger utils.Logger, v protocol.Version, ) *wrappedConn { @@ -273,10 +277,13 @@ var newConnection = func( tokenGenerator: tokenGenerator, oneRTTStream: newCryptoStream(), perspective: protocol.PerspectiveServer, - tracer: tracer, + qlogTrace: qlogTrace, logger: logger, version: v, } + if qlogTrace != nil { + s.qlogger = qlogTrace.AddProducer() + } if origDestConnID.Len() > 0 { s.logID = origDestConnID.String() } else { @@ -303,15 +310,16 @@ var newConnection = func( ) s.preSetup() s.rttStats.SetInitialRTT(rtt) - s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler( + s.sentPacketHandler = ackhandler.NewSentPacketHandler( 0, protocol.ByteCount(s.config.InitialPacketSize), s.rttStats, &s.connStats, clientAddressValidated, s.conn.capabilities().ECN, + s.receivedPacketHandler.IgnorePacketsBelow, s.perspective, - s.tracer, + s.qlogger, s.logger, ) s.currentMTUEstimate.Store(uint32(estimateMaxPayloadSize(protocol.ByteCount(s.config.InitialPacketSize)))) @@ -344,8 +352,8 @@ var newConnection = func( } else { params.MaxDatagramFrameSize = protocol.InvalidByteCount } - if s.tracer != nil && s.tracer.SentTransportParameters != nil { - s.tracer.SentTransportParameters(params) + if s.qlogger != nil { + s.qlogTransportParameters(params, protocol.PerspectiveServer, false) } cs := handshake.NewCryptoSetupServer( clientDestConnID, @@ -355,12 +363,12 @@ var newConnection = func( tlsConf, conf.Allow0RTT, s.rttStats, - tracer, + s.qlogger, logger, s.version, ) s.cryptoStreamHandler = cs - s.packer = newPacketPacker(srcConnID, s.connIDManager.Get, s.initialStream, s.handshakeStream, s.sentPacketHandler, s.retransmissionQueue, cs, s.framer, s.receivedPacketHandler, s.datagramQueue, s.perspective) + s.packer = newPacketPacker(srcConnID, s.connIDManager.Get, s.initialStream, s.handshakeStream, s.sentPacketHandler, s.retransmissionQueue, cs, s.framer, &s.receivedPacketHandler, s.datagramQueue, s.perspective) s.unpacker = newPacketUnpacker(cs, s.srcConnIDLen) s.cryptoStreamManager = newCryptoStreamManager(s.initialStream, s.handshakeStream, s.oneRTTStream) return &wrappedConn{Conn: s} @@ -380,7 +388,7 @@ var newClientConnection = func( initialPacketNumber protocol.PacketNumber, enable0RTT bool, hasNegotiatedVersion bool, - tracer *logging.ConnectionTracer, + qlogTrace qlogwriter.Trace, logger utils.Logger, v protocol.Version, ) *wrappedConn { @@ -393,10 +401,23 @@ var newClientConnection = func( perspective: protocol.PerspectiveClient, logID: destConnID.String(), logger: logger, - tracer: tracer, + qlogTrace: qlogTrace, versionNegotiated: hasNegotiatedVersion, version: v, } + if qlogTrace != nil { + s.qlogger = qlogTrace.AddProducer() + } + if s.qlogger != nil { + var srcAddr, destAddr *net.UDPAddr + if addr, ok := conn.LocalAddr().(*net.UDPAddr); ok { + srcAddr = addr + } + if addr, ok := conn.RemoteAddr().(*net.UDPAddr); ok { + destAddr = addr + } + s.qlogger.RecordEvent(startedConnectionEvent(srcAddr, destAddr)) + } s.connIDManager = newConnIDManager( destConnID, func(token protocol.StatelessResetToken) { runner.AddResetToken(token, s) }, @@ -418,15 +439,16 @@ var newClientConnection = func( ) s.ctx, s.ctxCancel = context.WithCancelCause(ctx) s.preSetup() - s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler( + s.sentPacketHandler = ackhandler.NewSentPacketHandler( initialPacketNumber, protocol.ByteCount(s.config.InitialPacketSize), s.rttStats, &s.connStats, false, // has no effect s.conn.capabilities().ECN, + s.receivedPacketHandler.IgnorePacketsBelow, s.perspective, - s.tracer, + s.qlogger, s.logger, ) s.currentMTUEstimate.Store(uint32(estimateMaxPayloadSize(protocol.ByteCount(s.config.InitialPacketSize)))) @@ -456,8 +478,8 @@ var newClientConnection = func( } else { params.MaxDatagramFrameSize = protocol.InvalidByteCount } - if s.tracer != nil && s.tracer.SentTransportParameters != nil { - s.tracer.SentTransportParameters(params) + if s.qlogger != nil { + s.qlogTransportParameters(params, protocol.PerspectiveClient, false) } cs := handshake.NewCryptoSetupClient( destConnID, @@ -465,14 +487,14 @@ var newClientConnection = func( tlsConf, enable0RTT, s.rttStats, - tracer, + s.qlogger, logger, s.version, ) s.cryptoStreamHandler = cs s.cryptoStreamManager = newCryptoStreamManager(s.initialStream, s.handshakeStream, oneRTTStream) s.unpacker = newPacketUnpacker(cs, s.srcConnIDLen) - s.packer = newPacketPacker(srcConnID, s.connIDManager.Get, s.initialStream, s.handshakeStream, s.sentPacketHandler, s.retransmissionQueue, cs, s.framer, s.receivedPacketHandler, s.datagramQueue, s.perspective) + s.packer = newPacketPacker(srcConnID, s.connIDManager.Get, s.initialStream, s.handshakeStream, s.sentPacketHandler, s.retransmissionQueue, cs, s.framer, &s.receivedPacketHandler, s.datagramQueue, s.perspective) if len(tlsConf.ServerName) > 0 { s.tokenStoreKey = tlsConf.ServerName } else { @@ -498,7 +520,7 @@ func (c *Conn) preSetup() { c.config.EnableStreamResetPartialDelivery, false, // ACK_FREQUENCY is not supported yet ) - c.rttStats = &utils.RTTStats{} + c.rttStats = utils.NewRTTStats() c.connFlowController = flowcontrol.NewConnectionFlowController( protocol.ByteCount(c.config.InitialConnectionReceiveWindow), protocol.ByteCount(c.config.MaxConnectionReceiveWindow), @@ -532,6 +554,8 @@ func (c *Conn) preSetup() { c.lastPacketReceivedTime = now c.creationTime = now + c.receivedPacketHandler = *ackhandler.NewReceivedPacketHandler(c.logger) + c.datagramQueue = newDatagramQueue(c.scheduleSending, c.logger) c.connState.Version = c.version } @@ -597,7 +621,7 @@ runLoop: queue := c.undecryptablePacketsToProcess c.undecryptablePacketsToProcess = nil for _, p := range queue { - processed, err := c.handleOnePacket(p) + processed, err := c.handleOnePacket(p.receivedPacket, p.datagramID) if err != nil { c.setCloseError(&closeError{err: err}) break runLoop @@ -652,7 +676,7 @@ runLoop: // Check for loss detection timeout. // This could cause packets to be declared lost, and retransmissions to be enqueued. now := monotime.Now() - if timeout := c.sentPacketHandler.GetLossDetectionTimeout(); !timeout.IsZero() && timeout.Before(now) { + if timeout := c.sentPacketHandler.GetLossDetectionTimeout(); !timeout.IsZero() && !timeout.After(now) { if err := c.sentPacketHandler.OnLossDetectionTimeout(now); err != nil { c.setCloseError(&closeError{err: err}) break runLoop @@ -721,9 +745,9 @@ runLoop: c.cryptoStreamHandler.Close() c.sendQueue.Close() // close the send queue before sending the CONNECTION_CLOSE c.handleCloseError(closeErr) - if c.tracer != nil && c.tracer.Close != nil { + if c.qlogger != nil { if e := (&errCloseForRecreating{}); !errors.As(closeErr.err, &e) { - c.tracer.Close() + c.qlogger.Close() } } c.logger.Infof("Connection %s closed.", c.logID) @@ -750,10 +774,16 @@ func (c *Conn) supportsDatagrams() bool { func (c *Conn) ConnectionState() ConnectionState { c.connStateMutex.Lock() defer c.connStateMutex.Unlock() + cs := c.cryptoStreamHandler.ConnectionState() c.connState.TLS = cs.ConnectionState c.connState.Used0RTT = cs.Used0RTT - c.connState.SupportsStreamResetPartialDelivery = c.peerParams.EnableResetStreamAt + if c.peerParams != nil { + c.connState.SupportsDatagrams.Remote = c.supportsDatagrams() + c.connState.SupportsStreamResetPartialDelivery.Remote = c.peerParams.EnableResetStreamAt + } + c.connState.SupportsDatagrams.Local = c.config.EnableDatagrams + c.connState.SupportsStreamResetPartialDelivery.Local = c.config.EnableStreamResetPartialDelivery c.connState.GSO = c.conn.capabilities().GSO return c.connState } @@ -867,9 +897,6 @@ func (c *Conn) maybeResetTimer() { return } - if t := c.connIDGenerator.NextRetireTime(); !t.IsZero() && t.Before(deadline) { - deadline = t - } if !c.pacingDeadline.IsZero() && c.pacingDeadline.Before(deadline) { deadline = c.pacingDeadline } @@ -911,8 +938,10 @@ func (c *Conn) handleHandshakeComplete(now monotime.Time) error { c.connIDManager.SetHandshakeComplete() c.connIDGenerator.SetHandshakeComplete(now.Add(3 * c.rttStats.PTO(false))) - if c.tracer != nil && c.tracer.ChoseALPN != nil { - c.tracer.ChoseALPN(c.cryptoStreamHandler.ConnectionState().NegotiatedProtocol) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.ALPNInformation{ + ChosenALPN: c.cryptoStreamHandler.ConnectionState().NegotiatedProtocol, + }) } // The server applies transport parameters right away, but the client side has to wait for handshake completion. @@ -969,41 +998,48 @@ func (c *Conn) handleHandshakeConfirmed(now monotime.Time) error { return nil } +const maxPacketsToProcess = 32 + func (c *Conn) handlePackets() (wasProcessed bool, _ error) { - // Now process all packets in the receivedPackets channel. - // Limit the number of packets to the length of the receivedPackets channel, + // Process packets from the receivedPackets queue. + // Limit the number of packets to process to maxPacketsToProcess, // so we eventually get a chance to send out an ACK when receiving a lot of packets. c.receivedPacketMx.Lock() - numPackets := c.receivedPackets.Len() - if numPackets == 0 { + + if c.receivedPackets.Empty() { c.receivedPacketMx.Unlock() return false, nil } var hasMorePackets bool - for i := 0; i < numPackets; i++ { - if i > 0 { - c.receivedPacketMx.Lock() - } + for range maxPacketsToProcess { p := c.receivedPackets.PopFront() - hasMorePackets = !c.receivedPackets.Empty() c.receivedPacketMx.Unlock() - processed, err := c.handleOnePacket(p) + var datagramID qlog.DatagramID + if c.qlogger != nil && wire.IsLongHeaderPacket(p.data[0]) { + datagramID = qlog.CalculateDatagramID(p.data) + } + processed, err := c.handleOnePacket(p, datagramID) if err != nil { return false, err } if processed { wasProcessed = true } + c.receivedPacketMx.Lock() + hasMorePackets = !c.receivedPackets.Empty() if !hasMorePackets { break } - // only process a single packet at a time before handshake completion - if !c.handshakeComplete { + // Prioritize sending of new CRYPTO data. + // This is especially relevant when processing 0-RTT packets. + if !c.handshakeComplete && (c.initialStream.HasData() || c.handshakeStream.HasData()) { break } } + c.receivedPacketMx.Unlock() + if hasMorePackets { select { case c.notifyReceivedPacket <- struct{}{}: @@ -1013,12 +1049,11 @@ func (c *Conn) handlePackets() (wasProcessed bool, _ error) { return wasProcessed, nil } -func (c *Conn) handleOnePacket(rp receivedPacket) (wasProcessed bool, _ error) { +func (c *Conn) handleOnePacket(rp receivedPacket, datagramID qlog.DatagramID) (wasProcessed bool, _ error) { c.sentPacketHandler.ReceivedBytes(rp.Size(), rp.rcvTime) if wire.IsVersionNegotiationPacket(rp.data) { - c.handleVersionNegotiationPacket(rp) - return false, nil + return false, c.handleVersionNegotiationPacket(rp) } var counter uint8 @@ -1032,15 +1067,24 @@ func (c *Conn) handleOnePacket(rp receivedPacket) (wasProcessed bool, _ error) { destConnID, err := wire.ParseConnectionID(p.data, c.srcConnIDLen) if err != nil { - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropHeaderParseError) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Raw: qlog.RawInfo{Length: len(data)}, + DatagramID: datagramID, + Trigger: qlog.PacketDropHeaderParseError, + }) } c.logger.Debugf("error parsing packet, couldn't parse connection ID: %s", err) break } if destConnID != lastConnID { - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropUnknownConnectionID) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{DestConnectionID: destConnID}, + Raw: qlog.RawInfo{Length: len(data)}, + DatagramID: datagramID, + Trigger: qlog.PacketDropUnknownConnectionID, + }) } c.logger.Debugf("coalesced packet has different destination connection ID: %s, expected %s", destConnID, lastConnID) break @@ -1050,12 +1094,21 @@ func (c *Conn) handleOnePacket(rp receivedPacket) (wasProcessed bool, _ error) { if wire.IsLongHeaderPacket(p.data[0]) { hdr, packetData, rest, err := wire.ParsePacket(p.data) if err != nil { - if c.tracer != nil && c.tracer.DroppedPacket != nil { - dropReason := logging.PacketDropHeaderParseError + if c.qlogger != nil { if err == wire.ErrUnsupportedVersion { - dropReason = logging.PacketDropUnsupportedVersion + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{Version: hdr.Version}, + Raw: qlog.RawInfo{Length: len(data)}, + DatagramID: datagramID, + Trigger: qlog.PacketDropUnsupportedVersion, + }) + } else { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Raw: qlog.RawInfo{Length: len(data)}, + DatagramID: datagramID, + Trigger: qlog.PacketDropHeaderParseError, + }) } - c.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), dropReason) } c.logger.Debugf("error parsing packet: %s", err) break @@ -1063,8 +1116,12 @@ func (c *Conn) handleOnePacket(rp receivedPacket) (wasProcessed bool, _ error) { lastConnID = hdr.DestConnectionID if hdr.Version != c.version { - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(logging.PacketTypeFromHeader(hdr), protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedVersion) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Raw: qlog.RawInfo{Length: len(data)}, + DatagramID: datagramID, + Trigger: qlog.PacketDropUnexpectedVersion, + }) } c.logger.Debugf("Dropping packet with version %x. Expected %x.", hdr.Version, c.version) break @@ -1082,7 +1139,7 @@ func (c *Conn) handleOnePacket(rp receivedPacket) (wasProcessed bool, _ error) { p.data = packetData - processed, err := c.handleLongHeaderPacket(p, hdr) + processed, err := c.handleLongHeaderPacket(p, hdr, datagramID) if err != nil { return false, err } @@ -1094,7 +1151,7 @@ func (c *Conn) handleOnePacket(rp receivedPacket) (wasProcessed bool, _ error) { if counter > 0 { p.buffer.Split() } - processed, err := c.handleShortHeaderPacket(p, counter > 0) + processed, err := c.handleShortHeaderPacket(p, counter > 0, datagramID) if err != nil { return false, err } @@ -1110,7 +1167,11 @@ func (c *Conn) handleOnePacket(rp receivedPacket) (wasProcessed bool, _ error) { return wasProcessed, nil } -func (c *Conn) handleShortHeaderPacket(p receivedPacket, isCoalesced bool) (wasProcessed bool, _ error) { +func (c *Conn) handleShortHeaderPacket( + p receivedPacket, + isCoalesced bool, + datagramID qlog.DatagramID, // only for logging +) (wasProcessed bool, _ error) { var wasQueued bool defer func() { @@ -1122,7 +1183,15 @@ func (c *Conn) handleShortHeaderPacket(p receivedPacket, isCoalesced bool) (wasP destConnID, err := wire.ParseConnectionID(p.data, c.srcConnIDLen) if err != nil { - c.tracer.DroppedPacket(logging.PacketType1RTT, protocol.InvalidPacketNumber, protocol.ByteCount(len(p.data)), logging.PacketDropHeaderParseError) + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketType1RTT, + PacketNumber: protocol.InvalidPacketNumber, + }, + Raw: qlog.RawInfo{Length: len(p.data)}, + DatagramID: datagramID, + Trigger: qlog.PacketDropHeaderParseError, + }) return false, nil } pn, pnLen, keyPhase, data, err := c.unpacker.UnpackShortHeader(p.rcvTime, p.data) @@ -1138,7 +1207,7 @@ func (c *Conn) handleShortHeaderPacket(p receivedPacket, isCoalesced bool) (wasP return false, &StatelessResetError{} } } - wasQueued, err = c.handleUnpackError(err, p, logging.PacketType1RTT) + wasQueued, err = c.handleUnpackError(err, p, qlog.PacketType1RTT, datagramID) return false, err } c.largestRcvdAppData = max(c.largestRcvdAppData, pn) @@ -1150,26 +1219,38 @@ func (c *Conn) handleShortHeaderPacket(p receivedPacket, isCoalesced bool) (wasP if c.receivedPacketHandler.IsPotentiallyDuplicate(pn, protocol.Encryption1RTT) { c.logger.Debugf("Dropping (potentially) duplicate packet.") - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(logging.PacketType1RTT, pn, p.Size(), logging.PacketDropDuplicate) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketType1RTT, + PacketNumber: pn, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + DatagramID: datagramID, + Trigger: qlog.PacketDropDuplicate, + }) } return false, nil } - var log func([]logging.Frame) - if c.tracer != nil && c.tracer.ReceivedShortHeaderPacket != nil { - log = func(frames []logging.Frame) { - c.tracer.ReceivedShortHeaderPacket( - &logging.ShortHeader{ + var log func([]qlog.Frame) + if c.qlogger != nil { + log = func(frames []qlog.Frame) { + c.qlogger.RecordEvent(qlog.PacketReceived{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketType1RTT, DestConnectionID: destConnID, PacketNumber: pn, - PacketNumberLen: pnLen, - KeyPhase: keyPhase, + KeyPhaseBit: keyPhase, }, - p.Size(), - p.ecn, - frames, - ) + Raw: qlog.RawInfo{ + Length: int(p.Size()), + PayloadLength: int(p.Size() - wire.ShortHeaderLen(destConnID, pnLen)), + }, + DatagramID: datagramID, + Frames: frames, + ECN: toQlogECN(p.ecn), + }) } } isNonProbing, pathChallenge, err := c.handleUnpackedShortHeaderPacket(destConnID, pn, data, p.ecn, p.rcvTime, log) @@ -1200,7 +1281,7 @@ func (c *Conn) handleShortHeaderPacket(p receivedPacket, isCoalesced bool) (wasP return true, err } c.logger.Debugf("sending path probe packet to %s", p.remoteAddr) - c.logShortHeaderPacket(probe.DestConnID, probe.Ack, probe.Frames, probe.StreamFrames, probe.PacketNumber, probe.PacketNumberLen, probe.KeyPhase, protocol.ECNNon, buf.Len(), false) + c.logShortHeaderPacketWithDatagramID(probe, protocol.ECNNon, buf.Len(), false, datagramID) c.registerPackedShortHeaderPacket(probe, protocol.ECNNon, p.rcvTime) c.sendQueue.SendProbe(buf, p.remoteAddr) } @@ -1224,7 +1305,7 @@ func (c *Conn) handleShortHeaderPacket(p receivedPacket, isCoalesced bool) (wasP return true, nil } -func (c *Conn) handleLongHeaderPacket(p receivedPacket, hdr *wire.Header) (wasProcessed bool, _ error) { +func (c *Conn) handleLongHeaderPacket(p receivedPacket, hdr *wire.Header, datagramID qlog.DatagramID) (wasProcessed bool, _ error) { var wasQueued bool defer func() { @@ -1241,23 +1322,39 @@ func (c *Conn) handleLongHeaderPacket(p receivedPacket, hdr *wire.Header) (wasPr // The server can change the source connection ID with the first Handshake packet. // After this, all packets with a different source connection have to be ignored. if c.receivedFirstPacket && hdr.Type == protocol.PacketTypeInitial && hdr.SrcConnectionID != c.handshakeDestConnID { - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(logging.PacketTypeInitial, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnknownConnectionID) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketTypeInitial, + PacketNumber: protocol.InvalidPacketNumber, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + DatagramID: datagramID, + Trigger: qlog.PacketDropUnknownConnectionID, + }) } c.logger.Debugf("Dropping Initial packet (%d bytes) with unexpected source connection ID: %s (expected %s)", p.Size(), hdr.SrcConnectionID, c.handshakeDestConnID) return false, nil } // drop 0-RTT packets, if we are a client if c.perspective == protocol.PerspectiveClient && hdr.Type == protocol.PacketType0RTT { - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(logging.PacketType0RTT, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnexpectedPacket) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketType0RTT, + PacketNumber: protocol.InvalidPacketNumber, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + DatagramID: datagramID, + Trigger: qlog.PacketDropUnexpectedPacket, + }) } return false, nil } packet, err := c.unpacker.UnpackLongHeader(hdr, p.data) if err != nil { - wasQueued, err = c.handleUnpackError(err, p, logging.PacketTypeFromHeader(hdr)) + wasQueued, err = c.handleUnpackError(err, p, toQlogPacketType(hdr.Type), datagramID) return false, err } @@ -1268,30 +1365,51 @@ func (c *Conn) handleLongHeaderPacket(p receivedPacket, hdr *wire.Header) (wasPr if pn := packet.hdr.PacketNumber; c.receivedPacketHandler.IsPotentiallyDuplicate(pn, packet.encryptionLevel) { c.logger.Debugf("Dropping (potentially) duplicate packet.") - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(logging.PacketTypeFromHeader(hdr), pn, p.Size(), logging.PacketDropDuplicate) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: toQlogPacketType(packet.hdr.Type), + DestConnectionID: hdr.DestConnectionID, + SrcConnectionID: hdr.SrcConnectionID, + PacketNumber: pn, + Version: packet.hdr.Version, + }, + Raw: qlog.RawInfo{Length: int(p.Size()), PayloadLength: int(packet.hdr.Length)}, + DatagramID: datagramID, + Trigger: qlog.PacketDropDuplicate, + }) } return false, nil } - if err := c.handleUnpackedLongHeaderPacket(packet, p.ecn, p.rcvTime, p.Size()); err != nil { + if err := c.handleUnpackedLongHeaderPacket(packet, p.ecn, p.rcvTime, datagramID, p.Size()); err != nil { return false, err } return true, nil } -func (c *Conn) handleUnpackError(err error, p receivedPacket, pt logging.PacketType) (wasQueued bool, _ error) { +func (c *Conn) handleUnpackError(err error, p receivedPacket, pt qlog.PacketType, datagramID qlog.DatagramID) (wasQueued bool, _ error) { switch err { case handshake.ErrKeysDropped: - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(pt, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropKeyUnavailable) + if c.qlogger != nil { + connID, _ := wire.ParseConnectionID(p.data, c.srcConnIDLen) + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: pt, + DestConnectionID: connID, + PacketNumber: protocol.InvalidPacketNumber, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + DatagramID: datagramID, + Trigger: qlog.PacketDropKeyUnavailable, + }) } c.logger.Debugf("Dropping %s packet (%d bytes) because we already dropped the keys.", pt, p.Size()) return false, nil case handshake.ErrKeysNotYetAvailable: // Sealer for this encryption level not yet available. // Try again later. - c.tryQueueingUndecryptablePacket(p, pt) + c.tryQueueingUndecryptablePacket(p, pt, datagramID) return true, nil case wire.ErrInvalidReservedBits: return false, &qerr.TransportError{ @@ -1300,8 +1418,18 @@ func (c *Conn) handleUnpackError(err error, p receivedPacket, pt logging.PacketT } case handshake.ErrDecryptionFailed: // This might be a packet injected by an attacker. Drop it. - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(pt, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropPayloadDecryptError) + if c.qlogger != nil { + connID, _ := wire.ParseConnectionID(p.data, c.srcConnIDLen) + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: pt, + DestConnectionID: connID, + PacketNumber: protocol.InvalidPacketNumber, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + DatagramID: datagramID, + Trigger: qlog.PacketDropPayloadDecryptError, + }) } c.logger.Debugf("Dropping %s packet (%d bytes) that could not be unpacked. Error: %s", pt, p.Size(), err) return false, nil @@ -1309,8 +1437,18 @@ func (c *Conn) handleUnpackError(err error, p receivedPacket, pt logging.PacketT var headerErr *headerParseError if errors.As(err, &headerErr) { // This might be a packet injected by an attacker. Drop it. - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(pt, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropHeaderParseError) + if c.qlogger != nil { + connID, _ := wire.ParseConnectionID(p.data, c.srcConnIDLen) + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: pt, + DestConnectionID: connID, + PacketNumber: protocol.InvalidPacketNumber, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + DatagramID: datagramID, + Trigger: qlog.PacketDropHeaderParseError, + }) } c.logger.Debugf("Dropping %s packet (%d bytes) for which we couldn't unpack the header. Error: %s", pt, p.Size(), err) return false, nil @@ -1323,23 +1461,50 @@ func (c *Conn) handleUnpackError(err error, p receivedPacket, pt logging.PacketT 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) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketTypeRetry, + SrcConnectionID: hdr.SrcConnectionID, + DestConnectionID: hdr.DestConnectionID, + Version: hdr.Version, + }, + Raw: qlog.RawInfo{Length: len(data)}, + Trigger: qlog.PacketDropUnexpectedPacket, + }) } c.logger.Debugf("Ignoring Retry.") return false } if c.receivedFirstPacket { - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketTypeRetry, + SrcConnectionID: hdr.SrcConnectionID, + DestConnectionID: hdr.DestConnectionID, + Version: hdr.Version, + }, + Raw: qlog.RawInfo{Length: len(data)}, + Trigger: qlog.PacketDropUnexpectedPacket, + }) } c.logger.Debugf("Ignoring Retry, since we already received a packet.") return false } destConnID := c.connIDManager.Get() if hdr.SrcConnectionID == destConnID { - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketTypeRetry, + SrcConnectionID: hdr.SrcConnectionID, + DestConnectionID: hdr.DestConnectionID, + Version: hdr.Version, + }, + Raw: qlog.RawInfo{Length: len(data)}, + Trigger: qlog.PacketDropUnexpectedPacket, + }) } c.logger.Debugf("Ignoring Retry, since the server didn't change the Source Connection ID.") return false @@ -1353,8 +1518,17 @@ func (c *Conn) handleRetryPacket(hdr *wire.Header, data []byte, rcvTime monotime tag := handshake.GetRetryIntegrityTag(data[:len(data)-16], destConnID, hdr.Version) if !bytes.Equal(data[len(data)-16:], tag[:]) { - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropPayloadDecryptError) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketTypeRetry, + SrcConnectionID: hdr.SrcConnectionID, + DestConnectionID: hdr.DestConnectionID, + Version: hdr.Version, + }, + Raw: qlog.RawInfo{Length: len(data)}, + Trigger: qlog.PacketDropPayloadDecryptError, + }) } c.logger.Debugf("Ignoring spoofed Retry. Integrity Tag doesn't match.") return false @@ -1374,44 +1548,71 @@ func (c *Conn) handleRetryPacket(hdr *wire.Header, data []byte, rcvTime monotime (&wire.ExtendedHeader{Header: *hdr}).Log(c.logger) c.logger.Debugf("Switching destination connection ID to: %s", hdr.SrcConnectionID) } - if c.tracer != nil && c.tracer.ReceivedRetry != nil { - c.tracer.ReceivedRetry(hdr) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketReceived{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketTypeRetry, + DestConnectionID: destConnID, + SrcConnectionID: newDestConnID, + Version: hdr.Version, + Token: &qlog.Token{Raw: hdr.Token}, + }, + Raw: qlog.RawInfo{Length: len(data)}, + }) } c.scheduleSending() return true } -func (c *Conn) handleVersionNegotiationPacket(p receivedPacket) { +func (c *Conn) handleVersionNegotiationPacket(p receivedPacket) error { if c.perspective == protocol.PerspectiveServer || // servers never receive version negotiation packets c.receivedFirstPacket || c.versionNegotiated { // ignore delayed / duplicated version negotiation packets - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(logging.PacketTypeVersionNegotiation, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnexpectedPacket) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{PacketType: qlog.PacketTypeVersionNegotiation}, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropUnexpectedPacket, + }) } - return + return nil } src, dest, supportedVersions, err := wire.ParseVersionNegotiationPacket(p.data) if err != nil { - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(logging.PacketTypeVersionNegotiation, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropHeaderParseError) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{PacketType: qlog.PacketTypeVersionNegotiation}, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropHeaderParseError, + }) } c.logger.Debugf("Error parsing Version Negotiation packet: %s", err) - return + return nil } if slices.Contains(supportedVersions, c.version) { - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(logging.PacketTypeVersionNegotiation, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnexpectedVersion) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{PacketType: qlog.PacketTypeVersionNegotiation}, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropUnexpectedVersion, + }) } // The Version Negotiation packet contains the version that we offered. // This might be a packet sent by an attacker, or it was corrupted. - return + return nil } c.logger.Infof("Received a Version Negotiation packet. Supported Versions: %s", supportedVersions) - if c.tracer != nil && c.tracer.ReceivedVersionNegotiationPacket != nil { - c.tracer.ReceivedVersionNegotiationPacket(dest, src, supportedVersions) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.VersionNegotiationReceived{ + Header: qlog.PacketHeaderVersionNegotiation{ + DestConnectionID: dest, + SrcConnectionID: src, + }, + SupportedVersions: supportedVersions, + }) } newVersion, ok := protocol.ChooseSupportedVersion(c.config.Versions, supportedVersions) if !ok { @@ -1420,37 +1621,46 @@ func (c *Conn) handleVersionNegotiationPacket(p receivedPacket) { Theirs: supportedVersions, }) c.logger.Infof("No compatible QUIC version found.") - return + return nil } - if c.tracer != nil && c.tracer.NegotiatedVersion != nil { - c.tracer.NegotiatedVersion(newVersion, c.config.Versions, supportedVersions) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.VersionInformation{ + ChosenVersion: newVersion, + ClientVersions: c.config.Versions, + ServerVersions: supportedVersions, + }) } c.logger.Infof("Switching to QUIC version %s.", newVersion) nextPN, _ := c.sentPacketHandler.PeekPacketNumber(protocol.EncryptionInitial) - c.destroyImpl(&errCloseForRecreating{ + return &errCloseForRecreating{ nextPacketNumber: nextPN, nextVersion: newVersion, - }) + } } func (c *Conn) handleUnpackedLongHeaderPacket( packet *unpackedPacket, ecn protocol.ECN, rcvTime monotime.Time, + datagramID qlog.DatagramID, // only for logging packetSize protocol.ByteCount, // only for logging ) error { if !c.receivedFirstPacket { c.receivedFirstPacket = true - if !c.versionNegotiated && c.tracer != nil && c.tracer.NegotiatedVersion != nil { - var clientVersions, serverVersions []protocol.Version + if !c.versionNegotiated && c.qlogger != nil { + var clientVersions, serverVersions []Version switch c.perspective { case protocol.PerspectiveClient: clientVersions = c.config.Versions case protocol.PerspectiveServer: serverVersions = c.config.Versions } - c.tracer.NegotiatedVersion(c.version, clientVersions, serverVersions) + c.qlogger.RecordEvent(qlog.VersionInformation{ + ChosenVersion: c.version, + ClientVersions: clientVersions, + ServerVersions: serverVersions, + }) } // The server can change the source connection ID with the first Handshake packet. if c.perspective == protocol.PerspectiveClient && packet.hdr.SrcConnectionID != c.handshakeDestConnID { @@ -1469,13 +1679,15 @@ func (c *Conn) handleUnpackedLongHeaderPacket( c.handshakeDestConnID = packet.hdr.SrcConnectionID c.connIDManager.ChangeInitialConnID(packet.hdr.SrcConnectionID) } - if c.tracer != nil && c.tracer.StartedConnection != nil { - c.tracer.StartedConnection( - c.conn.LocalAddr(), - c.conn.RemoteAddr(), - packet.hdr.SrcConnectionID, - packet.hdr.DestConnectionID, - ) + if c.qlogger != nil { + var srcAddr, destAddr *net.UDPAddr + if addr, ok := c.conn.LocalAddr().(*net.UDPAddr); ok { + srcAddr = addr + } + if addr, ok := c.conn.RemoteAddr().(*net.UDPAddr); ok { + destAddr = addr + } + c.qlogger.RecordEvent(startedConnectionEvent(srcAddr, destAddr)) } } } @@ -1497,16 +1709,37 @@ func (c *Conn) handleUnpackedLongHeaderPacket( c.largestRcvdAppData = max(c.largestRcvdAppData, packet.hdr.PacketNumber) } - var log func([]logging.Frame) - if c.tracer != nil && c.tracer.ReceivedLongHeaderPacket != nil { - log = func(frames []logging.Frame) { - c.tracer.ReceivedLongHeaderPacket(packet.hdr, packetSize, ecn, frames) + var log func([]qlog.Frame) + if c.qlogger != nil { + log = func(frames []qlog.Frame) { + var token *qlog.Token + if len(packet.hdr.Token) > 0 { + token = &qlog.Token{Raw: packet.hdr.Token} + } + c.qlogger.RecordEvent(qlog.PacketReceived{ + Header: qlog.PacketHeader{ + PacketType: toQlogPacketType(packet.hdr.Type), + DestConnectionID: packet.hdr.DestConnectionID, + SrcConnectionID: packet.hdr.SrcConnectionID, + PacketNumber: packet.hdr.PacketNumber, + Version: packet.hdr.Version, + Token: token, + }, + Raw: qlog.RawInfo{ + Length: int(packetSize), + PayloadLength: int(packet.hdr.Length), + }, + DatagramID: datagramID, + Frames: frames, + ECN: toQlogECN(ecn), + }) } } isAckEliciting, _, _, err := c.handleFrames(packet.data, packet.hdr.DestConnectionID, packet.encryptionLevel, log, rcvTime) if err != nil { return err } + c.sentPacketHandler.ReceivedPacket(packet.encryptionLevel, rcvTime) return c.receivedPacketHandler.ReceivedPacket(packet.hdr.PacketNumber, ecn, packet.encryptionLevel, rcvTime, isAckEliciting) } @@ -1516,7 +1749,7 @@ func (c *Conn) handleUnpackedShortHeaderPacket( data []byte, ecn protocol.ECN, rcvTime monotime.Time, - log func([]logging.Frame), + log func([]qlog.Frame), ) (isNonProbing bool, pathChallenge *wire.PathChallengeFrame, _ error) { c.lastPacketReceivedTime = rcvTime c.firstAckElicitingPacketAfterIdleSentTime = 0 @@ -1526,6 +1759,7 @@ func (c *Conn) handleUnpackedShortHeaderPacket( if err != nil { return false, nil, err } + c.sentPacketHandler.ReceivedPacket(protocol.Encryption1RTT, rcvTime) if err := c.receivedPacketHandler.ReceivedPacket(pn, ecn, protocol.Encryption1RTT, rcvTime, isAckEliciting); err != nil { return false, nil, err } @@ -1538,14 +1772,14 @@ func (c *Conn) handleFrames( data []byte, destConnID protocol.ConnectionID, encLevel protocol.EncryptionLevel, - log func([]logging.Frame), + log func([]qlog.Frame), 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. - var frames []logging.Frame + var frames []qlog.Frame if log != nil { - frames = make([]logging.Frame, 0, 4) + frames = make([]qlog.Frame, 0, 4) } handshakeWasComplete := c.handshakeComplete var handleErr error @@ -1580,7 +1814,7 @@ func (c *Conn) handleFrames( data = data[l:] if log != nil { - frames = append(frames, toLoggingFrame(streamFrame)) + frames = append(frames, toQlogFrame(streamFrame)) } // an error occurred handling a previous frame, don't handle the current frame if skipHandling { @@ -1595,7 +1829,7 @@ func (c *Conn) handleFrames( } data = data[l:] if log != nil { - frames = append(frames, toLoggingFrame(ackFrame)) + frames = append(frames, toQlogFrame(ackFrame)) } // an error occurred handling a previous frame, don't handle the current frame if skipHandling { @@ -1611,7 +1845,7 @@ func (c *Conn) handleFrames( data = data[l:] if log != nil { - frames = append(frames, toLoggingFrame(datagramFrame)) + frames = append(frames, toQlogFrame(datagramFrame)) } // an error occurred handling a previous frame, don't handle the current frame if skipHandling { @@ -1627,7 +1861,7 @@ func (c *Conn) handleFrames( data = data[l:] if log != nil { - frames = append(frames, toLoggingFrame(frame)) + frames = append(frames, toQlogFrame(frame)) } // an error occurred handling a previous frame, don't handle the current frame if skipHandling { @@ -1721,8 +1955,16 @@ func (c *Conn) handlePacket(p receivedPacket) { // Discard packets once the amount of queued packets is larger than // the channel size, protocol.MaxConnUnprocessedPackets if c.receivedPackets.Len() >= protocol.MaxConnUnprocessedPackets { - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropDOSPrevention) + if c.qlogger != nil { + var datagramID qlog.DatagramID + if wire.IsLongHeaderPacket(p.data[0]) { + datagramID = qlog.CalculateDatagramID(p.data) + } + c.qlogger.RecordEvent(qlog.PacketDropped{ + Raw: qlog.RawInfo{Length: int(p.Size())}, + DatagramID: datagramID, + Trigger: qlog.PacketDropDOSPrevention, + }) } c.receivedPacketMx.Unlock() return @@ -1974,23 +2216,38 @@ func (c *Conn) handleCloseError(closeErr *closeError) { transportErr *TransportError ) var isRemoteClose bool + var trigger qlog.ConnectionCloseTrigger + var reason string + var transportErrorCode *qlog.TransportErrorCode + var applicationErrorCode *qlog.ApplicationErrorCode switch { case errors.Is(e, qerr.ErrIdleTimeout), - errors.Is(e, qerr.ErrHandshakeTimeout), - errors.As(e, &statelessResetErr), - errors.As(e, &versionNegotiationErr), - errors.As(e, &recreateErr): + errors.Is(e, qerr.ErrHandshakeTimeout): + trigger = qlog.ConnectionCloseTriggerIdleTimeout + case errors.As(e, &statelessResetErr): + trigger = qlog.ConnectionCloseTriggerStatelessReset + case errors.As(e, &versionNegotiationErr): + trigger = qlog.ConnectionCloseTriggerVersionMismatch + case errors.As(e, &recreateErr): case errors.As(e, &applicationErr): isRemoteClose = applicationErr.Remote + reason = applicationErr.ErrorMessage + applicationErrorCode = &applicationErr.ErrorCode case errors.As(e, &transportErr): isRemoteClose = transportErr.Remote + reason = transportErr.ErrorMessage + transportErrorCode = &transportErr.ErrorCode case closeErr.immediate: e = closeErr.err default: - e = &qerr.TransportError{ + te := &qerr.TransportError{ ErrorCode: qerr.InternalError, ErrorMessage: e.Error(), } + e = te + reason = te.ErrorMessage + code := te.ErrorCode + transportErrorCode = &code } c.streamsMap.CloseWithError(e) @@ -2004,8 +2261,18 @@ func (c *Conn) handleCloseError(closeErr *closeError) { // handler map when it is closed, so we need to make sure that this happens last. defer c.connIDManager.Close() - if c.tracer != nil && c.tracer.ClosedConnection != nil && !errors.As(e, &recreateErr) { - c.tracer.ClosedConnection(e) + if c.qlogger != nil && !errors.As(e, &recreateErr) { + initiator := qlog.InitiatorLocal + if isRemoteClose { + initiator = qlog.InitiatorRemote + } + c.qlogger.RecordEvent(qlog.ConnectionClosed{ + Initiator: initiator, + ConnectionError: transportErrorCode, + ApplicationError: applicationErrorCode, + Trigger: trigger, + Reason: reason, + }) } // If this is a remote close we're done here @@ -2031,9 +2298,6 @@ func (c *Conn) handleCloseError(closeErr *closeError) { } func (c *Conn) dropEncryptionLevel(encLevel protocol.EncryptionLevel, now monotime.Time) error { - if c.tracer != nil && c.tracer.DroppedEncryptionLevel != nil { - c.tracer.DroppedEncryptionLevel(encLevel) - } c.sentPacketHandler.DropPackets(encLevel, now) c.receivedPacketHandler.DropPackets(encLevel) //nolint:exhaustive // only Initial and 0-RTT need special treatment @@ -2054,22 +2318,41 @@ 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) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.ParametersSet{ + Restore: true, + Initiator: qlog.InitiatorRemote, + SentBy: c.perspective, + OriginalDestinationConnectionID: params.OriginalDestinationConnectionID, + InitialSourceConnectionID: params.InitialSourceConnectionID, + RetrySourceConnectionID: params.RetrySourceConnectionID, + StatelessResetToken: params.StatelessResetToken, + DisableActiveMigration: params.DisableActiveMigration, + MaxIdleTimeout: params.MaxIdleTimeout, + MaxUDPPayloadSize: params.MaxUDPPayloadSize, + AckDelayExponent: params.AckDelayExponent, + MaxAckDelay: params.MaxAckDelay, + ActiveConnectionIDLimit: params.ActiveConnectionIDLimit, + InitialMaxData: params.InitialMaxData, + InitialMaxStreamDataBidiLocal: params.InitialMaxStreamDataBidiLocal, + InitialMaxStreamDataBidiRemote: params.InitialMaxStreamDataBidiRemote, + InitialMaxStreamDataUni: params.InitialMaxStreamDataUni, + InitialMaxStreamsBidi: int64(params.MaxBidiStreamNum), + InitialMaxStreamsUni: int64(params.MaxUniStreamNum), + MaxDatagramFrameSize: params.MaxDatagramFrameSize, + EnableResetStreamAt: params.EnableResetStreamAt, + }) } c.peerParams = params c.connIDGenerator.SetMaxActiveConnIDs(params.ActiveConnectionIDLimit) c.connFlowController.UpdateSendWindow(params.InitialMaxData) c.streamsMap.HandleTransportParameters(params) - c.connStateMutex.Lock() - c.connState.SupportsDatagrams = c.supportsDatagrams() - c.connStateMutex.Unlock() } func (c *Conn) handleTransportParameters(params *wire.TransportParameters) error { - if c.tracer != nil && c.tracer.ReceivedTransportParameters != nil { - c.tracer.ReceivedTransportParameters(params) + if c.qlogger != nil { + c.qlogTransportParameters(params, c.perspective.Opposite(), false) } if err := c.checkTransportParameters(params); err != nil { return &qerr.TransportError{ @@ -2094,10 +2377,6 @@ func (c *Conn) handleTransportParameters(params *wire.TransportParameters) error // the client's transport parameters. close(c.earlyConnReadyChan) } - - c.connStateMutex.Lock() - c.connState.SupportsDatagrams = c.supportsDatagrams() - c.connStateMutex.Unlock() return nil } @@ -2161,7 +2440,7 @@ func (c *Conn) applyTransportParameters() { c.rttStats, protocol.ByteCount(c.config.InitialPacketSize), maxPacketSize, - c.tracer, + c.qlogger, ) } @@ -2215,7 +2494,7 @@ func (c *Conn) sendPackets(now monotime.Time) error { return err } c.logger.Debugf("sending path probe packet from %s", c.LocalAddr()) - c.logShortHeaderPacket(probe.DestConnID, probe.Ack, probe.Frames, probe.StreamFrames, probe.PacketNumber, probe.PacketNumberLen, probe.KeyPhase, protocol.ECNNon, buf.Len(), false) + c.logShortHeaderPacket(probe, protocol.ECNNon, buf.Len()) c.registerPackedShortHeaderPacket(probe, protocol.ECNNon, now) tr.WriteTo(buf.Data, c.conn.RemoteAddr()) // There's (likely) more data to send. Loop around again. @@ -2236,7 +2515,7 @@ func (c *Conn) sendPackets(now monotime.Time) error { return err } ecn := c.sentPacketHandler.ECNMode(true) - c.logShortHeaderPacket(p.DestConnID, p.Ack, p.Frames, p.StreamFrames, p.PacketNumber, p.PacketNumberLen, p.KeyPhase, ecn, buf.Len(), false) + c.logShortHeaderPacket(p, ecn, buf.Len()) c.registerPackedShortHeaderPacket(p, ecn, now) c.sendQueue.Send(buf, 0, ecn) // There's (likely) more data to send. Loop around again. @@ -2405,7 +2684,7 @@ func (c *Conn) maybeSendAckOnlyPacket(now monotime.Time) error { } return err } - c.logShortHeaderPacket(p.DestConnID, p.Ack, p.Frames, p.StreamFrames, p.PacketNumber, p.PacketNumberLen, p.KeyPhase, ecn, buf.Len(), false) + c.logShortHeaderPacket(p, ecn, buf.Len()) c.registerPackedShortHeaderPacket(p, ecn, now) c.sendQueue.Send(buf, 0, ecn) return nil @@ -2459,7 +2738,7 @@ func (c *Conn) appendOneShortHeaderPacket(buf *packetBuffer, maxSize protocol.By return 0, err } size := buf.Len() - startLen - c.logShortHeaderPacket(p.DestConnID, p.Ack, p.Frames, p.StreamFrames, p.PacketNumber, p.PacketNumberLen, p.KeyPhase, ecn, size, false) + c.logShortHeaderPacket(p, ecn, size) c.registerPackedShortHeaderPacket(p, ecn, now) return size, nil } @@ -2678,23 +2957,38 @@ func (c *Conn) scheduleSending() { } // tryQueueingUndecryptablePacket queues a packet for which we're missing the decryption keys. -// The logging.PacketType is only used for logging purposes. -func (c *Conn) tryQueueingUndecryptablePacket(p receivedPacket, pt logging.PacketType) { +// The qlogevents.PacketType is only used for logging purposes. +func (c *Conn) tryQueueingUndecryptablePacket(p receivedPacket, pt qlog.PacketType, datagramID qlog.DatagramID) { if c.handshakeComplete { panic("shouldn't queue undecryptable packets after handshake completion") } if len(c.undecryptablePackets)+1 > protocol.MaxUndecryptablePackets { - if c.tracer != nil && c.tracer.DroppedPacket != nil { - c.tracer.DroppedPacket(pt, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropDOSPrevention) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: pt, + PacketNumber: protocol.InvalidPacketNumber, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + DatagramID: datagramID, + Trigger: qlog.PacketDropDOSPrevention, + }) } c.logger.Infof("Dropping undecryptable packet (%d bytes). Undecryptable packet queue full.", p.Size()) return } c.logger.Infof("Queueing packet (%d bytes) for later decryption", p.Size()) - if c.tracer != nil && c.tracer.BufferedPacket != nil { - c.tracer.BufferedPacket(pt, p.Size()) + if c.qlogger != nil { + c.qlogger.RecordEvent(qlog.PacketBuffered{ + Header: qlog.PacketHeader{ + PacketType: pt, + PacketNumber: protocol.InvalidPacketNumber, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + DatagramID: datagramID, + }) } - c.undecryptablePackets = append(c.undecryptablePackets, p) + c.undecryptablePackets = append(c.undecryptablePackets, receivedPacketWithDatagramID{receivedPacket: p, datagramID: datagramID}) } func (c *Conn) queueControlFrame(f wire.Frame) { @@ -2819,6 +3113,19 @@ func (c *Conn) HandshakeComplete() <-chan struct{} { return c.handshakeCompleteChan } +// QlogTrace returns the qlog trace of the QUIC connection. +// It is nil if qlog is not enabled. +func (c *Conn) QlogTrace() qlogwriter.Trace { + return c.qlogTrace +} + +// NextConnection transitions a connection to be usable after a 0-RTT rejection. +// It waits for the handshake to complete and then enables the connection for normal use. +// This should be called when the server rejects 0-RTT and the application receives +// [Err0RTTRejected] errors. +// +// Note that 0-RTT rejection invalidates all data sent in 0-RTT packets. It is the +// application's responsibility to handle this (for example by resending the data). func (c *Conn) NextConnection(ctx context.Context) (*Conn, error) { // The handshake might fail after the server rejected 0-RTT. // This could happen if the Finished message is malformed or never received. diff --git a/vendor/github.com/quic-go/quic-go/connection_logging.go b/vendor/github.com/quic-go/quic-go/connection_logging.go index 5ea12d130c..c828c8be02 100644 --- a/vendor/github.com/quic-go/quic-go/connection_logging.go +++ b/vendor/github.com/quic-go/quic-go/connection_logging.go @@ -1,46 +1,53 @@ package quic import ( + "net" + "net/netip" "slices" - "github.com/quic-go/quic-go/internal/ackhandler" "github.com/quic-go/quic-go/internal/protocol" "github.com/quic-go/quic-go/internal/wire" - "github.com/quic-go/quic-go/logging" + "github.com/quic-go/quic-go/qlog" ) // ConvertFrame converts a wire.Frame into a logging.Frame. // This makes it possible for external packages to access the frames. // Furthermore, it removes the data slices from CRYPTO and STREAM frames. -func toLoggingFrame(frame wire.Frame) logging.Frame { +func toQlogFrame(frame wire.Frame) qlog.Frame { switch f := frame.(type) { case *wire.AckFrame: // We use a pool for ACK frames. // Implementations of the tracer interface may hold on to frames, so we need to make a copy here. - return toLoggingAckFrame(f) + return qlog.Frame{Frame: toQlogAckFrame(f)} case *wire.CryptoFrame: - return &logging.CryptoFrame{ - Offset: f.Offset, - Length: protocol.ByteCount(len(f.Data)), + return qlog.Frame{ + Frame: &qlog.CryptoFrame{ + Offset: int64(f.Offset), + Length: int64(len(f.Data)), + }, } case *wire.StreamFrame: - return &logging.StreamFrame{ - StreamID: f.StreamID, - Offset: f.Offset, - Length: f.DataLen(), - Fin: f.Fin, + return qlog.Frame{ + Frame: &qlog.StreamFrame{ + StreamID: f.StreamID, + Offset: int64(f.Offset), + Length: int64(f.DataLen()), + Fin: f.Fin, + }, } case *wire.DatagramFrame: - return &logging.DatagramFrame{ - Length: logging.ByteCount(len(f.Data)), + return qlog.Frame{ + Frame: &qlog.DatagramFrame{ + Length: int64(len(f.Data)), + }, } default: - return logging.Frame(frame) + return qlog.Frame{Frame: frame} } } -func toLoggingAckFrame(f *wire.AckFrame) *logging.AckFrame { - ack := &logging.AckFrame{ +func toQlogAckFrame(f *wire.AckFrame) *qlog.AckFrame { + ack := &qlog.AckFrame{ AckRanges: slices.Clone(f.AckRanges), DelayTime: f.DelayTime, ECNCE: f.ECNCE, @@ -50,7 +57,7 @@ func toLoggingAckFrame(f *wire.AckFrame) *logging.AckFrame { return ack } -func (c *Conn) logLongHeaderPacket(p *longHeaderPacket, ecn protocol.ECN) { +func (c *Conn) logLongHeaderPacket(p *longHeaderPacket, ecn protocol.ECN, datagramID qlog.DatagramID) { // quic-go logging if c.logger.Debug() { p.header.Log(c.logger) @@ -66,90 +73,113 @@ func (c *Conn) logLongHeaderPacket(p *longHeaderPacket, ecn protocol.ECN) { } // tracing - if c.tracer != nil && c.tracer.SentLongHeaderPacket != nil { - frames := make([]logging.Frame, 0, len(p.frames)) + if c.qlogger != nil { + numFrames := len(p.frames) + len(p.streamFrames) + if p.ack != nil { + numFrames++ + } + frames := make([]qlog.Frame, 0, numFrames) + if p.ack != nil { + frames = append(frames, toQlogFrame(p.ack)) + } for _, f := range p.frames { - frames = append(frames, toLoggingFrame(f.Frame)) + frames = append(frames, toQlogFrame(f.Frame)) } for _, f := range p.streamFrames { - frames = append(frames, toLoggingFrame(f.Frame)) - } - var ack *logging.AckFrame - if p.ack != nil { - ack = toLoggingAckFrame(p.ack) + frames = append(frames, toQlogFrame(f.Frame)) } - c.tracer.SentLongHeaderPacket(p.header, p.length, ecn, ack, frames) + c.qlogger.RecordEvent(qlog.PacketSent{ + Header: qlog.PacketHeader{ + PacketType: toQlogPacketType(p.header.Type), + KeyPhaseBit: p.header.KeyPhase, + PacketNumber: p.header.PacketNumber, + Version: p.header.Version, + SrcConnectionID: p.header.SrcConnectionID, + DestConnectionID: p.header.DestConnectionID, + }, + Raw: qlog.RawInfo{ + Length: int(p.length), + PayloadLength: int(p.header.Length), + }, + DatagramID: datagramID, + Frames: frames, + ECN: toQlogECN(ecn), + }) } } -func (c *Conn) logShortHeaderPacket( - destConnID protocol.ConnectionID, - ackFrame *wire.AckFrame, - frames []ackhandler.Frame, - streamFrames []ackhandler.StreamFrame, - pn protocol.PacketNumber, - pnLen protocol.PacketNumberLen, - kp protocol.KeyPhaseBit, - ecn protocol.ECN, - size protocol.ByteCount, - isCoalesced bool, -) { +func (c *Conn) logShortHeaderPacket(p shortHeaderPacket, ecn protocol.ECN, size protocol.ByteCount) { + c.logShortHeaderPacketWithDatagramID(p, ecn, size, false, 0) +} + +func (c *Conn) logShortHeaderPacketWithDatagramID(p shortHeaderPacket, ecn protocol.ECN, size protocol.ByteCount, isCoalesced bool, datagramID qlog.DatagramID) { if c.logger.Debug() && !isCoalesced { - c.logger.Debugf("-> Sending packet %d (%d bytes) for connection %s, 1-RTT (ECN: %s)", pn, size, c.logID, ecn) + c.logger.Debugf("-> Sending packet %d (%d bytes) for connection %s, 1-RTT (ECN: %s)", p.PacketNumber, size, c.logID, ecn) } // quic-go logging if c.logger.Debug() { - wire.LogShortHeader(c.logger, destConnID, pn, pnLen, kp) - if ackFrame != nil { - wire.LogFrame(c.logger, ackFrame, true) + wire.LogShortHeader(c.logger, p.DestConnID, p.PacketNumber, p.PacketNumberLen, p.KeyPhase) + if p.Ack != nil { + wire.LogFrame(c.logger, p.Ack, true) } - for _, f := range frames { + for _, f := range p.Frames { wire.LogFrame(c.logger, f.Frame, true) } - for _, f := range streamFrames { + for _, f := range p.StreamFrames { wire.LogFrame(c.logger, f.Frame, true) } } // tracing - if c.tracer != nil && c.tracer.SentShortHeaderPacket != nil { - fs := make([]logging.Frame, 0, len(frames)+len(streamFrames)) - for _, f := range frames { - fs = append(fs, toLoggingFrame(f.Frame)) - } - for _, f := range streamFrames { - fs = append(fs, toLoggingFrame(f.Frame)) - } - var ack *logging.AckFrame - if ackFrame != nil { - ack = toLoggingAckFrame(ackFrame) - } - c.tracer.SentShortHeaderPacket( - &logging.ShortHeader{DestConnectionID: destConnID, PacketNumber: pn, PacketNumberLen: pnLen, KeyPhase: kp}, - size, - ecn, - ack, - fs, - ) + if c.qlogger != nil { + numFrames := len(p.Frames) + len(p.StreamFrames) + if p.Ack != nil { + numFrames++ + } + fs := make([]qlog.Frame, 0, numFrames) + if p.Ack != nil { + fs = append(fs, toQlogFrame(p.Ack)) + } + for _, f := range p.Frames { + fs = append(fs, toQlogFrame(f.Frame)) + } + for _, f := range p.StreamFrames { + fs = append(fs, toQlogFrame(f.Frame)) + } + c.qlogger.RecordEvent(qlog.PacketSent{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketType1RTT, + KeyPhaseBit: p.KeyPhase, + PacketNumber: p.PacketNumber, + Version: c.version, + DestConnectionID: p.DestConnID, + }, + Raw: qlog.RawInfo{ + Length: int(size), + PayloadLength: int(size - wire.ShortHeaderLen(p.DestConnID, p.PacketNumberLen)), + }, + DatagramID: datagramID, + Frames: fs, + ECN: toQlogECN(ecn), + }) } } func (c *Conn) logCoalescedPacket(packet *coalescedPacket, ecn protocol.ECN) { + var datagramID qlog.DatagramID + if c.qlogger != nil { + datagramID = qlog.CalculateDatagramID(packet.buffer.Data) + } if c.logger.Debug() { // There's a short period between dropping both Initial and Handshake keys and completion of the handshake, // during which we might call PackCoalescedPacket but just pack a short header packet. if len(packet.longHdrPackets) == 0 && packet.shortHdrPacket != nil { - c.logShortHeaderPacket( - packet.shortHdrPacket.DestConnID, - packet.shortHdrPacket.Ack, - packet.shortHdrPacket.Frames, - packet.shortHdrPacket.StreamFrames, - packet.shortHdrPacket.PacketNumber, - packet.shortHdrPacket.PacketNumberLen, - packet.shortHdrPacket.KeyPhase, + c.logShortHeaderPacketWithDatagramID( + *packet.shortHdrPacket, ecn, packet.shortHdrPacket.Length, false, + datagramID, ) return } @@ -160,9 +190,126 @@ func (c *Conn) logCoalescedPacket(packet *coalescedPacket, ecn protocol.ECN) { } } for _, p := range packet.longHdrPackets { - c.logLongHeaderPacket(p, ecn) + c.logLongHeaderPacket(p, ecn, datagramID) } if p := packet.shortHdrPacket; p != nil { - c.logShortHeaderPacket(p.DestConnID, p.Ack, p.Frames, p.StreamFrames, p.PacketNumber, p.PacketNumberLen, p.KeyPhase, ecn, p.Length, true) + c.logShortHeaderPacketWithDatagramID(*p, ecn, p.Length, true, datagramID) + } +} + +func (c *Conn) qlogTransportParameters(tp *wire.TransportParameters, sentBy protocol.Perspective, restore bool) { + ev := qlog.ParametersSet{ + Restore: restore, + OriginalDestinationConnectionID: tp.OriginalDestinationConnectionID, + InitialSourceConnectionID: tp.InitialSourceConnectionID, + RetrySourceConnectionID: tp.RetrySourceConnectionID, + StatelessResetToken: tp.StatelessResetToken, + DisableActiveMigration: tp.DisableActiveMigration, + MaxIdleTimeout: tp.MaxIdleTimeout, + MaxUDPPayloadSize: tp.MaxUDPPayloadSize, + AckDelayExponent: tp.AckDelayExponent, + MaxAckDelay: tp.MaxAckDelay, + ActiveConnectionIDLimit: tp.ActiveConnectionIDLimit, + InitialMaxData: tp.InitialMaxData, + InitialMaxStreamDataBidiLocal: tp.InitialMaxStreamDataBidiLocal, + InitialMaxStreamDataBidiRemote: tp.InitialMaxStreamDataBidiRemote, + InitialMaxStreamDataUni: tp.InitialMaxStreamDataUni, + InitialMaxStreamsBidi: int64(tp.MaxBidiStreamNum), + InitialMaxStreamsUni: int64(tp.MaxUniStreamNum), + MaxDatagramFrameSize: tp.MaxDatagramFrameSize, + EnableResetStreamAt: tp.EnableResetStreamAt, + } + if sentBy == c.perspective { + ev.Initiator = qlog.InitiatorLocal + } else { + ev.Initiator = qlog.InitiatorRemote + } + if tp.PreferredAddress != nil { + ev.PreferredAddress = &qlog.PreferredAddress{ + IPv4: tp.PreferredAddress.IPv4, + IPv6: tp.PreferredAddress.IPv6, + ConnectionID: tp.PreferredAddress.ConnectionID, + StatelessResetToken: tp.PreferredAddress.StatelessResetToken, + } + } + c.qlogger.RecordEvent(ev) +} + +func toQlogECN(ecn protocol.ECN) qlog.ECN { + //nolint:exhaustive // only need to handle the 3 valid values + switch ecn { + case protocol.ECT0: + return qlog.ECT0 + case protocol.ECT1: + return qlog.ECT1 + case protocol.ECNCE: + return qlog.ECNCE + default: + return qlog.ECNUnsupported + } +} + +func toQlogPacketType(pt protocol.PacketType) qlog.PacketType { + var qpt qlog.PacketType + switch pt { + case protocol.PacketTypeInitial: + qpt = qlog.PacketTypeInitial + case protocol.PacketTypeHandshake: + qpt = qlog.PacketTypeHandshake + case protocol.PacketType0RTT: + qpt = qlog.PacketType0RTT + case protocol.PacketTypeRetry: + qpt = qlog.PacketTypeRetry + } + return qpt +} + +func toPathEndpointInfo(addr *net.UDPAddr) qlog.PathEndpointInfo { + if addr == nil { + return qlog.PathEndpointInfo{} + } + + var info qlog.PathEndpointInfo + if addr.IP == nil || addr.IP.To4() != nil { + addrPort := netip.AddrPortFrom(netip.AddrFrom4([4]byte(addr.IP.To4())), uint16(addr.Port)) + if addrPort.IsValid() { + info.IPv4 = addrPort + } + } else { + addrPort := netip.AddrPortFrom(netip.AddrFrom16([16]byte(addr.IP.To16())), uint16(addr.Port)) + if addrPort.IsValid() { + info.IPv6 = addrPort + } + } + return info +} + +// startedConnectionEvent builds a StartedConnection event using consistent logic +// for both endpoints. If the local address is unspecified (e.g., dual-stack +// listener), it selects the family based on the remote address and uses the +// unspecified address of that family with the local port. +func startedConnectionEvent(local, remote *net.UDPAddr) qlog.StartedConnection { + var localInfo, remoteInfo qlog.PathEndpointInfo + if remote != nil { + remoteInfo = toPathEndpointInfo(remote) + } + if local != nil { + if local.IP == nil || local.IP.IsUnspecified() { + // Choose local family based on the remote address family. + if remote != nil && remote.IP.To4() != nil { + ap := netip.AddrPortFrom(netip.AddrFrom4([4]byte{}), uint16(local.Port)) + if ap.IsValid() { + localInfo.IPv4 = ap + } + } else if remote != nil && remote.IP.To16() != nil && remote.IP.To4() == nil { + ap := netip.AddrPortFrom(netip.AddrFrom16([16]byte{}), uint16(local.Port)) + if ap.IsValid() { + localInfo.IPv6 = ap + } + } + } else { + localInfo = toPathEndpointInfo(local) + } } + return qlog.StartedConnection{Local: localInfo, Remote: remoteInfo} } diff --git a/vendor/github.com/quic-go/quic-go/frame_sorter.go b/vendor/github.com/quic-go/quic-go/frame_sorter.go index bee0abadb5..20c6d9cc08 100644 --- a/vendor/github.com/quic-go/quic-go/frame_sorter.go +++ b/vendor/github.com/quic-go/quic-go/frame_sorter.go @@ -235,3 +235,40 @@ func (s *frameSorter) Pop() (protocol.ByteCount, []byte, func()) { func (s *frameSorter) HasMoreData() bool { return len(s.queue) > 0 } + +var errTooLittleData = errors.New("too little data") + +// Peek copies len(p) consecutive bytes starting at offset into p, without removing them. +// It is only possible to peek from an offset where a frame starts. +// +// If there isn't enough consecutive data available, errTooLittleData is returned. +func (s *frameSorter) Peek(offset protocol.ByteCount, p []byte) error { + if len(p) == 0 { + return nil + } + + // first, check if we have enough consecutive data available + pos := offset + remaining := len(p) + for remaining > 0 { + entry, ok := s.queue[pos] + if !ok { + return errTooLittleData + } + entryLen := len(entry.Data) + if remaining <= entryLen { + break // enough data available + } + remaining -= entryLen + pos += protocol.ByteCount(entryLen) + } + + pos = offset + var copied int + for copied < len(p) { + entry := s.queue[pos] // the entry is guaranteed to exist from the check above + copied += copy(p[copied:], entry.Data) + pos += protocol.ByteCount(len(entry.Data)) + } + return nil +} diff --git a/vendor/github.com/quic-go/quic-go/interface.go b/vendor/github.com/quic-go/quic-go/interface.go index 45a03a52ac..119e320560 100644 --- a/vendor/github.com/quic-go/quic-go/interface.go +++ b/vendor/github.com/quic-go/quic-go/interface.go @@ -10,7 +10,7 @@ import ( "github.com/quic-go/quic-go/internal/handshake" "github.com/quic-go/quic-go/internal/protocol" - "github.com/quic-go/quic-go/logging" + "github.com/quic-go/quic-go/qlogwriter" ) // The StreamID is the ID of a QUIC stream. @@ -58,20 +58,6 @@ type TokenStore interface { // when the server rejects a 0-RTT connection attempt. var Err0RTTRejected = errors.New("0-RTT rejected") -// ConnectionTracingKey can be used to associate a [logging.ConnectionTracer] with a [Conn]. -// It is set on the Conn.Context() context, -// as well as on the context passed to logging.Tracer.NewConnectionTracer. -// -// Deprecated: Applications can set their own tracing key using Transport.ConnContext. -var ConnectionTracingKey = connTracingCtxKey{} - -// ConnectionTracingID is the type of the context value saved under the ConnectionTracingKey. -// -// Deprecated: Applications can set their own tracing key using Transport.ConnContext. -type ConnectionTracingID uint64 - -type connTracingCtxKey struct{} - // QUICVersionContextKey can be used to find out the QUIC version of a TLS handshake from the // context returned by tls.Config.ClientInfo.Context. var QUICVersionContextKey = handshake.QUICVersionContextKey @@ -189,13 +175,9 @@ type Config struct { // Enable QUIC Stream Resets with Partial Delivery. // See https://datatracker.ietf.org/doc/html/draft-ietf-quic-reliable-stream-reset-07. EnableStreamResetPartialDelivery bool - Tracer func(context.Context, logging.Perspective, ConnectionID) *logging.ConnectionTracer -} -// ClientHelloInfo contains information about an incoming connection attempt. -// -// Deprecated: Use ClientInfo instead. -type ClientHelloInfo = ClientInfo + Tracer func(ctx context.Context, isClient bool, connID ConnectionID) qlogwriter.Trace +} // ClientInfo contains information about an incoming connection attempt. type ClientInfo struct { @@ -212,13 +194,18 @@ type ClientInfo struct { type ConnectionState struct { // TLS contains information about the TLS connection state, incl. the tls.ConnectionState. TLS tls.ConnectionState - // SupportsDatagrams indicates whether the peer advertised support for QUIC datagrams (RFC 9221). - // When true, datagrams can be sent using the Conn's SendDatagram method. - // This is a unilateral declaration by the peer - receiving datagrams is only possible if - // datagram support was enabled locally via Config.EnableDatagrams. - SupportsDatagrams bool - // SupportsStreamResetPartialDelivery indicates whether the peer advertised support for QUIC Stream Resets with Partial Delivery. - SupportsStreamResetPartialDelivery bool + // SupportsDatagrams indicates support for QUIC datagrams (RFC 9221). + SupportsDatagrams struct { + // Remote is true if the peer advertised datagram support. + // Local is true if datagram support was enabled via Config.EnableDatagrams. + Remote, Local bool + } + // SupportsStreamResetPartialDelivery indicates support for QUIC Stream Resets with Partial Delivery. + SupportsStreamResetPartialDelivery struct { + // Remote is true if the peer advertised support. + // Local is true if support was enabled via Config.EnableStreamResetPartialDelivery. + Remote, Local bool + } // Used0RTT says if 0-RTT resumption was used. Used0RTT bool // Version is the QUIC version of the QUIC connection. 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 deleted file mode 100644 index f1bac32763..0000000000 --- a/vendor/github.com/quic-go/quic-go/internal/ackhandler/ackhandler.go +++ /dev/null @@ -1,25 +0,0 @@ -package ackhandler - -import ( - "github.com/quic-go/quic-go/internal/protocol" - "github.com/quic-go/quic-go/internal/utils" - "github.com/quic-go/quic-go/logging" -) - -// NewAckHandler creates a new SentPacketHandler and a new ReceivedPacketHandler. -// clientAddressValidated indicates whether the address was validated beforehand by an address validation token. -// clientAddressValidated has no effect for a client. -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, 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 1b462a6065..123d3a341d 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 @@ -5,7 +5,8 @@ import ( "github.com/quic-go/quic-go/internal/protocol" "github.com/quic-go/quic-go/internal/utils" - "github.com/quic-go/quic-go/logging" + "github.com/quic-go/quic-go/qlog" + "github.com/quic-go/quic-go/qlogwriter" ) type ecnState uint8 @@ -18,6 +19,22 @@ const ( ecnStateFailed ) +const ( + // ecnFailedNoECNCounts is emitted when an ACK acknowledges ECN-marked packets, + // but doesn't contain any ECN counts + ecnFailedNoECNCounts = "ACK doesn't contain ECN marks" + // ecnFailedDecreasedECNCounts is emitted when an ACK frame decreases ECN counts + ecnFailedDecreasedECNCounts = "ACK decreases ECN counts" + // ecnFailedLostAllTestingPackets is emitted when all ECN testing packets are declared lost + ecnFailedLostAllTestingPackets = "all ECN testing packets declared lost" + // ecnFailedMoreECNCountsThanSent is emitted when an ACK contains more ECN counts than ECN-marked packets were sent + ecnFailedMoreECNCountsThanSent = "ACK contains more ECN counts than ECN-marked packets sent" + // ecnFailedTooFewECNCounts is emitted when an ACK contains fewer ECN counts than it acknowledges packets + ecnFailedTooFewECNCounts = "ACK contains fewer new ECN counts than acknowledged ECN-marked packets" + // ecnFailedManglingDetected is emitted when the path marks all ECN-marked packets as CE + ecnFailedManglingDetected = "ECN mangling detected" +) + // must fit into an uint8, otherwise numSentTesting and numLostTesting must have a larger type const numECNTestingPackets = 10 @@ -45,20 +62,20 @@ type ecnTracker struct { numSentECT0, numSentECT1 int64 numAckedECT0, numAckedECT1, numAckedECNCE int64 - tracer *logging.ConnectionTracer - logger utils.Logger + qlogger qlogwriter.Recorder + logger utils.Logger } var _ ecnHandler = &ecnTracker{} -func newECNTracker(logger utils.Logger, tracer *logging.ConnectionTracer) *ecnTracker { +func newECNTracker(logger utils.Logger, qlogger qlogwriter.Recorder) *ecnTracker { return &ecnTracker{ firstTestingPacket: protocol.InvalidPacketNumber, lastTestingPacket: protocol.InvalidPacketNumber, firstCapablePacket: protocol.InvalidPacketNumber, state: ecnStateInitial, logger: logger, - tracer: tracer, + qlogger: qlogger, } } @@ -92,8 +109,10 @@ func (e *ecnTracker) SentPacket(pn protocol.PacketNumber, ecn protocol.ECN) { e.firstTestingPacket = pn } if e.numSentECT0+e.numSentECT1 >= numECNTestingPackets { - if e.tracer != nil && e.tracer.ECNStateUpdated != nil { - e.tracer.ECNStateUpdated(logging.ECNStateUnknown, logging.ECNTriggerNoTrigger) + if e.qlogger != nil { + e.qlogger.RecordEvent(qlog.ECNStateUpdated{ + State: qlog.ECNStateUnknown, + }) } e.state = ecnStateUnknown e.lastTestingPacket = pn @@ -103,8 +122,10 @@ func (e *ecnTracker) SentPacket(pn protocol.PacketNumber, ecn protocol.ECN) { func (e *ecnTracker) Mode() protocol.ECN { switch e.state { case ecnStateInitial: - if e.tracer != nil && e.tracer.ECNStateUpdated != nil { - e.tracer.ECNStateUpdated(logging.ECNStateTesting, logging.ECNTriggerNoTrigger) + if e.qlogger != nil { + e.qlogger.RecordEvent(qlog.ECNStateUpdated{ + State: qlog.ECNStateTesting, + }) } e.state = ecnStateTesting return e.Mode() @@ -131,8 +152,11 @@ func (e *ecnTracker) LostPacket(pn protocol.PacketNumber) { } if e.numLostTesting >= e.numSentTesting { e.logger.Debugf("Disabling ECN. All testing packets were lost.") - if e.tracer != nil && e.tracer.ECNStateUpdated != nil { - e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedLostAllTestingPackets) + if e.qlogger != nil { + e.qlogger.RecordEvent(qlog.ECNStateUpdated{ + State: qlog.ECNStateFailed, + Trigger: ecnFailedLostAllTestingPackets, + }) } e.state = ecnStateFailed return @@ -153,8 +177,11 @@ func (e *ecnTracker) HandleNewlyAcked(packets []packetWithPacketNumber, ect0, ec // the total number of packets sent with each corresponding ECT codepoint. if ect0 > e.numSentECT0 || ect1 > e.numSentECT1 { e.logger.Debugf("Disabling ECN. Received more ECT(0) / ECT(1) acknowledgements than packets sent.") - if e.tracer != nil && e.tracer.ECNStateUpdated != nil { - e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedMoreECNCountsThanSent) + if e.qlogger != nil { + e.qlogger.RecordEvent(qlog.ECNStateUpdated{ + State: qlog.ECNStateFailed, + Trigger: ecnFailedMoreECNCountsThanSent, + }) } e.state = ecnStateFailed return false @@ -179,8 +206,11 @@ func (e *ecnTracker) HandleNewlyAcked(packets []packetWithPacketNumber, ect0, ec // * peers that don't report any ECN counts if (ackedECT0 > 0 || ackedECT1 > 0) && ect0 == 0 && ect1 == 0 && ecnce == 0 { e.logger.Debugf("Disabling ECN. ECN-marked packet acknowledged, but no ECN counts on ACK frame.") - if e.tracer != nil && e.tracer.ECNStateUpdated != nil { - e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedNoECNCounts) + if e.qlogger != nil { + e.qlogger.RecordEvent(qlog.ECNStateUpdated{ + State: qlog.ECNStateFailed, + Trigger: ecnFailedNoECNCounts, + }) } e.state = ecnStateFailed return false @@ -196,8 +226,11 @@ func (e *ecnTracker) HandleNewlyAcked(packets []packetWithPacketNumber, ect0, ec // Any decrease means that the peer's counting logic is broken. if newECT0 < 0 || newECT1 < 0 || newECNCE < 0 { e.logger.Debugf("Disabling ECN. ECN counts decreased unexpectedly.") - if e.tracer != nil && e.tracer.ECNStateUpdated != nil { - e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedDecreasedECNCounts) + if e.qlogger != nil { + e.qlogger.RecordEvent(qlog.ECNStateUpdated{ + State: qlog.ECNStateFailed, + Trigger: ecnFailedDecreasedECNCounts, + }) } e.state = ecnStateFailed return false @@ -208,8 +241,11 @@ func (e *ecnTracker) HandleNewlyAcked(packets []packetWithPacketNumber, ect0, ec // This could be the result of (partial) bleaching. if newECT0+newECNCE < ackedECT0 { e.logger.Debugf("Disabling ECN. Received less ECT(0) + ECN-CE than packets sent with ECT(0).") - if e.tracer != nil && e.tracer.ECNStateUpdated != nil { - e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedTooFewECNCounts) + if e.qlogger != nil { + e.qlogger.RecordEvent(qlog.ECNStateUpdated{ + State: qlog.ECNStateFailed, + Trigger: ecnFailedTooFewECNCounts, + }) } e.state = ecnStateFailed return false @@ -218,8 +254,11 @@ func (e *ecnTracker) HandleNewlyAcked(packets []packetWithPacketNumber, ect0, ec // the number of newly acknowledged packets sent with an ECT(1) marking. if newECT1+newECNCE < ackedECT1 { e.logger.Debugf("Disabling ECN. Received less ECT(1) + ECN-CE than packets sent with ECT(1).") - if e.tracer != nil && e.tracer.ECNStateUpdated != nil { - e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedTooFewECNCounts) + if e.qlogger != nil { + e.qlogger.RecordEvent(qlog.ECNStateUpdated{ + State: qlog.ECNStateFailed, + Trigger: ecnFailedTooFewECNCounts, + }) } e.state = ecnStateFailed return false @@ -249,8 +288,10 @@ func (e *ecnTracker) HandleNewlyAcked(packets []packetWithPacketNumber, ect0, ec // This check won't succeed if the path is mangling ECN-marks (i.e. rewrites all ECN-marked packets to CE). if ackedTestingPacket && (newECT0 > 0 || newECT1 > 0) { e.logger.Debugf("ECN capability confirmed.") - if e.tracer != nil && e.tracer.ECNStateUpdated != nil { - e.tracer.ECNStateUpdated(logging.ECNStateCapable, logging.ECNTriggerNoTrigger) + if e.qlogger != nil { + e.qlogger.RecordEvent(qlog.ECNStateUpdated{ + State: qlog.ECNStateCapable, + }) } e.state = ecnStateCapable } @@ -267,8 +308,11 @@ func (e *ecnTracker) failIfMangled() { if e.numSentECT0+e.numSentECT1 > numAckedECNCE { return } - if e.tracer != nil && e.tracer.ECNStateUpdated != nil { - e.tracer.ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedManglingDetected) + if e.qlogger != nil { + e.qlogger.RecordEvent(qlog.ECNStateUpdated{ + State: qlog.ECNStateFailed, + Trigger: ecnFailedManglingDetected, + }) } e.state = ecnStateFailed } 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 9ee9da7d99..620a5e11f0 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 @@ -13,6 +13,7 @@ type SentPacketHandler interface { // ReceivedAck processes an ACK frame. // It does not store a copy of the frame. ReceivedAck(f *wire.AckFrame, encLevel protocol.EncryptionLevel, rcvTime monotime.Time) (bool /* 1-RTT packet acked */, error) + ReceivedPacket(protocol.EncryptionLevel, monotime.Time) ReceivedBytes(_ protocol.ByteCount, rcvTime monotime.Time) DropPackets(_ protocol.EncryptionLevel, rcvTime monotime.Time) ResetForRetry(rcvTime monotime.Time) @@ -36,18 +37,3 @@ type SentPacketHandler interface { MigratedPath(now monotime.Time, initialMaxPacketSize protocol.ByteCount) } - -type sentPacketTracker interface { - GetLowestPacketNotConfirmedAcked() protocol.PacketNumber - 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 monotime.Time, ackEliciting bool) error - DropPackets(protocol.EncryptionLevel) - - 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/mockgen.go b/vendor/github.com/quic-go/quic-go/internal/ackhandler/mockgen.go index 323d9fde9f..3add80d1f3 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,5 @@ package ackhandler -//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 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 f0500e9f2d..7d9863000a 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 @@ -24,12 +24,15 @@ type packet struct { IsPathMTUProbePacket bool // We don't report the loss of Path MTU probe packets to the congestion controller. includedInBytesInFlight bool - declaredLost bool isPathProbePacket bool } -func (p *packet) outstanding() bool { - return !p.declaredLost && !p.IsPathMTUProbePacket && !p.isPathProbePacket +func (p *packet) Outstanding() bool { + return !p.IsPathMTUProbePacket && !p.isPathProbePacket && p.IsAckEliciting() +} + +func (p *packet) IsAckEliciting() bool { + return len(p.StreamFrames) > 0 || len(p.Frames) > 0 } var packetPool = sync.Pool{New: func() any { return &packet{} }} @@ -44,7 +47,6 @@ func getPacket() *packet { p.SendTime = 0 p.IsPathMTUProbePacket = false p.includedInBytesInFlight = false - p.declaredLost = 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 7180db85f1..d0d24bf30a 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 @@ -9,9 +9,7 @@ import ( "github.com/quic-go/quic-go/internal/wire" ) -type receivedPacketHandler struct { - sentPackets sentPacketTracker - +type ReceivedPacketHandler struct { initialPackets *receivedPacketTracker handshakePackets *receivedPacketTracker appDataPackets appDataReceivedPacketTracker @@ -19,11 +17,8 @@ type receivedPacketHandler struct { lowest1RTTPacket protocol.PacketNumber } -var _ ReceivedPacketHandler = &receivedPacketHandler{} - -func newReceivedPacketHandler(sentPackets sentPacketTracker, logger utils.Logger) ReceivedPacketHandler { - return &receivedPacketHandler{ - sentPackets: sentPackets, +func NewReceivedPacketHandler(logger utils.Logger) *ReceivedPacketHandler { + return &ReceivedPacketHandler{ initialPackets: newReceivedPacketTracker(), handshakePackets: newReceivedPacketTracker(), appDataPackets: *newAppDataReceivedPacketTracker(logger), @@ -31,14 +26,13 @@ func newReceivedPacketHandler(sentPackets sentPacketTracker, logger utils.Logger } } -func (h *receivedPacketHandler) ReceivedPacket( +func (h *ReceivedPacketHandler) ReceivedPacket( pn protocol.PacketNumber, ecn protocol.ECN, encLevel protocol.EncryptionLevel, rcvTime monotime.Time, ackEliciting bool, ) error { - h.sentPackets.ReceivedPacket(encLevel, rcvTime) switch encLevel { case protocol.EncryptionInitial: return h.initialPackets.ReceivedPacket(pn, ecn, ackEliciting) @@ -58,17 +52,17 @@ func (h *receivedPacketHandler) ReceivedPacket( if h.lowest1RTTPacket == protocol.InvalidPacketNumber || pn < h.lowest1RTTPacket { h.lowest1RTTPacket = pn } - if err := h.appDataPackets.ReceivedPacket(pn, ecn, rcvTime, ackEliciting); err != nil { - return err - } - h.appDataPackets.IgnoreBelow(h.sentPackets.GetLowestPacketNotConfirmedAcked()) - return nil + return h.appDataPackets.ReceivedPacket(pn, ecn, rcvTime, ackEliciting) default: panic(fmt.Sprintf("received packet with unknown encryption level: %s", encLevel)) } } -func (h *receivedPacketHandler) DropPackets(encLevel protocol.EncryptionLevel) { +func (h *ReceivedPacketHandler) IgnorePacketsBelow(pn protocol.PacketNumber) { + h.appDataPackets.IgnoreBelow(pn) +} + +func (h *ReceivedPacketHandler) DropPackets(encLevel protocol.EncryptionLevel) { //nolint:exhaustive // 1-RTT packet number space is never dropped. switch encLevel { case protocol.EncryptionInitial: @@ -83,11 +77,11 @@ func (h *receivedPacketHandler) DropPackets(encLevel protocol.EncryptionLevel) { } } -func (h *receivedPacketHandler) GetAlarmTimeout() monotime.Time { +func (h *ReceivedPacketHandler) GetAlarmTimeout() monotime.Time { return h.appDataPackets.GetAlarmTimeout() } -func (h *receivedPacketHandler) GetAckFrame(encLevel protocol.EncryptionLevel, now monotime.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: @@ -108,7 +102,7 @@ func (h *receivedPacketHandler) GetAckFrame(encLevel protocol.EncryptionLevel, n } } -func (h *receivedPacketHandler) IsPotentiallyDuplicate(pn protocol.PacketNumber, encLevel protocol.EncryptionLevel) bool { +func (h *ReceivedPacketHandler) IsPotentiallyDuplicate(pn protocol.PacketNumber, encLevel protocol.EncryptionLevel) bool { switch encLevel { case protocol.EncryptionInitial: if h.initialPackets != nil { 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 80f154ba86..64092cccf8 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 @@ -155,6 +155,9 @@ func (h *appDataReceivedPacketTracker) isMissing(p protocol.PacketNumber) bool { } func (h *appDataReceivedPacketTracker) hasNewMissingPackets() bool { + if h.lastAck == nil { + return false + } if h.largestObserved < reorderingThreshold { return false } @@ -170,12 +173,6 @@ func (h *appDataReceivedPacketTracker) hasNewMissingPackets() bool { } func (h *appDataReceivedPacketTracker) shouldQueueACK(pn protocol.PacketNumber, ecn protocol.ECN, wasMissing bool) bool { - // always acknowledge the first packet - if h.lastAck == nil { - h.logger.Debugf("\tQueueing ACK because the first packet should be acknowledged.") - return true - } - // Send an ACK if this packet was reported missing in an ACK sent before. // Ack decimation with reordering relies on the timer to send an ACK, but if // missing packets we reported in the previous ACK, send an ACK immediately. 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 11739e2258..9b539eadd0 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 @@ -11,7 +11,8 @@ import ( "github.com/quic-go/quic-go/internal/qerr" "github.com/quic-go/quic-go/internal/utils" "github.com/quic-go/quic-go/internal/wire" - "github.com/quic-go/quic-go/logging" + "github.com/quic-go/quic-go/qlog" + "github.com/quic-go/quic-go/qlogwriter" ) const ( @@ -59,7 +60,7 @@ func newPacketNumberSpace(initialPN protocol.PacketNumber, isAppData bool) *pack type alarmTimer struct { Time monotime.Time - TimerType logging.TimerType + TimerType qlog.TimerType EncryptionLevel protocol.EncryptionLevel } @@ -68,6 +69,8 @@ type sentPacketHandler struct { handshakePackets *packetNumberSpace appDataPackets *packetNumberSpace lostPackets lostPacketTracker // only for application-data packet number space + // send time of the largest acknowledged packet, across all packet number spaces + largestAckedTime monotime.Time // Do we know that the peer completed address validation yet? // Always true for the server. @@ -80,11 +83,7 @@ type sentPacketHandler struct { handshakeConfirmed bool - // lowestNotConfirmedAcked is the lowest packet number that we sent an ACK for, but haven't received confirmation, that this ACK actually arrived - // example: we send an ACK for packets 90-100 with packet number 20 - // once we receive an ACK from the peer for packet 20, the lowestNotConfirmedAcked is 101 - // Only applies to the application-data packet number space. - lowestNotConfirmedAcked protocol.PacketNumber + ignorePacketsBelow func(protocol.PacketNumber) ackedPackets []packetWithPacketNumber // to avoid allocations in detectAndRemoveAckedPackets @@ -109,35 +108,34 @@ type sentPacketHandler struct { perspective protocol.Perspective - tracer *logging.ConnectionTracer - logger utils.Logger + qlogger qlogwriter.Recorder + lastMetrics qlog.MetricsUpdated + logger utils.Logger } -var ( - _ SentPacketHandler = &sentPacketHandler{} - _ sentPacketTracker = &sentPacketHandler{} -) +var _ SentPacketHandler = &sentPacketHandler{} // clientAddressValidated indicates whether the address was validated beforehand by an address validation token. // If the address was validated, the amplification limit doesn't apply. It has no effect for a client. -func newSentPacketHandler( +func NewSentPacketHandler( initialPN protocol.PacketNumber, initialMaxDatagramSize protocol.ByteCount, rttStats *utils.RTTStats, connStats *utils.ConnectionStats, clientAddressValidated bool, enableECN bool, + ignorePacketsBelow func(protocol.PacketNumber), pers protocol.Perspective, - tracer *logging.ConnectionTracer, + qlogger qlogwriter.Recorder, logger utils.Logger, -) *sentPacketHandler { +) SentPacketHandler { congestion := congestion.NewCubicSender( congestion.DefaultClock{}, rttStats, connStats, initialMaxDatagramSize, true, // use Reno - tracer, + qlogger, ) h := &sentPacketHandler{ @@ -150,13 +148,14 @@ func newSentPacketHandler( rttStats: rttStats, connStats: connStats, congestion: congestion, + ignorePacketsBelow: ignorePacketsBelow, perspective: pers, - tracer: tracer, + qlogger: qlogger, logger: logger, } if enableECN { h.enableECN = true - h.ecnTracker = newECNTracker(logger, tracer) + h.ecnTracker = newECNTracker(logger, qlogger) } return h } @@ -213,8 +212,8 @@ func (h *sentPacketHandler) DropPackets(encLevel protocol.EncryptionLevel, now m default: panic(fmt.Sprintf("Cannot drop keys for encryption level %s", encLevel)) } - if h.tracer != nil && h.tracer.UpdatedPTOCount != nil && h.ptoCount != 0 { - h.tracer.UpdatedPTOCount(0) + if h.qlogger != nil && h.ptoCount != 0 { + h.qlogger.RecordEvent(qlog.PTOCountUpdated{PTOCount: 0}) } h.ptoCount = 0 h.numProbesToSend = 0 @@ -240,12 +239,12 @@ func (h *sentPacketHandler) ReceivedPacket(l protocol.EncryptionLevel, t monotim } func (h *sentPacketHandler) packetsInFlight() int { - packetsInFlight := h.appDataPackets.history.Len() + packetsInFlight := h.appDataPackets.history.NumOutstanding() if h.handshakePackets != nil { - packetsInFlight += h.handshakePackets.history.Len() + packetsInFlight += h.handshakePackets.history.NumOutstanding() } if h.initialPackets != nil { - packetsInFlight += h.initialPackets.history.Len() + packetsInFlight += h.initialPackets.history.NumOutstanding() } return packetsInFlight } @@ -273,15 +272,19 @@ func (h *sentPacketHandler) SentPacket( } pnSpace.largestSent = pn - isAckEliciting := len(streamFrames) > 0 || len(frames) > 0 + + p := getPacket() + p.SendTime = t + p.EncryptionLevel = encLevel + p.Length = size + p.Frames = frames + p.LargestAcked = largestAcked + p.StreamFrames = streamFrames + p.IsPathMTUProbePacket = isPathMTUProbePacket + p.isPathProbePacket = isPathProbePacket + isAckEliciting := p.IsAckEliciting() if isPathProbePacket { - p := getPacket() - p.SendTime = t - p.EncryptionLevel = encLevel - p.Length = size - p.Frames = frames - p.isPathProbePacket = true pnSpace.history.SentPathProbePacket(pn, p) h.setLossDetectionTimer(t) return @@ -289,6 +292,7 @@ func (h *sentPacketHandler) SentPacket( if isAckEliciting { pnSpace.lastAckElicitingPacketTime = t h.bytesInFlight += size + p.includedInBytesInFlight = true if h.numProbesToSend > 0 { h.numProbesToSend-- } @@ -299,31 +303,65 @@ func (h *sentPacketHandler) SentPacket( h.ecnTracker.SentPacket(pn, ecn) } + pnSpace.history.SentPacket(pn, p) if !isAckEliciting { - pnSpace.history.SentNonAckElicitingPacket(pn) if !h.peerCompletedAddressValidation { h.setLossDetectionTimer(t) } return } - - p := getPacket() - p.SendTime = t - p.EncryptionLevel = encLevel - p.Length = size - p.LargestAcked = largestAcked - p.StreamFrames = streamFrames - p.Frames = frames - p.IsPathMTUProbePacket = isPathMTUProbePacket - p.includedInBytesInFlight = true - - 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()) + if h.qlogger != nil { + h.qlogMetricsUpdated() } h.setLossDetectionTimer(t) } +func (h *sentPacketHandler) qlogMetricsUpdated() { + var metricsUpdatedEvent qlog.MetricsUpdated + var updated bool + if h.rttStats.HasMeasurement() { + if h.lastMetrics.MinRTT != h.rttStats.MinRTT() { + metricsUpdatedEvent.MinRTT = h.rttStats.MinRTT() + h.lastMetrics.MinRTT = metricsUpdatedEvent.MinRTT + updated = true + } + if h.lastMetrics.SmoothedRTT != h.rttStats.SmoothedRTT() { + metricsUpdatedEvent.SmoothedRTT = h.rttStats.SmoothedRTT() + h.lastMetrics.SmoothedRTT = metricsUpdatedEvent.SmoothedRTT + updated = true + } + if h.lastMetrics.LatestRTT != h.rttStats.LatestRTT() { + metricsUpdatedEvent.LatestRTT = h.rttStats.LatestRTT() + h.lastMetrics.LatestRTT = metricsUpdatedEvent.LatestRTT + updated = true + } + if h.lastMetrics.RTTVariance != h.rttStats.MeanDeviation() { + metricsUpdatedEvent.RTTVariance = h.rttStats.MeanDeviation() + h.lastMetrics.RTTVariance = metricsUpdatedEvent.RTTVariance + updated = true + } + } + if cwnd := h.congestion.GetCongestionWindow(); h.lastMetrics.CongestionWindow != int(cwnd) { + metricsUpdatedEvent.CongestionWindow = int(cwnd) + h.lastMetrics.CongestionWindow = metricsUpdatedEvent.CongestionWindow + updated = true + } + if h.lastMetrics.BytesInFlight != int(h.bytesInFlight) { + metricsUpdatedEvent.BytesInFlight = int(h.bytesInFlight) + h.lastMetrics.BytesInFlight = metricsUpdatedEvent.BytesInFlight + updated = true + } + packetsInFlight := h.packetsInFlight() + if h.lastMetrics.PacketsInFlight != packetsInFlight { + metricsUpdatedEvent.PacketsInFlight = packetsInFlight + h.lastMetrics.PacketsInFlight = metricsUpdatedEvent.PacketsInFlight + updated = true + } + if updated { + h.qlogger.RecordEvent(metricsUpdatedEvent) + } +} + func (h *sentPacketHandler) getPacketNumberSpace(encLevel protocol.EncryptionLevel) *packetNumberSpace { switch encLevel { case protocol.EncryptionInitial: @@ -358,21 +396,26 @@ func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.En } priorInFlight := h.bytesInFlight - ackedPackets, err := h.detectAndRemoveAckedPackets(ack, encLevel) + ackedPackets, hasAckEliciting, err := h.detectAndRemoveAckedPackets(ack, encLevel) if err != nil || len(ackedPackets) == 0 { return false, err } - // update the RTT, if the largest acked is newly acknowledged + // update the RTT, if: + // * the largest acked is newly acknowledged, AND + // * at least one new ack-eliciting packet was acknowledged if len(ackedPackets) > 0 { - if p := ackedPackets[len(ackedPackets)-1]; p.PacketNumber == ack.LargestAcked() && !p.isPathProbePacket { + if p := ackedPackets[len(ackedPackets)-1]; p.PacketNumber == ack.LargestAcked() && !p.isPathProbePacket && hasAckEliciting { // don't use the ack delay for Initial and Handshake packets var ackDelay time.Duration if encLevel == protocol.Encryption1RTT { ackDelay = min(ack.DelayTime, h.rttStats.MaxAckDelay()) } - h.rttStats.UpdateRTT(rcvTime.Sub(p.SendTime), ackDelay) - if h.logger.Debug() { - h.logger.Debugf("\tupdated RTT: %s (σ: %s)", h.rttStats.SmoothedRTT(), h.rttStats.MeanDeviation()) + if h.largestAckedTime.IsZero() || !p.SendTime.Before(h.largestAckedTime) { + h.rttStats.UpdateRTT(rcvTime.Sub(p.SendTime), ackDelay) + if h.logger.Debug() { + h.logger.Debugf("\tupdated RTT: %s (σ: %s)", h.rttStats.SmoothedRTT(), h.rttStats.MeanDeviation()) + } + h.largestAckedTime = p.SendTime } h.congestion.MaybeExitSlowStart() } @@ -394,7 +437,7 @@ func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.En } var acked1RTTPacket bool for _, p := range ackedPackets { - if p.includedInBytesInFlight && !p.declaredLost { + if p.includedInBytesInFlight { h.congestion.OnPacketAcked(p.PacketNumber, p.Length, priorInFlight, rcvTime) } if p.EncryptionLevel == protocol.Encryption1RTT { @@ -424,15 +467,15 @@ func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.En // Reset the pto_count unless the client is unsure if the server has validated the client's address. if h.peerCompletedAddressValidation { - if h.tracer != nil && h.tracer.UpdatedPTOCount != nil && h.ptoCount != 0 { - h.tracer.UpdatedPTOCount(0) + if h.qlogger != nil && h.ptoCount != 0 { + h.qlogger.RecordEvent(qlog.PTOCountUpdated{PTOCount: 0}) } h.ptoCount = 0 } h.numProbesToSend = 0 - if h.tracer != nil && h.tracer.UpdatedMetrics != nil { - h.tracer.UpdatedMetrics(h.rttStats, h.congestion.GetCongestionWindow(), h.bytesInFlight, h.packetsInFlight()) + if h.qlogger != nil { + h.qlogMetricsUpdated() } h.setLossDetectionTimer(rcvTime) @@ -463,8 +506,13 @@ func (h *sentPacketHandler) detectSpuriousLosses(ack *wire.AckFrame, ackTime mon 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) + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.SpuriousLoss{ + EncryptionLevel: protocol.Encryption1RTT, + PacketNumber: pn, + PacketReordering: uint64(packetReordering), + TimeReordering: timeReordering, + }) } spuriousLosses = append(spuriousLosses, pn) } @@ -474,14 +522,13 @@ func (h *sentPacketHandler) detectSpuriousLosses(ack *wire.AckFrame, ackTime mon } } -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) ([]packetWithPacketNumber, error) { +func (h *sentPacketHandler) detectAndRemoveAckedPackets( + ack *wire.AckFrame, + encLevel protocol.EncryptionLevel, +) (_ []packetWithPacketNumber, hasAckEliciting bool, _ error) { if len(h.ackedPackets) > 0 { - return nil, errors.New("ackhandler BUG: ackedPackets slice not empty") + return nil, false, errors.New("ackhandler BUG: ackedPackets slice not empty") } pnSpace := h.getPacketNumberSpace(encLevel) @@ -489,7 +536,7 @@ func (h *sentPacketHandler) detectAndRemoveAckedPackets(ack *wire.AckFrame, encL if encLevel == protocol.Encryption1RTT { for p := range pnSpace.history.SkippedPackets() { if ack.AcksPacket(p) { - return nil, &qerr.TransportError{ + return nil, false, &qerr.TransportError{ ErrorCode: qerr.ProtocolViolation, ErrorMessage: fmt.Sprintf("received an ACK for skipped packet number: %d (%s)", p, encLevel), } @@ -521,7 +568,7 @@ func (h *sentPacketHandler) detectAndRemoveAckedPackets(ack *wire.AckFrame, encL continue } 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) + return nil, false, fmt.Errorf("BUG: ackhandler would have acked wrong packet %d, while evaluating range %d -> %d", pn, ackRange.Smallest, ackRange.Largest) } } if p.isPathProbePacket { @@ -532,6 +579,9 @@ func (h *sentPacketHandler) detectAndRemoveAckedPackets(ack *wire.AckFrame, encL } continue } + if p.IsAckEliciting() { + hasAckEliciting = true + } h.ackedPackets = append(h.ackedPackets, packetWithPacketNumber{PacketNumber: pn, packet: p}) } if h.logger.Debug() && len(h.ackedPackets) > 0 { @@ -543,8 +593,8 @@ func (h *sentPacketHandler) detectAndRemoveAckedPackets(ack *wire.AckFrame, encL } for _, p := range h.ackedPackets { - if p.LargestAcked != protocol.InvalidPacketNumber && encLevel == protocol.Encryption1RTT { - h.lowestNotConfirmedAcked = max(h.lowestNotConfirmedAcked, p.LargestAcked+1) + if p.LargestAcked != protocol.InvalidPacketNumber && encLevel == protocol.Encryption1RTT && h.ignorePacketsBelow != nil { + h.ignorePacketsBelow(p.LargestAcked + 1) } for _, f := range p.Frames { @@ -558,13 +608,11 @@ func (h *sentPacketHandler) detectAndRemoveAckedPackets(ack *wire.AckFrame, encL } } if err := pnSpace.history.Remove(p.PacketNumber); err != nil { - return nil, err - } - if h.tracer != nil && h.tracer.AcknowledgedPacket != nil { - h.tracer.AcknowledgedPacket(encLevel, p.PacketNumber) + return nil, false, err } } - return h.ackedPackets, nil + // TODO: add support for the transport:packets_acked qlog event + return h.ackedPackets, hasAckEliciting, nil } func (h *sentPacketHandler) getLossTimeAndSpace() (monotime.Time, protocol.EncryptionLevel) { @@ -653,13 +701,20 @@ func (h *sentPacketHandler) setLossDetectionTimer(now monotime.Time) { hasAlarm := !newAlarm.Time.IsZero() if !hasAlarm && !oldAlarm.Time.IsZero() { h.logger.Debugf("Canceling loss detection timer.") - if h.tracer != nil && h.tracer.LossTimerCanceled != nil { - h.tracer.LossTimerCanceled() + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.LossTimerUpdated{ + Type: qlog.LossTimerUpdateTypeCancelled, + }) } } - if hasAlarm && h.tracer != nil && h.tracer.SetLossTimer != nil && newAlarm != oldAlarm { - h.tracer.SetLossTimer(newAlarm.TimerType, newAlarm.EncryptionLevel, newAlarm.Time.ToTime()) + if h.qlogger != nil && hasAlarm && newAlarm != oldAlarm { + h.qlogger.RecordEvent(qlog.LossTimerUpdated{ + Type: qlog.LossTimerUpdateTypeSet, + TimerType: newAlarm.TimerType, + EncLevel: newAlarm.EncryptionLevel, + Time: newAlarm.Time.ToTime(), + }) } } @@ -687,7 +742,7 @@ func (h *sentPacketHandler) lossDetectionTime(now monotime.Time) alarmTimer { if !lossTime.IsZero() && (pathProbeLossTime.IsZero() || lossTime.Before(pathProbeLossTime)) { return alarmTimer{ Time: lossTime, - TimerType: logging.TimerTypeACK, + TimerType: qlog.TimerTypeACK, EncryptionLevel: encLevel, } } @@ -695,14 +750,14 @@ func (h *sentPacketHandler) lossDetectionTime(now monotime.Time) alarmTimer { if !ptoTime.IsZero() && (pathProbeLossTime.IsZero() || ptoTime.Before(pathProbeLossTime)) { return alarmTimer{ Time: ptoTime, - TimerType: logging.TimerTypePTO, + TimerType: qlog.TimerTypePTO, EncryptionLevel: encLevel, } } if !pathProbeLossTime.IsZero() { return alarmTimer{ Time: pathProbeLossTime, - TimerType: logging.TimerTypePathProbe, + TimerType: qlog.TimerTypePathProbe, EncryptionLevel: protocol.Encryption1RTT, } } @@ -751,22 +806,34 @@ func (h *sentPacketHandler) detectLostPackets(now monotime.Time, encLevel protoc var packetLost bool if !p.SendTime.After(lostSendTime) { packetLost = true - if !p.isPathProbePacket { + if !p.isPathProbePacket && p.IsAckEliciting() { if h.logger.Debug() { h.logger.Debugf("\tlost packet %d (time threshold)", pn) } - if h.tracer != nil && h.tracer.LostPacket != nil { - h.tracer.LostPacket(p.EncryptionLevel, pn, logging.PacketLossTimeThreshold) + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.PacketLost{ + Header: qlog.PacketHeader{ + PacketType: qlog.EncryptionLevelToPacketType(p.EncryptionLevel), + PacketNumber: pn, + }, + Trigger: qlog.PacketLossTimeThreshold, + }) } } } else if pnSpace.history.Difference(pnSpace.largestAcked, pn) >= packetThreshold { packetLost = true - if !p.isPathProbePacket { + if !p.isPathProbePacket && p.IsAckEliciting() { if h.logger.Debug() { h.logger.Debugf("\tlost packet %d (reordering threshold)", pn) } - if h.tracer != nil && h.tracer.LostPacket != nil { - h.tracer.LostPacket(p.EncryptionLevel, pn, logging.PacketLossReorderingThreshold) + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.PacketLost{ + Header: qlog.PacketHeader{ + PacketType: qlog.EncryptionLevelToPacketType(p.EncryptionLevel), + PacketNumber: pn, + }, + Trigger: qlog.PacketLossReorderingThreshold, + }) } } } else if pnSpace.lossTime.IsZero() { @@ -782,7 +849,7 @@ func (h *sentPacketHandler) detectLostPackets(now monotime.Time, encLevel protoc h.lostPackets.Add(pn, p.SendTime) } pnSpace.history.DeclareLost(pn) - if !p.isPathProbePacket { + if !p.isPathProbePacket && p.IsAckEliciting() { // 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) @@ -809,8 +876,12 @@ func (h *sentPacketHandler) OnLossDetectionTimeout(now monotime.Time) error { if h.logger.Debug() { h.logger.Debugf("Loss detection alarm fired in loss timer mode. Loss time: %s", earliestLossTime) } - if h.tracer != nil && h.tracer.LossTimerExpired != nil { - h.tracer.LossTimerExpired(logging.TimerTypeACK, encLevel) + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.LossTimerUpdated{ + Type: qlog.LossTimerUpdateTypeExpired, + TimerType: qlog.TimerTypeACK, + EncLevel: encLevel, + }) } // Early retransmit or time loss detection h.detectLostPackets(now, encLevel) @@ -847,13 +918,13 @@ func (h *sentPacketHandler) OnLossDetectionTimeout(now monotime.Time) error { if h.logger.Debug() { h.logger.Debugf("Loss detection alarm for %s fired in PTO mode. PTO count: %d", encLevel, h.ptoCount) } - if h.tracer != nil { - if h.tracer.LossTimerExpired != nil { - h.tracer.LossTimerExpired(logging.TimerTypePTO, encLevel) - } - if h.tracer.UpdatedPTOCount != nil { - h.tracer.UpdatedPTOCount(h.ptoCount) - } + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.LossTimerUpdated{ + Type: qlog.LossTimerUpdateTypeExpired, + TimerType: qlog.TimerTypePTO, + EncLevel: encLevel, + }) + h.qlogger.RecordEvent(qlog.PTOCountUpdated{PTOCount: h.ptoCount}) } h.numProbesToSend += 2 //nolint:exhaustive // We never arm a PTO timer for 0-RTT packets. @@ -973,11 +1044,12 @@ func (h *sentPacketHandler) QueueProbePacket(encLevel protocol.EncryptionLevel) if p == nil { return false } - h.queueFramesForRetransmission(p) // TODO: don't declare the packet lost here. // Keep track of acknowledged frames instead. - h.removeFromBytesInFlight(p) + // Call DeclareLost before queueFramesForRetransmission, which clears the packet's frames. pnSpace.history.DeclareLost(pn) + h.removeFromBytesInFlight(p) + h.queueFramesForRetransmission(p) return true } @@ -1006,14 +1078,14 @@ func (h *sentPacketHandler) ResetForRetry(now monotime.Time) { if firstPacketSendTime.IsZero() { firstPacketSendTime = p.SendTime } - if !p.declaredLost { + if p.IsAckEliciting() { 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 { + if p.IsAckEliciting() { h.queueFramesForRetransmission(p) } } @@ -1026,20 +1098,20 @@ func (h *sentPacketHandler) ResetForRetry(now monotime.Time) { if h.logger.Debug() { h.logger.Debugf("\tupdated RTT: %s (σ: %s)", h.rttStats.SmoothedRTT(), h.rttStats.MeanDeviation()) } - if h.tracer != nil && h.tracer.UpdatedMetrics != nil { - h.tracer.UpdatedMetrics(h.rttStats, h.congestion.GetCongestionWindow(), h.bytesInFlight, h.packetsInFlight()) + if h.qlogger != nil { + h.qlogMetricsUpdated() } } h.initialPackets = newPacketNumberSpace(h.initialPackets.pns.Peek(), false) h.appDataPackets = newPacketNumberSpace(h.appDataPackets.pns.Peek(), true) oldAlarm := h.alarm h.alarm = alarmTimer{} - if h.tracer != nil { - if h.tracer.UpdatedPTOCount != nil { - h.tracer.UpdatedPTOCount(0) - } - if !oldAlarm.Time.IsZero() && h.tracer.LossTimerCanceled != nil { - h.tracer.LossTimerCanceled() + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.PTOCountUpdated{PTOCount: 0}) + if !oldAlarm.Time.IsZero() { + h.qlogger.RecordEvent(qlog.LossTimerUpdated{ + Type: qlog.LossTimerUpdateTypeCancelled, + }) } } h.ptoCount = 0 @@ -1051,7 +1123,9 @@ func (h *sentPacketHandler) MigratedPath(now monotime.Time, initialMaxDatagramSi h.appDataPackets.history.DeclareLost(pn) if !p.isPathProbePacket { h.removeFromBytesInFlight(p) - h.queueFramesForRetransmission(p) + if p.IsAckEliciting() { + h.queueFramesForRetransmission(p) + } } } for pn := range h.appDataPackets.history.PathProbes() { @@ -1063,7 +1137,7 @@ func (h *sentPacketHandler) MigratedPath(now monotime.Time, initialMaxDatagramSi h.connStats, initialMaxDatagramSize, true, // use Reno - h.tracer, + h.qlogger, ) h.setLossDetectionTimer(now) } 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 54b74cf745..1500e1cdf7 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 @@ -58,17 +58,10 @@ func (h *sentPacketHistory) SkippedPacket(pn protocol.PacketNumber) { h.skippedPackets = append(h.skippedPackets, pn) } -func (h *sentPacketHistory) SentNonAckElicitingPacket(pn protocol.PacketNumber) { - h.checkSequentialPacketNumberUse(pn) - if len(h.packets) > 0 { - h.packets = append(h.packets, nil) - } -} - -func (h *sentPacketHistory) SentAckElicitingPacket(pn protocol.PacketNumber, p *packet) { +func (h *sentPacketHistory) SentPacket(pn protocol.PacketNumber, p *packet) { h.checkSequentialPacketNumberUse(pn) h.packets = append(h.packets, p) - if p.outstanding() { + if p.Outstanding() { h.numOutstanding++ } } @@ -111,7 +104,7 @@ func (h *sentPacketHistory) FirstOutstanding() (protocol.PacketNumber, *packet) return protocol.InvalidPacketNumber, nil } for i, p := range h.packets { - if p != nil && p.outstanding() { + if p != nil && p.Outstanding() { return h.firstPacketNumber + protocol.PacketNumber(i), p } } @@ -140,6 +133,10 @@ func (h *sentPacketHistory) Len() int { return len(h.packets) } +func (h *sentPacketHistory) NumOutstanding() int { + return h.numOutstanding +} + // 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 { @@ -148,7 +145,7 @@ func (h *sentPacketHistory) Remove(pn protocol.PacketNumber) error { return fmt.Errorf("packet %d not found in sent packet history", pn) } p := h.packets[idx] - if p.outstanding() { + if p.Outstanding() { h.numOutstanding-- if h.numOutstanding < 0 { panic("negative number of outstanding packets") @@ -243,7 +240,7 @@ func (h *sentPacketHistory) DeclareLost(pn protocol.PacketNumber) { return } p := h.packets[idx] - if p.outstanding() { + if p.Outstanding() { h.numOutstanding-- if h.numOutstanding < 0 { panic("negative number of outstanding packets") diff --git a/vendor/github.com/quic-go/quic-go/internal/congestion/bandwidth.go b/vendor/github.com/quic-go/quic-go/internal/congestion/bandwidth.go index 1d03abbb8a..3ad827d2b2 100644 --- a/vendor/github.com/quic-go/quic-go/internal/congestion/bandwidth.go +++ b/vendor/github.com/quic-go/quic-go/internal/congestion/bandwidth.go @@ -1,7 +1,6 @@ package congestion import ( - "math" "time" "github.com/quic-go/quic-go/internal/protocol" @@ -10,8 +9,6 @@ import ( // Bandwidth of a connection type Bandwidth uint64 -const infBandwidth Bandwidth = math.MaxUint64 - const ( // BitsPerSecond is 1 bit per second BitsPerSecond Bandwidth = 1 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 1ae10390ae..e5457ddb81 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 @@ -6,7 +6,8 @@ import ( "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" + "github.com/quic-go/quic-go/qlog" + "github.com/quic-go/quic-go/qlogwriter" ) const ( @@ -56,8 +57,8 @@ type cubicSender struct { maxDatagramSize protocol.ByteCount - lastState logging.CongestionState - tracer *logging.ConnectionTracer + lastState qlog.CongestionState + qlogger qlogwriter.Recorder } var ( @@ -72,7 +73,7 @@ func NewCubicSender( connStats *utils.ConnectionStats, initialMaxDatagramSize protocol.ByteCount, reno bool, - tracer *logging.ConnectionTracer, + qlogger qlogwriter.Recorder, ) *cubicSender { return newCubicSender( clock, @@ -82,7 +83,7 @@ func NewCubicSender( initialMaxDatagramSize, initialCongestionWindow*initialMaxDatagramSize, protocol.MaxCongestionWindowPackets*initialMaxDatagramSize, - tracer, + qlogger, ) } @@ -94,7 +95,7 @@ func newCubicSender( initialMaxDatagramSize, initialCongestionWindow, initialMaxCongestionWindow protocol.ByteCount, - tracer *logging.ConnectionTracer, + qlogger qlogwriter.Recorder, ) *cubicSender { c := &cubicSender{ rttStats: rttStats, @@ -109,13 +110,15 @@ func newCubicSender( cubic: NewCubic(clock), clock: clock, reno: reno, - tracer: tracer, + qlogger: qlogger, maxDatagramSize: initialMaxDatagramSize, } c.pacer = newPacer(c.BandwidthEstimate) - if c.tracer != nil && c.tracer.UpdatedCongestionState != nil { - c.lastState = logging.CongestionStateSlowStart - c.tracer.UpdatedCongestionState(logging.CongestionStateSlowStart) + if c.qlogger != nil { + c.lastState = qlog.CongestionStateSlowStart + c.qlogger.RecordEvent(qlog.CongestionStateUpdated{ + State: qlog.CongestionStateSlowStart, + }) } return c } @@ -173,7 +176,7 @@ func (c *cubicSender) MaybeExitSlowStart() { c.hybridSlowStart.ShouldExitSlowStart(c.rttStats.LatestRTT(), c.rttStats.MinRTT(), c.GetCongestionWindow()/c.maxDatagramSize) { // exit slow start c.slowStartThreshold = c.congestionWindow - c.maybeTraceStateChange(logging.CongestionStateCongestionAvoidance) + c.maybeQlogStateChange(qlog.CongestionStateCongestionAvoidance) } } @@ -203,7 +206,7 @@ func (c *cubicSender) OnCongestionEvent(packetNumber protocol.PacketNumber, lost return } c.lastCutbackExitedSlowstart = c.InSlowStart() - c.maybeTraceStateChange(logging.CongestionStateRecovery) + c.maybeQlogStateChange(qlog.CongestionStateRecovery) if c.reno { c.congestionWindow = protocol.ByteCount(float64(c.congestionWindow) * renoBeta) @@ -232,7 +235,7 @@ func (c *cubicSender) maybeIncreaseCwnd( // the current window. if !c.isCwndLimited(priorInFlight) { c.cubic.OnApplicationLimited() - c.maybeTraceStateChange(logging.CongestionStateApplicationLimited) + c.maybeQlogStateChange(qlog.CongestionStateApplicationLimited) return } if c.congestionWindow >= c.maxCongestionWindow() { @@ -241,11 +244,11 @@ func (c *cubicSender) maybeIncreaseCwnd( if c.InSlowStart() { // TCP slow start, exponential growth, increase by one for each ACK. c.congestionWindow += c.maxDatagramSize - c.maybeTraceStateChange(logging.CongestionStateSlowStart) + c.maybeQlogStateChange(qlog.CongestionStateSlowStart) return } // Congestion avoidance - c.maybeTraceStateChange(logging.CongestionStateCongestionAvoidance) + c.maybeQlogStateChange(qlog.CongestionStateCongestionAvoidance) if c.reno { // Classic Reno congestion avoidance. c.numAckedPackets++ @@ -275,8 +278,8 @@ func (c *cubicSender) isCwndLimited(bytesInFlight protocol.ByteCount) bool { func (c *cubicSender) BandwidthEstimate() Bandwidth { srtt := c.rttStats.SmoothedRTT() if srtt == 0 { - // If we haven't measured an rtt, the bandwidth estimate is unknown. - return infBandwidth + // This should never happen, but if it does, avoid division by zero. + srtt = protocol.TimerGranularity } return BandwidthFromDelta(c.GetCongestionWindow(), srtt) } @@ -306,11 +309,11 @@ func (c *cubicSender) OnConnectionMigration() { c.slowStartThreshold = c.initialMaxCongestionWindow } -func (c *cubicSender) maybeTraceStateChange(new logging.CongestionState) { - if c.tracer == nil || c.tracer.UpdatedCongestionState == nil || new == c.lastState { +func (c *cubicSender) maybeQlogStateChange(new qlog.CongestionState) { + if c.qlogger == nil || new == c.lastState { return } - c.tracer.UpdatedCongestionState(new) + c.qlogger.RecordEvent(qlog.CongestionStateUpdated{State: new}) c.lastState = new } 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 92757eedac..7656f52968 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 @@ -1,6 +1,7 @@ package congestion import ( + "math" "time" "github.com/quic-go/quic-go/internal/monotime" @@ -48,8 +49,13 @@ func (p *pacer) Budget(now monotime.Time) protocol.ByteCount { if p.lastSentTime.IsZero() { return p.maxBurstSize() } - budget := p.budgetAtLastSent + (protocol.ByteCount(p.adjustedBandwidth())*protocol.ByteCount(now.Sub(p.lastSentTime).Nanoseconds()))/1e9 - if budget < 0 { // protect against overflows + delta := now.Sub(p.lastSentTime) + var added protocol.ByteCount + if delta > 0 { + added = p.timeScaledBandwidth(uint64(delta.Nanoseconds())) + } + budget := p.budgetAtLastSent + added + if added > 0 && budget < p.budgetAtLastSent { budget = protocol.MaxByteCount } return min(p.maxBurstSize(), budget) @@ -57,11 +63,30 @@ func (p *pacer) Budget(now monotime.Time) protocol.ByteCount { func (p *pacer) maxBurstSize() protocol.ByteCount { return max( - protocol.ByteCount(uint64((protocol.MinPacingDelay+protocol.TimerGranularity).Nanoseconds())*p.adjustedBandwidth())/1e9, + p.timeScaledBandwidth(uint64((protocol.MinPacingDelay + protocol.TimerGranularity).Nanoseconds())), maxBurstSizePackets*p.maxDatagramSize, ) } +// timeScaledBandwidth calculates the number of bytes that may be sent within +// a given time interval (ns nanoseconds), based on the current bandwidth estimate. +// It caps the scaled value to the maximum allowed burst and handles overflows. +func (p *pacer) timeScaledBandwidth(ns uint64) protocol.ByteCount { + bw := p.adjustedBandwidth() + if bw == 0 { + return 0 + } + const nsPerSecond = 1e9 + maxBurst := maxBurstSizePackets * p.maxDatagramSize + var scaled protocol.ByteCount + if ns > math.MaxUint64/bw { + scaled = maxBurst + } else { + scaled = protocol.ByteCount(bw * ns / nsPerSecond) + } + return scaled +} + // TimeUntilSend returns when the next packet should be sent. // It returns zero if a packet can be sent immediately. func (p *pacer) TimeUntilSend() monotime.Time { diff --git a/vendor/github.com/quic-go/quic-go/internal/handshake/aead.go b/vendor/github.com/quic-go/quic-go/internal/handshake/aead.go index 1baf5d6b0b..ce83ab18c7 100644 --- a/vendor/github.com/quic-go/quic-go/internal/handshake/aead.go +++ b/vendor/github.com/quic-go/quic-go/internal/handshake/aead.go @@ -6,7 +6,7 @@ import ( "github.com/quic-go/quic-go/internal/protocol" ) -func createAEAD(suite *cipherSuite, trafficSecret []byte, v protocol.Version) *xorNonceAEAD { +func createAEAD(suite cipherSuite, trafficSecret []byte, v protocol.Version) *xorNonceAEAD { keyLabel := hkdfLabelKeyV1 ivLabel := hkdfLabelIVV1 if v == protocol.Version2 { diff --git a/vendor/github.com/quic-go/quic-go/internal/handshake/cipher_suite.go b/vendor/github.com/quic-go/quic-go/internal/handshake/cipher_suite.go index d8a381daf0..03fe0dad8e 100644 --- a/vendor/github.com/quic-go/quic-go/internal/handshake/cipher_suite.go +++ b/vendor/github.com/quic-go/quic-go/internal/handshake/cipher_suite.go @@ -23,14 +23,14 @@ type cipherSuite struct { func (s cipherSuite) IVLen() int { return aeadNonceLength } -func getCipherSuite(id uint16) *cipherSuite { +func getCipherSuite(id uint16) cipherSuite { switch id { case tls.TLS_AES_128_GCM_SHA256: - return &cipherSuite{ID: tls.TLS_AES_128_GCM_SHA256, Hash: crypto.SHA256, KeyLen: 16, AEAD: aeadAESGCMTLS13} + return cipherSuite{ID: tls.TLS_AES_128_GCM_SHA256, Hash: crypto.SHA256, KeyLen: 16, AEAD: aeadAESGCMTLS13} case tls.TLS_CHACHA20_POLY1305_SHA256: - return &cipherSuite{ID: tls.TLS_CHACHA20_POLY1305_SHA256, Hash: crypto.SHA256, KeyLen: 32, AEAD: aeadChaCha20Poly1305} + return cipherSuite{ID: tls.TLS_CHACHA20_POLY1305_SHA256, Hash: crypto.SHA256, KeyLen: 32, AEAD: aeadChaCha20Poly1305} case tls.TLS_AES_256_GCM_SHA384: - return &cipherSuite{ID: tls.TLS_AES_256_GCM_SHA384, Hash: crypto.SHA384, KeyLen: 32, AEAD: aeadAESGCMTLS13} + return cipherSuite{ID: tls.TLS_AES_256_GCM_SHA384, Hash: crypto.SHA384, KeyLen: 32, AEAD: aeadAESGCMTLS13} default: panic(fmt.Sprintf("unknown cypher suite: %d", id)) } diff --git a/vendor/github.com/quic-go/quic-go/internal/handshake/crypto_setup.go b/vendor/github.com/quic-go/quic-go/internal/handshake/crypto_setup.go index d3a9734d2f..d481ac675a 100644 --- a/vendor/github.com/quic-go/quic-go/internal/handshake/crypto_setup.go +++ b/vendor/github.com/quic-go/quic-go/internal/handshake/crypto_setup.go @@ -14,7 +14,8 @@ import ( "github.com/quic-go/quic-go/internal/qerr" "github.com/quic-go/quic-go/internal/utils" "github.com/quic-go/quic-go/internal/wire" - "github.com/quic-go/quic-go/logging" + "github.com/quic-go/quic-go/qlog" + "github.com/quic-go/quic-go/qlogwriter" "github.com/quic-go/quic-go/quicvarint" ) @@ -40,8 +41,8 @@ type cryptoSetup struct { rttStats *utils.RTTStats - tracer *logging.ConnectionTracer - logger utils.Logger + qlogger qlogwriter.Recorder + logger utils.Logger perspective protocol.Perspective @@ -72,7 +73,7 @@ func NewCryptoSetupClient( tlsConf *tls.Config, enable0RTT bool, rttStats *utils.RTTStats, - tracer *logging.ConnectionTracer, + qlogger qlogwriter.Recorder, logger utils.Logger, version protocol.Version, ) CryptoSetup { @@ -80,7 +81,7 @@ func NewCryptoSetupClient( connID, tp, rttStats, - tracer, + qlogger, logger, protocol.PerspectiveClient, version, @@ -108,7 +109,7 @@ func NewCryptoSetupServer( tlsConf *tls.Config, allow0RTT bool, rttStats *utils.RTTStats, - tracer *logging.ConnectionTracer, + qlogger qlogwriter.Recorder, logger utils.Logger, version protocol.Version, ) CryptoSetup { @@ -116,7 +117,7 @@ func NewCryptoSetupServer( connID, tp, rttStats, - tracer, + qlogger, logger, protocol.PerspectiveServer, version, @@ -137,24 +138,30 @@ func newCryptoSetup( connID protocol.ConnectionID, tp *wire.TransportParameters, rttStats *utils.RTTStats, - tracer *logging.ConnectionTracer, + qlogger qlogwriter.Recorder, logger utils.Logger, perspective protocol.Perspective, version protocol.Version, ) *cryptoSetup { initialSealer, initialOpener := NewInitialAEAD(connID, perspective, version) - if tracer != nil && tracer.UpdatedKeyFromTLS != nil { - tracer.UpdatedKeyFromTLS(protocol.EncryptionInitial, protocol.PerspectiveClient) - tracer.UpdatedKeyFromTLS(protocol.EncryptionInitial, protocol.PerspectiveServer) + if qlogger != nil { + qlogger.RecordEvent(qlog.KeyUpdated{ + Trigger: qlog.KeyUpdateTLS, + KeyType: encLevelToKeyType(protocol.EncryptionInitial, protocol.PerspectiveClient), + }) + qlogger.RecordEvent(qlog.KeyUpdated{ + Trigger: qlog.KeyUpdateTLS, + KeyType: encLevelToKeyType(protocol.EncryptionInitial, protocol.PerspectiveServer), + }) } return &cryptoSetup{ initialSealer: initialSealer, initialOpener: initialOpener, - aead: newUpdatableAEAD(rttStats, tracer, logger, version), + aead: newUpdatableAEAD(rttStats, qlogger, logger, version), events: make([]Event, 0, 16), ourParams: tp, rttStats: rttStats, - tracer: tracer, + qlogger: qlogger, logger: logger, perspective: perspective, version: version, @@ -165,9 +172,15 @@ func (h *cryptoSetup) ChangeConnectionID(id protocol.ConnectionID) { initialSealer, initialOpener := NewInitialAEAD(id, h.perspective, h.version) h.initialSealer = initialSealer h.initialOpener = initialOpener - if h.tracer != nil && h.tracer.UpdatedKeyFromTLS != nil { - h.tracer.UpdatedKeyFromTLS(protocol.EncryptionInitial, protocol.PerspectiveClient) - h.tracer.UpdatedKeyFromTLS(protocol.EncryptionInitial, protocol.PerspectiveServer) + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.KeyUpdated{ + Trigger: qlog.KeyUpdateTLS, + KeyType: encLevelToKeyType(protocol.EncryptionInitial, protocol.PerspectiveClient), + }) + h.qlogger.RecordEvent(qlog.KeyUpdated{ + Trigger: qlog.KeyUpdateTLS, + KeyType: encLevelToKeyType(protocol.EncryptionInitial, protocol.PerspectiveServer), + }) } } @@ -374,19 +387,29 @@ func (h *cryptoSetup) GetSessionTicket() ([]byte, error) { // We can't check h.tlsConfig here, since the actual config might have been obtained from // the GetConfigForClient callback. // See https://github.com/golang/go/issues/62032. - // Once that issue is resolved, this error assertion can be removed. + // This error assertion can be removed once we drop support for Go 1.25. if strings.Contains(err.Error(), "session ticket keys unavailable") { return nil, nil } return nil, err } - ev := h.conn.NextEvent() - if ev.Kind != tls.QUICWriteData || ev.Level != tls.QUICEncryptionLevelApplication { - panic("crypto/tls bug: where's my session ticket?") - } - ticket := ev.Data - if ev := h.conn.NextEvent(); ev.Kind != tls.QUICNoEvent { - panic("crypto/tls bug: why more than one ticket?") + // If session tickets are disabled, NextEvent will immediately return QUICNoEvent, + // and we will return a nil ticket. + var ticket []byte + for { + ev := h.conn.NextEvent() + if ev.Kind == tls.QUICNoEvent { + break + } + if ev.Kind == tls.QUICWriteData && ev.Level == tls.QUICEncryptionLevelApplication { + if ticket != nil { + h.logger.Errorf("unexpected multiple session tickets") + continue + } + ticket = ev.Data + } else { + h.logger.Errorf("unexpected event: %v", ev.Kind) + } } return ticket, nil } @@ -462,8 +485,11 @@ func (h *cryptoSetup) setReadKey(el tls.QUICEncryptionLevel, suiteID uint16, tra panic("unexpected read encryption level") } h.events = append(h.events, Event{Kind: EventReceivedReadKeys}) - if h.tracer != nil && h.tracer.UpdatedKeyFromTLS != nil { - h.tracer.UpdatedKeyFromTLS(protocol.FromTLSEncryptionLevel(el), h.perspective.Opposite()) + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.KeyUpdated{ + Trigger: qlog.KeyUpdateTLS, + KeyType: encLevelToKeyType(protocol.FromTLSEncryptionLevel(el), h.perspective.Opposite()), + }) } } @@ -482,8 +508,11 @@ func (h *cryptoSetup) setWriteKey(el tls.QUICEncryptionLevel, suiteID uint16, tr if h.logger.Debug() { h.logger.Debugf("Installed 0-RTT Write keys (using %s)", tls.CipherSuiteName(suite.ID)) } - if h.tracer != nil && h.tracer.UpdatedKeyFromTLS != nil { - h.tracer.UpdatedKeyFromTLS(protocol.Encryption0RTT, h.perspective) + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.KeyUpdated{ + Trigger: qlog.KeyUpdateTLS, + KeyType: encLevelToKeyType(protocol.Encryption0RTT, h.perspective), + }) } // don't set used0RTT here. 0-RTT might still get rejected. return @@ -506,15 +535,18 @@ func (h *cryptoSetup) setWriteKey(el tls.QUICEncryptionLevel, suiteID uint16, tr h.used0RTT.Store(true) h.zeroRTTSealer = nil h.logger.Debugf("Dropping 0-RTT keys.") - if h.tracer != nil && h.tracer.DroppedEncryptionLevel != nil { - h.tracer.DroppedEncryptionLevel(protocol.Encryption0RTT) + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeClient0RTT}) } } default: panic("unexpected write encryption level") } - if h.tracer != nil && h.tracer.UpdatedKeyFromTLS != nil { - h.tracer.UpdatedKeyFromTLS(protocol.FromTLSEncryptionLevel(el), h.perspective) + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.KeyUpdated{ + Trigger: qlog.KeyUpdateTLS, + KeyType: encLevelToKeyType(protocol.FromTLSEncryptionLevel(el), h.perspective), + }) } } @@ -539,6 +571,10 @@ func (h *cryptoSetup) DiscardInitialKeys() { h.initialSealer = nil if dropped { h.logger.Debugf("Dropping Initial keys.") + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeClientInitial}) + h.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeServerInitial}) + } } } @@ -558,6 +594,10 @@ func (h *cryptoSetup) SetHandshakeConfirmed() { } if dropped { h.logger.Debugf("Dropping Handshake keys.") + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeClientHandshake}) + h.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeServerHandshake}) + } } } @@ -625,8 +665,8 @@ func (h *cryptoSetup) Get1RTTOpener() (ShortHeaderOpener, error) { if h.zeroRTTOpener != nil && time.Since(h.handshakeCompleteTime) > 3*h.rttStats.PTO(true) { h.zeroRTTOpener = nil h.logger.Debugf("Dropping 0-RTT keys.") - if h.tracer != nil && h.tracer.DroppedEncryptionLevel != nil { - h.tracer.DroppedEncryptionLevel(protocol.Encryption0RTT) + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeClient0RTT}) } } @@ -649,3 +689,32 @@ func wrapError(err error) error { } return &qerr.TransportError{ErrorCode: qerr.InternalError, ErrorMessage: err.Error()} } + +func encLevelToKeyType(encLevel protocol.EncryptionLevel, pers protocol.Perspective) qlog.KeyType { + if pers == protocol.PerspectiveServer { + switch encLevel { + case protocol.EncryptionInitial: + return qlog.KeyTypeServerInitial + case protocol.EncryptionHandshake: + return qlog.KeyTypeServerHandshake + case protocol.Encryption0RTT: + return qlog.KeyTypeServer0RTT + case protocol.Encryption1RTT: + return qlog.KeyTypeServer1RTT + default: + return "" + } + } + switch encLevel { + case protocol.EncryptionInitial: + return qlog.KeyTypeClientInitial + case protocol.EncryptionHandshake: + return qlog.KeyTypeClientHandshake + case protocol.Encryption0RTT: + return qlog.KeyTypeClient0RTT + case protocol.Encryption1RTT: + return qlog.KeyTypeClient1RTT + default: + return "" + } +} diff --git a/vendor/github.com/quic-go/quic-go/internal/handshake/header_protector.go b/vendor/github.com/quic-go/quic-go/internal/handshake/header_protector.go index 2c5ee42f12..93c3cd9809 100644 --- a/vendor/github.com/quic-go/quic-go/internal/handshake/header_protector.go +++ b/vendor/github.com/quic-go/quic-go/internal/handshake/header_protector.go @@ -24,7 +24,7 @@ func hkdfHeaderProtectionLabel(v protocol.Version) string { return "quic hp" } -func newHeaderProtector(suite *cipherSuite, trafficSecret []byte, isLongHeader bool, v protocol.Version) headerProtector { +func newHeaderProtector(suite cipherSuite, trafficSecret []byte, isLongHeader bool, v protocol.Version) headerProtector { hkdfLabel := hkdfHeaderProtectionLabel(v) switch suite.ID { case tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384: @@ -44,7 +44,7 @@ type aesHeaderProtector struct { var _ headerProtector = &aesHeaderProtector{} -func newAESHeaderProtector(suite *cipherSuite, trafficSecret []byte, isLongHeader bool, hkdfLabel string) headerProtector { +func newAESHeaderProtector(suite cipherSuite, trafficSecret []byte, isLongHeader bool, hkdfLabel string) headerProtector { hpKey := hkdfExpandLabel(suite.Hash, trafficSecret, []byte{}, hkdfLabel, suite.KeyLen) block, err := aes.NewCipher(hpKey) if err != nil { @@ -88,7 +88,7 @@ type chachaHeaderProtector struct { var _ headerProtector = &chachaHeaderProtector{} -func newChaChaHeaderProtector(suite *cipherSuite, trafficSecret []byte, isLongHeader bool, hkdfLabel string) headerProtector { +func newChaChaHeaderProtector(suite cipherSuite, trafficSecret []byte, isLongHeader bool, hkdfLabel string) headerProtector { hpKey := hkdfExpandLabel(suite.Hash, trafficSecret, []byte{}, hkdfLabel, suite.KeyLen) p := &chachaHeaderProtector{ 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 ef92ab1c4e..f88f76ad8b 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 @@ -12,7 +12,8 @@ import ( "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/logging" + "github.com/quic-go/quic-go/qlog" + "github.com/quic-go/quic-go/qlogwriter" ) var keyUpdateInterval atomic.Uint64 @@ -31,7 +32,7 @@ func SetKeyUpdateInterval(v uint64) (reset func()) { var FirstKeyUpdateInterval uint64 = 100 type updatableAEAD struct { - suite *cipherSuite + suite cipherSuite keyPhase protocol.KeyPhase largestAcked protocol.PacketNumber @@ -65,7 +66,7 @@ type updatableAEAD struct { rttStats *utils.RTTStats - tracer *logging.ConnectionTracer + qlogger qlogwriter.Recorder logger utils.Logger version protocol.Version @@ -78,14 +79,14 @@ var ( _ ShortHeaderSealer = &updatableAEAD{} ) -func newUpdatableAEAD(rttStats *utils.RTTStats, tracer *logging.ConnectionTracer, logger utils.Logger, version protocol.Version) *updatableAEAD { +func newUpdatableAEAD(rttStats *utils.RTTStats, qlogger qlogwriter.Recorder, logger utils.Logger, version protocol.Version) *updatableAEAD { return &updatableAEAD{ firstPacketNumber: protocol.InvalidPacketNumber, largestAcked: protocol.InvalidPacketNumber, firstRcvdWithCurrentKey: protocol.InvalidPacketNumber, firstSentWithCurrentKey: protocol.InvalidPacketNumber, rttStats: rttStats, - tracer: tracer, + qlogger: qlogger, logger: logger, version: version, } @@ -94,8 +95,15 @@ func newUpdatableAEAD(rttStats *utils.RTTStats, tracer *logging.ConnectionTracer func (a *updatableAEAD) rollKeys() { if a.prevRcvAEAD != nil { a.logger.Debugf("Dropping key phase %d ahead of scheduled time. Drop time was: %s", a.keyPhase-1, a.prevRcvAEADExpiry) - if a.tracer != nil && a.tracer.DroppedKey != nil { - a.tracer.DroppedKey(a.keyPhase - 1) + if a.qlogger != nil { + a.qlogger.RecordEvent(qlog.KeyDiscarded{ + KeyType: qlog.KeyTypeClient1RTT, + KeyPhase: a.keyPhase - 1, + }) + a.qlogger.RecordEvent(qlog.KeyDiscarded{ + KeyType: qlog.KeyTypeServer1RTT, + KeyPhase: a.keyPhase - 1, + }) } a.prevRcvAEADExpiry = 0 } @@ -128,10 +136,10 @@ func (a *updatableAEAD) getNextTrafficSecret(hash crypto.Hash, ts []byte) []byte // SetReadKey sets the read key. // For the client, this function is called before SetWriteKey. // For the server, this function is called after SetWriteKey. -func (a *updatableAEAD) SetReadKey(suite *cipherSuite, trafficSecret []byte) { +func (a *updatableAEAD) SetReadKey(suite cipherSuite, trafficSecret []byte) { a.rcvAEAD = createAEAD(suite, trafficSecret, a.version) a.headerDecrypter = newHeaderProtector(suite, trafficSecret, false, a.version) - if a.suite == nil { + if a.suite.ID == 0 { // suite is not set yet a.setAEADParameters(a.rcvAEAD, suite) } @@ -142,10 +150,10 @@ func (a *updatableAEAD) SetReadKey(suite *cipherSuite, trafficSecret []byte) { // SetWriteKey sets the write key. // For the client, this function is called after SetReadKey. // For the server, this function is called before SetReadKey. -func (a *updatableAEAD) SetWriteKey(suite *cipherSuite, trafficSecret []byte) { +func (a *updatableAEAD) SetWriteKey(suite cipherSuite, trafficSecret []byte) { a.sendAEAD = createAEAD(suite, trafficSecret, a.version) a.headerEncrypter = newHeaderProtector(suite, trafficSecret, false, a.version) - if a.suite == nil { + if a.suite.ID == 0 { // suite is not set yet a.setAEADParameters(a.sendAEAD, suite) } @@ -153,7 +161,7 @@ func (a *updatableAEAD) SetWriteKey(suite *cipherSuite, trafficSecret []byte) { a.nextSendAEAD = createAEAD(suite, a.nextSendTrafficSecret, a.version) } -func (a *updatableAEAD) setAEADParameters(aead cipher.AEAD, suite *cipherSuite) { +func (a *updatableAEAD) setAEADParameters(aead cipher.AEAD, suite cipherSuite) { a.nonceBuf = make([]byte, aead.NonceSize()) a.aeadOverhead = aead.Overhead() a.suite = suite @@ -190,8 +198,15 @@ func (a *updatableAEAD) open(dst, src []byte, rcvTime monotime.Time, pn protocol a.prevRcvAEAD = nil a.logger.Debugf("Dropping key phase %d", a.keyPhase-1) a.prevRcvAEADExpiry = 0 - if a.tracer != nil && a.tracer.DroppedKey != nil { - a.tracer.DroppedKey(a.keyPhase - 1) + if a.qlogger != nil { + a.qlogger.RecordEvent(qlog.KeyDiscarded{ + KeyType: qlog.KeyTypeClient1RTT, + KeyPhase: a.keyPhase - 1, + }) + a.qlogger.RecordEvent(qlog.KeyDiscarded{ + KeyType: qlog.KeyTypeServer1RTT, + KeyPhase: a.keyPhase - 1, + }) } } binary.BigEndian.PutUint64(a.nonceBuf[len(a.nonceBuf)-8:], uint64(pn)) @@ -224,8 +239,17 @@ func (a *updatableAEAD) open(dst, src []byte, rcvTime monotime.Time, pn protocol // The peer initiated this key update. It's safe to drop the keys for the previous generation now. // Start a timer to drop the previous key generation. a.startKeyDropTimer(rcvTime) - if a.tracer != nil && a.tracer.UpdatedKey != nil { - a.tracer.UpdatedKey(a.keyPhase, true) + if a.qlogger != nil { + a.qlogger.RecordEvent(qlog.KeyUpdated{ + Trigger: qlog.KeyUpdateRemote, + KeyType: qlog.KeyTypeClient1RTT, + KeyPhase: a.keyPhase, + }) + a.qlogger.RecordEvent(qlog.KeyUpdated{ + Trigger: qlog.KeyUpdateRemote, + KeyType: qlog.KeyTypeServer1RTT, + KeyPhase: a.keyPhase, + }) } a.firstRcvdWithCurrentKey = pn return dec, err @@ -315,9 +339,17 @@ func (a *updatableAEAD) shouldInitiateKeyUpdate() bool { func (a *updatableAEAD) KeyPhase() protocol.KeyPhaseBit { if a.shouldInitiateKeyUpdate() { a.rollKeys() - a.logger.Debugf("Initiating key update to key phase %d", a.keyPhase) - if a.tracer != nil && a.tracer.UpdatedKey != nil { - a.tracer.UpdatedKey(a.keyPhase, false) + if a.qlogger != nil { + a.qlogger.RecordEvent(qlog.KeyUpdated{ + Trigger: qlog.KeyUpdateLocal, + KeyType: qlog.KeyTypeClient1RTT, + KeyPhase: a.keyPhase, + }) + a.qlogger.RecordEvent(qlog.KeyUpdated{ + Trigger: qlog.KeyUpdateLocal, + KeyType: qlog.KeyTypeServer1RTT, + KeyPhase: a.keyPhase, + }) } } return a.keyPhase.Bit() diff --git a/vendor/github.com/quic-go/quic-go/internal/protocol/params.go b/vendor/github.com/quic-go/quic-go/internal/protocol/params.go index fe86317fbb..0861d57fc4 100644 --- a/vendor/github.com/quic-go/quic-go/internal/protocol/params.go +++ b/vendor/github.com/quic-go/quic-go/internal/protocol/params.go @@ -112,16 +112,13 @@ const MinStreamFrameSize ByteCount = 128 // we send after the handshake completes. const MaxPostHandshakeCryptoFrameSize = 1000 -// MaxAckFrameSize is the maximum size for an ACK frame that we write -// Due to the varint encoding, ACK frames can grow (almost) indefinitely large. -// The MaxAckFrameSize should be large enough to encode many ACK range, -// but must ensure that a maximum size ACK frame fits into one packet. -const MaxAckFrameSize ByteCount = 1000 - // MaxNumAckRanges is the maximum number of ACK ranges that we send in an ACK frame. // It also serves as a limit for the packet history. // If at any point we keep track of more ranges, old ranges are discarded. -const MaxNumAckRanges = 32 +// +// This value also guarantees that ACK Range Count value in the ACK frame can be encoded +// in a single byte varint. +const MaxNumAckRanges = 64 // MinPacingDelay is the minimum duration that is used for packet pacing // If the packet packing frequency is higher, multiple packets might be sent at once. 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 61cbef5c3d..2753130732 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 @@ -12,10 +12,11 @@ const ( oneMinusAlpha = 1 - rttAlpha rttBeta = 0.25 oneMinusBeta = 1 - rttBeta - // The default RTT used before an RTT sample is taken. - defaultInitialRTT = 100 * time.Millisecond ) +// The default RTT used before an RTT sample is taken +const DefaultInitialRTT = 100 * time.Millisecond + // RTTStats provides round-trip statistics type RTTStats struct { hasMeasurement bool @@ -28,6 +29,14 @@ type RTTStats struct { maxAckDelay atomic.Int64 // nanoseconds } +func NewRTTStats() *RTTStats { + var rttStats RTTStats + rttStats.minRTT.Store(DefaultInitialRTT.Nanoseconds()) + rttStats.latestRTT.Store(DefaultInitialRTT.Nanoseconds()) + rttStats.smoothedRTT.Store(DefaultInitialRTT.Nanoseconds()) + return &rttStats +} + // MinRTT Returns the minRTT for the entire connection. // May return Zero if no valid updates have occurred. func (r *RTTStats) MinRTT() time.Duration { @@ -58,8 +67,8 @@ func (r *RTTStats) MaxAckDelay() time.Duration { // PTO gets the probe timeout duration. func (r *RTTStats) PTO(includeMaxAckDelay bool) time.Duration { - if r.SmoothedRTT() == 0 { - return 2 * defaultInitialRTT + if !r.hasMeasurement { + return 2 * DefaultInitialRTT } pto := r.SmoothedRTT() + max(4*r.MeanDeviation(), protocol.TimerGranularity) if includeMaxAckDelay { @@ -79,9 +88,9 @@ func (r *RTTStats) UpdateRTT(sendDelta, ackDelay time.Duration) { // the client may cause a high ackDelay to result in underestimation of the // r.minRTT. minRTT := time.Duration(r.minRTT.Load()) - if minRTT == 0 || minRTT > sendDelta { + if !r.hasMeasurement || minRTT > sendDelta { minRTT = sendDelta - r.minRTT.Store(int64(sendDelta)) + r.minRTT.Store(sendDelta.Nanoseconds()) } // Correct for ackDelay if information received from the peer results in a @@ -91,21 +100,25 @@ func (r *RTTStats) UpdateRTT(sendDelta, ackDelay time.Duration) { if sample-minRTT >= ackDelay { sample -= ackDelay } - r.latestRTT.Store(int64(sample)) + r.latestRTT.Store(sample.Nanoseconds()) // First time call. if !r.hasMeasurement { r.hasMeasurement = true - r.smoothedRTT.Store(int64(sample)) - r.meanDeviation.Store(int64(sample / 2)) + r.smoothedRTT.Store(sample.Nanoseconds()) + r.meanDeviation.Store(sample.Nanoseconds() / 2) } else { 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)) + r.meanDeviation.Store(meanDev.Nanoseconds()) + r.smoothedRTT.Store(newSmoothedRTT.Nanoseconds()) } } +func (r *RTTStats) HasMeasurement() bool { + return r.hasMeasurement +} + // SetMaxAckDelay sets the max_ack_delay func (r *RTTStats) SetMaxAckDelay(mad time.Duration) { r.maxAckDelay.Store(int64(mad)) @@ -127,9 +140,9 @@ func (r *RTTStats) SetInitialRTT(t time.Duration) { func (r *RTTStats) ResetForPathMigration() { r.hasMeasurement = false - r.minRTT.Store(0) - r.latestRTT.Store(0) - r.smoothedRTT.Store(0) + r.minRTT.Store(DefaultInitialRTT.Nanoseconds()) + r.latestRTT.Store(DefaultInitialRTT.Nanoseconds()) + r.smoothedRTT.Store(DefaultInitialRTT.Nanoseconds()) r.meanDeviation.Store(0) // max_ack_delay remains valid } diff --git a/vendor/github.com/quic-go/quic-go/internal/wire/ack_frame.go b/vendor/github.com/quic-go/quic-go/internal/wire/ack_frame.go index 68bebfa791..191e15307c 100644 --- a/vendor/github.com/quic-go/quic-go/internal/wire/ack_frame.go +++ b/vendor/github.com/quic-go/quic-go/internal/wire/ack_frame.go @@ -64,7 +64,7 @@ func parseAckFrame(frame *AckFrame, b []byte, typ FrameType, ackDelayExponent ui frame.AckRanges = append(frame.AckRanges, AckRange{Smallest: smallest, Largest: largestAcked}) // read all the other ACK ranges - for i := uint64(0); i < numBlocks; i++ { + for range numBlocks { g, l, err := quicvarint.Parse(b) if err != nil { return 0, replaceUnexpectedEOF(err) @@ -129,7 +129,7 @@ func (f *AckFrame) Append(b []byte, _ protocol.Version) ([]byte, error) { b = quicvarint.Append(b, uint64(f.LargestAcked())) b = quicvarint.Append(b, encodeAckDelay(f.DelayTime)) - numRanges := f.numEncodableAckRanges() + numRanges := min(len(f.AckRanges), protocol.MaxNumAckRanges) b = quicvarint.Append(b, uint64(numRanges-1)) // write the first range @@ -154,46 +154,69 @@ func (f *AckFrame) Append(b []byte, _ protocol.Version) ([]byte, error) { // Length of a written frame func (f *AckFrame) Length(_ protocol.Version) protocol.ByteCount { largestAcked := f.AckRanges[0].Largest - numRanges := f.numEncodableAckRanges() - length := 1 + quicvarint.Len(uint64(largestAcked)) + quicvarint.Len(encodeAckDelay(f.DelayTime)) + // The number of ACK ranges is limited to 64, which guarantees that the + // ACK Range Count value can be encoded in a single byte varint. + length := 1 + quicvarint.Len(uint64(largestAcked)) + quicvarint.Len(encodeAckDelay(f.DelayTime)) + 1 - length += quicvarint.Len(uint64(numRanges - 1)) lowestInFirstRange := f.AckRanges[0].Smallest length += quicvarint.Len(uint64(largestAcked - lowestInFirstRange)) - for i := 1; i < numRanges; i++ { + for i := 1; i < min(len(f.AckRanges), protocol.MaxNumAckRanges); i++ { gap, len := f.encodeAckRange(i) length += quicvarint.Len(gap) length += quicvarint.Len(len) } if f.ECT0 > 0 || f.ECT1 > 0 || f.ECNCE > 0 { - length += quicvarint.Len(f.ECT0) - length += quicvarint.Len(f.ECT1) - length += quicvarint.Len(f.ECNCE) + length += quicvarint.Len(f.ECT0) + quicvarint.Len(f.ECT1) + quicvarint.Len(f.ECNCE) } return protocol.ByteCount(length) } +// Truncate truncates the ACK frame to fit into maxSize, +// and to at most 64 ACK ranges. +// maxSize must be large enough to fit at least one ACK range. +func (f *AckFrame) Truncate(maxSize protocol.ByteCount, _ protocol.Version) { + f.AckRanges = f.AckRanges[:f.numEncodableAckRanges(maxSize)] +} + // gets the number of ACK ranges that can be encoded -// such that the resulting frame is smaller than the maximum ACK frame size -func (f *AckFrame) numEncodableAckRanges() int { - length := 1 + quicvarint.Len(uint64(f.LargestAcked())) + quicvarint.Len(encodeAckDelay(f.DelayTime)) - length += 2 // assume that the number of ranges will consume 2 bytes - for i := 1; i < len(f.AckRanges); i++ { - gap, len := f.encodeAckRange(i) - rangeLen := quicvarint.Len(gap) + quicvarint.Len(len) - if protocol.ByteCount(length+rangeLen) > protocol.MaxAckFrameSize { - // Writing range i would exceed the MaxAckFrameSize. - // So encode one range less than that. - return i - 1 +// such that the resulting frame is smaller than maxSize +func (f *AckFrame) numEncodableAckRanges(maxSize protocol.ByteCount) int { + // Fast path: Most ACK frames are relatively small, and we don't need to calculate the exact length. + // We just assume the worst case scenario: every varint is encoded to 8 bytes. + // If the result is still smaller than the maximum ACK frame size, the actual ACK frame will definitely fit. + length := 1 + 8 /* largest acked */ + 8 /* delay */ + 1 /* ack range count */ + 8 /* first range */ + if f.ECT0 > 0 || f.ECT1 > 0 || f.ECNCE > 0 { + length += 8 + 8 + 8 + } + numRanges := min(len(f.AckRanges), protocol.MaxNumAckRanges) + length += 2 * 8 * (numRanges - 1) + if protocol.ByteCount(length) <= maxSize { + return numRanges + } + + // Slow path: Calculate the exact length of the ACK frame. + length = 1 + quicvarint.Len(uint64(f.LargestAcked())) + quicvarint.Len(encodeAckDelay(f.DelayTime)) + 1 + _, firstRange := f.encodeAckRange(0) + length += quicvarint.Len(firstRange) + if f.ECT0 > 0 || f.ECT1 > 0 || f.ECNCE > 0 { + length += quicvarint.Len(f.ECT0) + quicvarint.Len(f.ECT1) + quicvarint.Len(f.ECNCE) + } + for i := 1; i < numRanges; i++ { + gap, l := f.encodeAckRange(i) + rangeLen := quicvarint.Len(gap) + quicvarint.Len(l) + if protocol.ByteCount(length+rangeLen) > maxSize { + // Writing range i would exceed the maximum size, + // so encode one range less than that. + return i } length += rangeLen } - return len(f.AckRanges) + return numRanges } -func (f *AckFrame) encodeAckRange(i int) (uint64 /* gap */, uint64 /* length */) { +func (f *AckFrame) encodeAckRange(i int) (gap, length uint64) { if i == 0 { return 0, uint64(f.AckRanges[0].Largest - f.AckRanges[0].Smallest) } @@ -218,7 +241,7 @@ func (f *AckFrame) validateAckRanges() bool { } } - // check the consistency for ACK with multiple NACK ranges + // check the consistency for ACK with multiple ACK ranges for i, ackRange := range f.AckRanges { if i == 0 { continue 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 deleted file mode 100644 index d6c2b48dc7..0000000000 --- a/vendor/github.com/quic-go/quic-go/logging/connection_tracer.go +++ /dev/null @@ -1,45 +0,0 @@ -package logging - -import ( - "net" - "time" -) - -//go:generate go run generate_multiplexer.go ConnectionTracer connection_tracer.go multiplexer.tmpl connection_tracer_multiplexer.go - -// A ConnectionTracer records events. -type ConnectionTracer struct { - StartedConnection func(local, remote net.Addr, srcConnID, destConnID ConnectionID) - NegotiatedVersion func(chosen Version, clientVersions, serverVersions []Version) - ClosedConnection func(err error) - SentTransportParameters func(parameters *TransportParameters) - ReceivedTransportParameters func(parameters *TransportParameters) - RestoredTransportParameters func(parameters *TransportParameters) // for 0-RTT - SentLongHeaderPacket func(hdr *ExtendedHeader, size ByteCount, ecn ECN, ack *AckFrame, frames []Frame) - SentShortHeaderPacket func(hdr *ShortHeader, size ByteCount, ecn ECN, ack *AckFrame, frames []Frame) - ReceivedVersionNegotiationPacket func(dest, src ArbitraryLenConnectionID, versions []Version) - ReceivedRetry func(hdr *Header) - ReceivedLongHeaderPacket func(hdr *ExtendedHeader, size ByteCount, ecn ECN, frames []Frame) - ReceivedShortHeaderPacket func(hdr *ShortHeader, size ByteCount, ecn ECN, frames []Frame) - BufferedPacket func(packetType PacketType, size ByteCount) - DroppedPacket func(packetType PacketType, pn PacketNumber, size ByteCount, reason PacketDropReason) - 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) - UpdatedKeyFromTLS func(encLevel EncryptionLevel, p Perspective) - UpdatedKey func(keyPhase KeyPhase, remote bool) - DroppedEncryptionLevel func(encLevel EncryptionLevel) - DroppedKey func(keyPhase KeyPhase) - SetLossTimer func(timerType TimerType, encLevel EncryptionLevel, time time.Time) - LossTimerExpired func(timerType TimerType, encLevel EncryptionLevel) - LossTimerCanceled func() - ECNStateUpdated func(state ECNState, trigger ECNStateTrigger) - ChoseALPN func(protocol string) - // Close is called when the connection is closed. - Close func() - Debug func(name, msg string) -} 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 deleted file mode 100644 index 48ac1d38f4..0000000000 --- a/vendor/github.com/quic-go/quic-go/logging/connection_tracer_multiplexer.go +++ /dev/null @@ -1,243 +0,0 @@ -// Code generated by generate_multiplexer.go; DO NOT EDIT. - -package logging - -import ( - "net" - "time" -) - -func NewMultiplexedConnectionTracer(tracers ...*ConnectionTracer) *ConnectionTracer { - if len(tracers) == 0 { - return nil - } - if len(tracers) == 1 { - return tracers[0] - } - return &ConnectionTracer{ - StartedConnection: func(local net.Addr, remote net.Addr, srcConnID ConnectionID, destConnID ConnectionID) { - for _, t := range tracers { - if t.StartedConnection != nil { - t.StartedConnection(local, remote, srcConnID, destConnID) - } - } - }, - NegotiatedVersion: func(chosen Version, clientVersions []Version, serverVersions []Version) { - for _, t := range tracers { - if t.NegotiatedVersion != nil { - t.NegotiatedVersion(chosen, clientVersions, serverVersions) - } - } - }, - ClosedConnection: func(err error) { - for _, t := range tracers { - if t.ClosedConnection != nil { - t.ClosedConnection(err) - } - } - }, - SentTransportParameters: func(parameters *TransportParameters) { - for _, t := range tracers { - if t.SentTransportParameters != nil { - t.SentTransportParameters(parameters) - } - } - }, - ReceivedTransportParameters: func(parameters *TransportParameters) { - for _, t := range tracers { - if t.ReceivedTransportParameters != nil { - t.ReceivedTransportParameters(parameters) - } - } - }, - RestoredTransportParameters: func(parameters *TransportParameters) { - for _, t := range tracers { - if t.RestoredTransportParameters != nil { - t.RestoredTransportParameters(parameters) - } - } - }, - SentLongHeaderPacket: func(hdr *ExtendedHeader, size ByteCount, ecn ECN, ack *AckFrame, frames []Frame) { - for _, t := range tracers { - if t.SentLongHeaderPacket != nil { - t.SentLongHeaderPacket(hdr, size, ecn, ack, frames) - } - } - }, - SentShortHeaderPacket: func(hdr *ShortHeader, size ByteCount, ecn ECN, ack *AckFrame, frames []Frame) { - for _, t := range tracers { - if t.SentShortHeaderPacket != nil { - t.SentShortHeaderPacket(hdr, size, ecn, ack, frames) - } - } - }, - ReceivedVersionNegotiationPacket: func(dest ArbitraryLenConnectionID, src ArbitraryLenConnectionID, versions []Version) { - for _, t := range tracers { - if t.ReceivedVersionNegotiationPacket != nil { - t.ReceivedVersionNegotiationPacket(dest, src, versions) - } - } - }, - ReceivedRetry: func(hdr *Header) { - for _, t := range tracers { - if t.ReceivedRetry != nil { - t.ReceivedRetry(hdr) - } - } - }, - ReceivedLongHeaderPacket: func(hdr *ExtendedHeader, size ByteCount, ecn ECN, frames []Frame) { - for _, t := range tracers { - if t.ReceivedLongHeaderPacket != nil { - t.ReceivedLongHeaderPacket(hdr, size, ecn, frames) - } - } - }, - ReceivedShortHeaderPacket: func(hdr *ShortHeader, size ByteCount, ecn ECN, frames []Frame) { - for _, t := range tracers { - if t.ReceivedShortHeaderPacket != nil { - t.ReceivedShortHeaderPacket(hdr, size, ecn, frames) - } - } - }, - BufferedPacket: func(packetType PacketType, size ByteCount) { - for _, t := range tracers { - if t.BufferedPacket != nil { - t.BufferedPacket(packetType, size) - } - } - }, - DroppedPacket: func(packetType PacketType, pn PacketNumber, size ByteCount, reason PacketDropReason) { - for _, t := range tracers { - if t.DroppedPacket != nil { - t.DroppedPacket(packetType, pn, size, reason) - } - } - }, - UpdatedMetrics: func(rttStats *RTTStats, cwnd ByteCount, bytesInFlight ByteCount, packetsInFlight int) { - for _, t := range tracers { - if t.UpdatedMetrics != nil { - t.UpdatedMetrics(rttStats, cwnd, bytesInFlight, packetsInFlight) - } - } - }, - AcknowledgedPacket: func(encLevel EncryptionLevel, pn PacketNumber) { - for _, t := range tracers { - if t.AcknowledgedPacket != nil { - t.AcknowledgedPacket(encLevel, pn) - } - } - }, - LostPacket: func(encLevel EncryptionLevel, pn PacketNumber, reason PacketLossReason) { - for _, t := range tracers { - if t.LostPacket != nil { - t.LostPacket(encLevel, pn, reason) - } - } - }, - 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 { - t.UpdatedMTU(mtu, done) - } - } - }, - UpdatedCongestionState: func(state CongestionState) { - for _, t := range tracers { - if t.UpdatedCongestionState != nil { - t.UpdatedCongestionState(state) - } - } - }, - UpdatedPTOCount: func(value uint32) { - for _, t := range tracers { - if t.UpdatedPTOCount != nil { - t.UpdatedPTOCount(value) - } - } - }, - UpdatedKeyFromTLS: func(encLevel EncryptionLevel, p Perspective) { - for _, t := range tracers { - if t.UpdatedKeyFromTLS != nil { - t.UpdatedKeyFromTLS(encLevel, p) - } - } - }, - UpdatedKey: func(keyPhase KeyPhase, remote bool) { - for _, t := range tracers { - if t.UpdatedKey != nil { - t.UpdatedKey(keyPhase, remote) - } - } - }, - DroppedEncryptionLevel: func(encLevel EncryptionLevel) { - for _, t := range tracers { - if t.DroppedEncryptionLevel != nil { - t.DroppedEncryptionLevel(encLevel) - } - } - }, - DroppedKey: func(keyPhase KeyPhase) { - for _, t := range tracers { - if t.DroppedKey != nil { - t.DroppedKey(keyPhase) - } - } - }, - SetLossTimer: func(timerType TimerType, encLevel EncryptionLevel, time time.Time) { - for _, t := range tracers { - if t.SetLossTimer != nil { - t.SetLossTimer(timerType, encLevel, time) - } - } - }, - LossTimerExpired: func(timerType TimerType, encLevel EncryptionLevel) { - for _, t := range tracers { - if t.LossTimerExpired != nil { - t.LossTimerExpired(timerType, encLevel) - } - } - }, - LossTimerCanceled: func() { - for _, t := range tracers { - if t.LossTimerCanceled != nil { - t.LossTimerCanceled() - } - } - }, - ECNStateUpdated: func(state ECNState, trigger ECNStateTrigger) { - for _, t := range tracers { - if t.ECNStateUpdated != nil { - t.ECNStateUpdated(state, trigger) - } - } - }, - ChoseALPN: func(protocol string) { - for _, t := range tracers { - if t.ChoseALPN != nil { - t.ChoseALPN(protocol) - } - } - }, - Close: func() { - for _, t := range tracers { - if t.Close != nil { - t.Close() - } - } - }, - Debug: func(name string, msg string) { - for _, t := range tracers { - if t.Debug != nil { - t.Debug(name, msg) - } - } - }, - } -} diff --git a/vendor/github.com/quic-go/quic-go/logging/frame.go b/vendor/github.com/quic-go/quic-go/logging/frame.go deleted file mode 100644 index e5721eff53..0000000000 --- a/vendor/github.com/quic-go/quic-go/logging/frame.go +++ /dev/null @@ -1,70 +0,0 @@ -package logging - -import "github.com/quic-go/quic-go/internal/wire" - -// A Frame is a QUIC frame -type Frame any - -// The AckRange is used within the AckFrame. -// It is a range of packet numbers that is being acknowledged. -type AckRange = wire.AckRange - -type ( - // An AckFrame is an ACK frame. - AckFrame = wire.AckFrame - // A ConnectionCloseFrame is a CONNECTION_CLOSE frame. - ConnectionCloseFrame = wire.ConnectionCloseFrame - // A DataBlockedFrame is a DATA_BLOCKED frame. - DataBlockedFrame = wire.DataBlockedFrame - // A HandshakeDoneFrame is a HANDSHAKE_DONE frame. - HandshakeDoneFrame = wire.HandshakeDoneFrame - // A MaxDataFrame is a MAX_DATA frame. - MaxDataFrame = wire.MaxDataFrame - // A MaxStreamDataFrame is a MAX_STREAM_DATA frame. - MaxStreamDataFrame = wire.MaxStreamDataFrame - // A MaxStreamsFrame is a MAX_STREAMS_FRAME. - MaxStreamsFrame = wire.MaxStreamsFrame - // A NewConnectionIDFrame is a NEW_CONNECTION_ID frame. - NewConnectionIDFrame = wire.NewConnectionIDFrame - // A NewTokenFrame is a NEW_TOKEN frame. - NewTokenFrame = wire.NewTokenFrame - // A PathChallengeFrame is a PATH_CHALLENGE frame. - PathChallengeFrame = wire.PathChallengeFrame - // A PathResponseFrame is a PATH_RESPONSE frame. - PathResponseFrame = wire.PathResponseFrame - // A PingFrame is a PING frame. - PingFrame = wire.PingFrame - // A ResetStreamFrame is a RESET_STREAM frame. - ResetStreamFrame = wire.ResetStreamFrame - // A RetireConnectionIDFrame is a RETIRE_CONNECTION_ID frame. - RetireConnectionIDFrame = wire.RetireConnectionIDFrame - // A StopSendingFrame is a STOP_SENDING frame. - StopSendingFrame = wire.StopSendingFrame - // A StreamsBlockedFrame is a STREAMS_BLOCKED frame. - 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. -type CryptoFrame struct { - Offset ByteCount - Length ByteCount -} - -// A StreamFrame is a STREAM frame. -type StreamFrame struct { - StreamID StreamID - Offset ByteCount - Length ByteCount - Fin bool -} - -// A DatagramFrame is a DATAGRAM frame. -type DatagramFrame struct { - Length ByteCount -} diff --git a/vendor/github.com/quic-go/quic-go/logging/generate_multiplexer.go b/vendor/github.com/quic-go/quic-go/logging/generate_multiplexer.go deleted file mode 100644 index c152b846a0..0000000000 --- a/vendor/github.com/quic-go/quic-go/logging/generate_multiplexer.go +++ /dev/null @@ -1,161 +0,0 @@ -//go:build generate - -package main - -import ( - "bytes" - "fmt" - "go/ast" - "go/parser" - "go/printer" - "go/token" - "log" - "os" - "strings" - "text/template" - - "golang.org/x/tools/imports" -) - -func main() { - if len(os.Args) != 5 { - log.Fatalf("Usage: %s ", os.Args[0]) - } - - structName := os.Args[1] - inputFile := os.Args[2] - templateFile := os.Args[3] - outputFile := os.Args[4] - - fset := token.NewFileSet() - - // Parse the input file containing the struct type - file, err := parser.ParseFile(fset, inputFile, nil, parser.AllErrors) - if err != nil { - log.Fatalf("Failed to parse file: %v", err) - } - - var fields []*ast.Field - - // Find the specified struct type in the AST - for _, decl := range file.Decls { - genDecl, ok := decl.(*ast.GenDecl) - if !ok || genDecl.Tok != token.TYPE { - continue - } - for _, spec := range genDecl.Specs { - typeSpec, ok := spec.(*ast.TypeSpec) - if !ok || typeSpec.Name.Name != structName { - continue - } - structType, ok := typeSpec.Type.(*ast.StructType) - if !ok { - log.Fatalf("%s is not a struct", structName) - } - fields = structType.Fields.List - break - } - } - - if fields == nil { - log.Fatalf("Could not find %s type", structName) - } - - // Prepare data for the template - type FieldData struct { - Name string - Params string - Args string - HasParams bool - ReturnTypes string - HasReturn bool - } - - var fieldDataList []FieldData - - for _, field := range fields { - funcType, ok := field.Type.(*ast.FuncType) - if !ok { - continue - } - for _, name := range field.Names { - fieldData := FieldData{Name: name.Name} - - // extract parameters - var params []string - var args []string - if funcType.Params != nil { - for i, param := range funcType.Params.List { - // We intentionally reject unnamed (and, further down, "_") function parameters. - // We could auto-generate parameter names, - // but having meaningful variable names will be more helpful for the user. - if len(param.Names) == 0 { - log.Fatalf("encountered unnamed parameter at position %d in function %s", i, fieldData.Name) - } - var buf bytes.Buffer - printer.Fprint(&buf, fset, param.Type) - paramType := buf.String() - for _, paramName := range param.Names { - if paramName.Name == "_" { - log.Fatalf("encountered underscore parameter at position %d in function %s", i, fieldData.Name) - } - params = append(params, fmt.Sprintf("%s %s", paramName.Name, paramType)) - args = append(args, paramName.Name) - } - } - } - fieldData.Params = strings.Join(params, ", ") - fieldData.Args = strings.Join(args, ", ") - fieldData.HasParams = len(params) > 0 - - // extract return types - if funcType.Results != nil && len(funcType.Results.List) > 0 { - fieldData.HasReturn = true - var returns []string - for _, result := range funcType.Results.List { - var buf bytes.Buffer - printer.Fprint(&buf, fset, result.Type) - returns = append(returns, buf.String()) - } - if len(returns) == 1 { - fieldData.ReturnTypes = fmt.Sprintf(" %s", returns[0]) - } else { - fieldData.ReturnTypes = fmt.Sprintf(" (%s)", strings.Join(returns, ", ")) - } - } - - fieldDataList = append(fieldDataList, fieldData) - } - } - - // Read the template from file - templateContent, err := os.ReadFile(templateFile) - if err != nil { - log.Fatalf("Failed to read template file: %v", err) - } - - // Generate the code using the template - tmpl, err := template.New("multiplexer").Funcs(template.FuncMap{"join": strings.Join}).Parse(string(templateContent)) - if err != nil { - log.Fatalf("Failed to parse template: %v", err) - } - - var generatedCode bytes.Buffer - generatedCode.WriteString("// Code generated by generate_multiplexer.go; DO NOT EDIT.\n\n") - if err = tmpl.Execute(&generatedCode, map[string]interface{}{ - "Fields": fieldDataList, - "StructName": structName, - }); err != nil { - log.Fatalf("Failed to execute template: %v", err) - } - - // Format the generated code and add imports - formattedCode, err := imports.Process(outputFile, generatedCode.Bytes(), nil) - if err != nil { - log.Fatalf("Failed to process imports: %v", err) - } - - if err := os.WriteFile(outputFile, formattedCode, 0o644); err != nil { - log.Fatalf("Failed to write output file: %v", err) - } -} diff --git a/vendor/github.com/quic-go/quic-go/logging/interface.go b/vendor/github.com/quic-go/quic-go/logging/interface.go deleted file mode 100644 index 1f8edb92c6..0000000000 --- a/vendor/github.com/quic-go/quic-go/logging/interface.go +++ /dev/null @@ -1,111 +0,0 @@ -// Package logging defines a logging interface for quic-go. -// This package should not be considered stable -package logging - -import ( - "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" -) - -type ( - // A ByteCount is used to count bytes. - ByteCount = protocol.ByteCount - // ECN is the ECN value - ECN = protocol.ECN - // A ConnectionID is a QUIC Connection ID. - ConnectionID = protocol.ConnectionID - // An ArbitraryLenConnectionID is a QUIC Connection ID that can be up to 255 bytes long. - ArbitraryLenConnectionID = protocol.ArbitraryLenConnectionID - // The EncryptionLevel is the encryption level of a packet. - EncryptionLevel = protocol.EncryptionLevel - // The KeyPhase is the key phase of the 1-RTT keys. - KeyPhase = protocol.KeyPhase - // The KeyPhaseBit is the value of the key phase bit of the 1-RTT packets. - KeyPhaseBit = protocol.KeyPhaseBit - // The PacketNumber is the packet number of a packet. - PacketNumber = protocol.PacketNumber - // The Perspective is the role of a QUIC endpoint (client or server). - Perspective = protocol.Perspective - // A StatelessResetToken is a stateless reset token. - StatelessResetToken = protocol.StatelessResetToken - // The StreamID is the stream ID. - StreamID = protocol.StreamID - // The StreamNum is the number of the stream. - StreamNum = protocol.StreamNum - // The StreamType is the type of the stream (unidirectional or bidirectional). - StreamType = protocol.StreamType - // The Version is the QUIC version. - Version = protocol.Version - - // The Header is the QUIC packet header, before removing header protection. - Header = wire.Header - // The ExtendedHeader is the QUIC Long Header packet header, after removing header protection. - ExtendedHeader = wire.ExtendedHeader - // The TransportParameters are QUIC transport parameters. - TransportParameters = wire.TransportParameters - // The PreferredAddress is the preferred address sent in the transport parameters. - PreferredAddress = wire.PreferredAddress - - // A TransportError is a transport-level error code. - TransportError = qerr.TransportErrorCode - // An ApplicationError is an application-defined error code. - ApplicationError = qerr.TransportErrorCode - - // The RTTStats contain statistics used by the congestion controller. - RTTStats = utils.RTTStats -) - -const ( - // ECNUnsupported means that no ECN value was set / received - ECNUnsupported = protocol.ECNUnsupported - // ECTNot is Not-ECT - ECTNot = protocol.ECNNon - // ECT0 is ECT(0) - ECT0 = protocol.ECT0 - // ECT1 is ECT(1) - ECT1 = protocol.ECT1 - // ECNCE is CE - ECNCE = protocol.ECNCE -) - -const ( - // KeyPhaseZero is key phase bit 0 - KeyPhaseZero = protocol.KeyPhaseZero - // KeyPhaseOne is key phase bit 1 - KeyPhaseOne = protocol.KeyPhaseOne -) - -const ( - // PerspectiveServer is used for a QUIC server - PerspectiveServer = protocol.PerspectiveServer - // PerspectiveClient is used for a QUIC client - PerspectiveClient = protocol.PerspectiveClient -) - -const ( - // EncryptionInitial is the Initial encryption level - EncryptionInitial = protocol.EncryptionInitial - // EncryptionHandshake is the Handshake encryption level - EncryptionHandshake = protocol.EncryptionHandshake - // Encryption1RTT is the 1-RTT encryption level - Encryption1RTT = protocol.Encryption1RTT - // Encryption0RTT is the 0-RTT encryption level - Encryption0RTT = protocol.Encryption0RTT -) - -const ( - // StreamTypeUni is a unidirectional stream - StreamTypeUni = protocol.StreamTypeUni - // StreamTypeBidi is a bidirectional stream - StreamTypeBidi = protocol.StreamTypeBidi -) - -// The ShortHeader is the QUIC Short Header packet header, after removing header protection. -type ShortHeader struct { - DestConnectionID ConnectionID - PacketNumber PacketNumber - PacketNumberLen protocol.PacketNumberLen - KeyPhase KeyPhaseBit -} diff --git a/vendor/github.com/quic-go/quic-go/logging/multiplexer.tmpl b/vendor/github.com/quic-go/quic-go/logging/multiplexer.tmpl deleted file mode 100644 index 9ba52e0ffa..0000000000 --- a/vendor/github.com/quic-go/quic-go/logging/multiplexer.tmpl +++ /dev/null @@ -1,21 +0,0 @@ -package logging - -func NewMultiplexed{{ .StructName }} (tracers ...*{{ .StructName }}) *{{ .StructName }} { - if len(tracers) == 0 { - return nil - } - if len(tracers) == 1 { - return tracers[0] - } - return &{{ .StructName }}{ - {{- range .Fields }} - {{ .Name }}: func({{ .Params }}){{ .ReturnTypes }} { - for _, t := range tracers { - if t.{{ .Name }} != nil { - t.{{ .Name }}({{ .Args }}) - } - } - }, - {{- end }} - } -} diff --git a/vendor/github.com/quic-go/quic-go/logging/packet_header.go b/vendor/github.com/quic-go/quic-go/logging/packet_header.go deleted file mode 100644 index 6b8df58d8a..0000000000 --- a/vendor/github.com/quic-go/quic-go/logging/packet_header.go +++ /dev/null @@ -1,24 +0,0 @@ -package logging - -import ( - "github.com/quic-go/quic-go/internal/protocol" -) - -// PacketTypeFromHeader determines the packet type from a *wire.Header. -func PacketTypeFromHeader(hdr *Header) PacketType { - if hdr.Version == 0 { - return PacketTypeVersionNegotiation - } - switch hdr.Type { - case protocol.PacketTypeInitial: - return PacketTypeInitial - case protocol.PacketTypeHandshake: - return PacketTypeHandshake - case protocol.PacketType0RTT: - return PacketType0RTT - case protocol.PacketTypeRetry: - return PacketTypeRetry - default: - return PacketTypeNotDetermined - } -} diff --git a/vendor/github.com/quic-go/quic-go/logging/tracer.go b/vendor/github.com/quic-go/quic-go/logging/tracer.go deleted file mode 100644 index 4fe0146279..0000000000 --- a/vendor/github.com/quic-go/quic-go/logging/tracer.go +++ /dev/null @@ -1,14 +0,0 @@ -package logging - -import "net" - -//go:generate go run generate_multiplexer.go Tracer tracer.go multiplexer.tmpl tracer_multiplexer.go - -// A Tracer traces events. -type Tracer struct { - SentPacket func(dest net.Addr, hdr *Header, size ByteCount, frames []Frame) - SentVersionNegotiationPacket func(dest net.Addr, destConnID, srcConnID ArbitraryLenConnectionID, versions []Version) - DroppedPacket func(addr net.Addr, packetType PacketType, size ByteCount, reason PacketDropReason) - Debug func(name, msg string) - Close func() -} diff --git a/vendor/github.com/quic-go/quic-go/logging/tracer_multiplexer.go b/vendor/github.com/quic-go/quic-go/logging/tracer_multiplexer.go deleted file mode 100644 index f0878cfe79..0000000000 --- a/vendor/github.com/quic-go/quic-go/logging/tracer_multiplexer.go +++ /dev/null @@ -1,51 +0,0 @@ -// Code generated by generate_multiplexer.go; DO NOT EDIT. - -package logging - -import "net" - -func NewMultiplexedTracer(tracers ...*Tracer) *Tracer { - if len(tracers) == 0 { - return nil - } - if len(tracers) == 1 { - return tracers[0] - } - return &Tracer{ - SentPacket: func(dest net.Addr, hdr *Header, size ByteCount, frames []Frame) { - for _, t := range tracers { - if t.SentPacket != nil { - t.SentPacket(dest, hdr, size, frames) - } - } - }, - SentVersionNegotiationPacket: func(dest net.Addr, destConnID ArbitraryLenConnectionID, srcConnID ArbitraryLenConnectionID, versions []Version) { - for _, t := range tracers { - if t.SentVersionNegotiationPacket != nil { - t.SentVersionNegotiationPacket(dest, destConnID, srcConnID, versions) - } - } - }, - DroppedPacket: func(addr net.Addr, packetType PacketType, size ByteCount, reason PacketDropReason) { - for _, t := range tracers { - if t.DroppedPacket != nil { - t.DroppedPacket(addr, packetType, size, reason) - } - } - }, - Debug: func(name string, msg string) { - for _, t := range tracers { - if t.Debug != nil { - t.Debug(name, msg) - } - } - }, - Close: func() { - for _, t := range tracers { - if t.Close != nil { - t.Close() - } - } - }, - } -} diff --git a/vendor/github.com/quic-go/quic-go/logging/types.go b/vendor/github.com/quic-go/quic-go/logging/types.go deleted file mode 100644 index 65da35595c..0000000000 --- a/vendor/github.com/quic-go/quic-go/logging/types.go +++ /dev/null @@ -1,130 +0,0 @@ -package logging - -// PacketType is the packet type of a QUIC packet -type PacketType uint8 - -const ( - // PacketTypeInitial is the packet type of an Initial packet - PacketTypeInitial PacketType = iota - // PacketTypeHandshake is the packet type of a Handshake packet - PacketTypeHandshake - // PacketTypeRetry is the packet type of a Retry packet - PacketTypeRetry - // PacketType0RTT is the packet type of a 0-RTT packet - PacketType0RTT - // PacketTypeVersionNegotiation is the packet type of a Version Negotiation packet - PacketTypeVersionNegotiation - // PacketType1RTT is a 1-RTT packet - PacketType1RTT - // PacketTypeStatelessReset is a stateless reset - PacketTypeStatelessReset - // PacketTypeNotDetermined is the packet type when it could not be determined - PacketTypeNotDetermined -) - -type PacketLossReason uint8 - -const ( - // PacketLossReorderingThreshold: when a packet is deemed lost due to reordering threshold - PacketLossReorderingThreshold PacketLossReason = iota - // PacketLossTimeThreshold: when a packet is deemed lost due to time threshold - PacketLossTimeThreshold -) - -type PacketDropReason uint8 - -const ( - // PacketDropKeyUnavailable is used when a packet is dropped because keys are unavailable - PacketDropKeyUnavailable PacketDropReason = iota - // PacketDropUnknownConnectionID is used when a packet is dropped because the connection ID is unknown - PacketDropUnknownConnectionID - // PacketDropHeaderParseError is used when a packet is dropped because header parsing failed - PacketDropHeaderParseError - // PacketDropPayloadDecryptError is used when a packet is dropped because decrypting the payload failed - PacketDropPayloadDecryptError - // PacketDropProtocolViolation is used when a packet is dropped due to a protocol violation - PacketDropProtocolViolation - // PacketDropDOSPrevention is used when a packet is dropped to mitigate a DoS attack - PacketDropDOSPrevention - // PacketDropUnsupportedVersion is used when a packet is dropped because the version is not supported - PacketDropUnsupportedVersion - // PacketDropUnexpectedPacket is used when an unexpected packet is received - PacketDropUnexpectedPacket - // PacketDropUnexpectedSourceConnectionID is used when a packet with an unexpected source connection ID is received - PacketDropUnexpectedSourceConnectionID - // PacketDropUnexpectedVersion is used when a packet with an unexpected version is received - PacketDropUnexpectedVersion - // PacketDropDuplicate is used when a duplicate packet is received - PacketDropDuplicate -) - -// TimerType is the type of the loss detection timer -type TimerType uint8 - -const ( - // TimerTypeACK is the timer type for the early retransmit timer - TimerTypeACK TimerType = iota + 1 - // TimerTypePTO is the timer type for the PTO retransmit timer - TimerTypePTO - // TimerTypePathProbe is the timer type for the path probe retransmit timer - TimerTypePathProbe -) - -// TimeoutReason is the reason why a connection is closed -type TimeoutReason uint8 - -const ( - // TimeoutReasonHandshake is used when the connection is closed due to a handshake timeout - // This reason is not defined in the qlog draft, but very useful for debugging. - TimeoutReasonHandshake TimeoutReason = iota - // TimeoutReasonIdle is used when the connection is closed due to an idle timeout - // This reason is not defined in the qlog draft, but very useful for debugging. - TimeoutReasonIdle -) - -type CongestionState uint8 - -const ( - // CongestionStateSlowStart is the slow start phase of Reno / Cubic - CongestionStateSlowStart CongestionState = iota - // CongestionStateCongestionAvoidance is the slow start phase of Reno / Cubic - CongestionStateCongestionAvoidance - // CongestionStateRecovery is the recovery phase of Reno / Cubic - CongestionStateRecovery - // CongestionStateApplicationLimited means that the congestion controller is application limited - CongestionStateApplicationLimited -) - -// ECNState is the state of the ECN state machine (see Appendix A.4 of RFC 9000) -type ECNState uint8 - -const ( - // ECNStateTesting is the testing state - ECNStateTesting ECNState = 1 + iota - // ECNStateUnknown is the unknown state - ECNStateUnknown - // ECNStateFailed is the failed state - ECNStateFailed - // ECNStateCapable is the capable state - ECNStateCapable -) - -// ECNStateTrigger is a trigger for an ECN state transition. -type ECNStateTrigger uint8 - -const ( - ECNTriggerNoTrigger ECNStateTrigger = iota - // ECNFailedNoECNCounts is emitted when an ACK acknowledges ECN-marked packets, - // but doesn't contain any ECN counts - ECNFailedNoECNCounts - // ECNFailedDecreasedECNCounts is emitted when an ACK frame decreases ECN counts - ECNFailedDecreasedECNCounts - // ECNFailedLostAllTestingPackets is emitted when all ECN testing packets are declared lost - ECNFailedLostAllTestingPackets - // ECNFailedMoreECNCountsThanSent is emitted when an ACK contains more ECN counts than ECN-marked packets were sent - ECNFailedMoreECNCountsThanSent - // ECNFailedTooFewECNCounts is emitted when an ACK contains fewer ECN counts than it acknowledges packets - ECNFailedTooFewECNCounts - // ECNFailedManglingDetected is emitted when the path marks all ECN-marked packets as CE - ECNFailedManglingDetected -) 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 244cfe17cf..950757f0c2 100644 --- a/vendor/github.com/quic-go/quic-go/mtu_discoverer.go +++ b/vendor/github.com/quic-go/quic-go/mtu_discoverer.go @@ -6,7 +6,8 @@ import ( "github.com/quic-go/quic-go/internal/protocol" "github.com/quic-go/quic-go/internal/utils" "github.com/quic-go/quic-go/internal/wire" - "github.com/quic-go/quic-go/logging" + "github.com/quic-go/quic-go/qlog" + "github.com/quic-go/quic-go/qlogwriter" ) type mtuDiscoverer interface { @@ -103,7 +104,7 @@ type mtuFinder struct { // We're therefore not concerned about overflows of this counter. generation uint8 - tracer *logging.ConnectionTracer + qlogger qlogwriter.Recorder } var _ mtuDiscoverer = &mtuFinder{} @@ -111,12 +112,12 @@ var _ mtuDiscoverer = &mtuFinder{} func newMTUDiscoverer( rttStats *utils.RTTStats, start, max protocol.ByteCount, - tracer *logging.ConnectionTracer, + qlogger qlogwriter.Recorder, ) *mtuFinder { f := &mtuFinder{ inFlight: protocol.InvalidByteCount, rttStats: rttStats, - tracer: tracer, + qlogger: qlogger, } f.init(start, max) return f @@ -223,8 +224,11 @@ func (h *mtuFinderAckHandler) OnAcked(wire.Frame) { } } } - if h.tracer != nil && h.tracer.UpdatedMTU != nil { - h.tracer.UpdatedMTU(size, h.done()) + if h.qlogger != nil { + h.qlogger.RecordEvent(qlog.MTUUpdated{ + Value: int(size), + Done: h.done(), + }) } } 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 e3933da6bb..175d3c86dc 100644 --- a/vendor/github.com/quic-go/quic-go/packet_packer.go +++ b/vendor/github.com/quic-go/quic-go/packet_packer.go @@ -350,7 +350,6 @@ func (p *packetPacker) PackCoalescedPacket(onlyAck bool, maxSize protocol.ByteCo now, false, onlyAck, - true, v, ) if initialPayload.length > 0 { @@ -373,7 +372,6 @@ func (p *packetPacker) PackCoalescedPacket(onlyAck bool, maxSize protocol.ByteCo now, false, onlyAck, - size == 0, v, ) if handshakePayload.length > 0 { @@ -399,7 +397,7 @@ func (p *packetPacker) PackCoalescedPacket(onlyAck bool, maxSize protocol.ByteCo connID = p.getDestConnID() oneRTTPacketNumber, oneRTTPacketNumberLen = p.pnManager.PeekPacketNumber(protocol.Encryption1RTT) hdrLen := wire.ShortHeaderLen(connID, oneRTTPacketNumberLen) - oneRTTPayload = p.maybeGetShortHeaderPacket(oneRTTSealer, hdrLen, maxSize-size, onlyAck, size == 0, now, v) + oneRTTPayload = p.maybeGetShortHeaderPacket(oneRTTSealer, hdrLen, maxSize-size, onlyAck, now, v) if oneRTTPayload.length > 0 { size += p.shortHeaderPacketLength(connID, oneRTTPacketNumberLen, oneRTTPayload) + protocol.ByteCount(oneRTTSealer.Overhead()) } @@ -486,7 +484,7 @@ func (p *packetPacker) appendPacket( pn, pnLen := p.pnManager.PeekPacketNumber(protocol.Encryption1RTT) connID := p.getDestConnID() hdrLen := wire.ShortHeaderLen(connID, pnLen) - pl := p.maybeGetShortHeaderPacket(sealer, hdrLen, maxPacketSize, onlyAck, true, now, v) + pl := p.maybeGetShortHeaderPacket(sealer, hdrLen, maxPacketSize, onlyAck, now, v) if pl.length == 0 { return shortHeaderPacket{}, errNothingToPack } @@ -500,17 +498,17 @@ func (p *packetPacker) maybeGetCryptoPacket( encLevel protocol.EncryptionLevel, now monotime.Time, addPingIfEmpty bool, - onlyAck, ackAllowed bool, + onlyAck bool, v protocol.Version, ) (*wire.ExtendedHeader, payload) { if onlyAck { if ack := p.acks.GetAckFrame(encLevel, now, true); ack != nil { - return p.getLongHeader(encLevel, v), payload{ - ack: ack, - length: ack.Length(v), - } + hdr := p.getLongHeader(encLevel, v) + maxPacketSize -= hdr.GetLength(v) + ack.Truncate(maxPacketSize, v) + return hdr, payload{ack: ack, length: ack.Length(v)} } - return nil, payload{} + return nil, payload{length: 0} } var hasCryptoData func() bool @@ -527,10 +525,7 @@ func (p *packetPacker) maybeGetCryptoPacket( handler := p.retransmissionQueue.AckHandler(encLevel) hasRetransmission := p.retransmissionQueue.HasData(encLevel) - var ack *wire.AckFrame - if ackAllowed { - ack = p.acks.GetAckFrame(encLevel, now, !hasRetransmission && !hasCryptoData()) - } + ack := p.acks.GetAckFrame(encLevel, now, !hasRetransmission && !hasCryptoData()) var pl payload if !hasCryptoData() && !hasRetransmission && ack == nil { if !addPingIfEmpty { @@ -542,13 +537,15 @@ func (p *packetPacker) maybeGetCryptoPacket( pl.length += ping.Length(v) } + hdr := p.getLongHeader(encLevel, v) + maxPacketSize -= hdr.GetLength(v) + if ack != nil { + ack.Truncate(maxPacketSize, v) pl.ack = ack pl.length = ack.Length(v) maxPacketSize -= pl.length } - hdr := p.getLongHeader(encLevel, v) - maxPacketSize -= hdr.GetLength(v) if hasRetransmission { for { frame := p.retransmissionQueue.GetFrame(encLevel, maxPacketSize, v) @@ -591,12 +588,12 @@ func (p *packetPacker) maybeGetAppDataPacketFor0RTT(sealer sealer, maxSize proto func (p *packetPacker) maybeGetShortHeaderPacket( sealer handshake.ShortHeaderSealer, hdrLen, maxPacketSize protocol.ByteCount, - onlyAck, ackAllowed bool, + onlyAck bool, now monotime.Time, v protocol.Version, ) payload { maxPayloadSize := maxPacketSize - hdrLen - protocol.ByteCount(sealer.Overhead()) - return p.maybeGetAppDataPacket(maxPayloadSize, onlyAck, ackAllowed, now, v) + return p.maybeGetAppDataPacket(maxPayloadSize, onlyAck, true, now, v) } func (p *packetPacker) maybeGetAppDataPacket( @@ -635,6 +632,7 @@ func (p *packetPacker) composeNextPacket( ) payload { if onlyAck { if ack := p.acks.GetAckFrame(protocol.Encryption1RTT, now, true); ack != nil { + ack.Truncate(maxPayloadSize, v) return payload{ack: ack, length: ack.Length(v)} } return payload{} @@ -643,13 +641,12 @@ func (p *packetPacker) composeNextPacket( hasData := p.framer.HasData() hasRetransmission := p.retransmissionQueue.HasData(protocol.Encryption1RTT) - var hasAck bool var pl payload if ackAllowed { if ack := p.acks.GetAckFrame(protocol.Encryption1RTT, now, !hasRetransmission && !hasData); ack != nil { + ack.Truncate(maxPayloadSize, v) pl.ack = ack pl.length += ack.Length(v) - hasAck = true } } @@ -660,7 +657,7 @@ func (p *packetPacker) composeNextPacket( pl.frames = append(pl.frames, ackhandler.Frame{Frame: f}) pl.length += size p.datagramQueue.Pop() - } else if !hasAck { + } else if pl.ack == nil { // The DATAGRAM frame doesn't fit, and the packet doesn't contain an ACK. // Discard this frame. There's no point in retrying this in the next packet, // as it's unlikely that the available packet size will increase. @@ -670,7 +667,7 @@ func (p *packetPacker) composeNextPacket( } } - if hasAck && !hasData && !hasRetransmission { + if pl.ack != nil && !hasData && !hasRetransmission { return pl } @@ -747,7 +744,6 @@ func (p *packetPacker) PackPTOProbePacket( now, addPingIfEmpty, false, - true, v, ) if pl.length == 0 { diff --git a/vendor/github.com/quic-go/quic-go/qlog/event.go b/vendor/github.com/quic-go/quic-go/qlog/event.go new file mode 100644 index 0000000000..83ca71f13a --- /dev/null +++ b/vendor/github.com/quic-go/quic-go/qlog/event.go @@ -0,0 +1,849 @@ +package qlog + +import ( + "fmt" + "net/netip" + "time" + + "github.com/quic-go/quic-go/internal/protocol" + "github.com/quic-go/quic-go/internal/qerr" + "github.com/quic-go/quic-go/qlogwriter/jsontext" +) + +func milliseconds(dur time.Duration) float64 { return float64(dur.Nanoseconds()) / 1e6 } + +type encoderHelper struct { + enc *jsontext.Encoder + err error +} + +func (h *encoderHelper) WriteToken(t jsontext.Token) { + if h.err != nil { + return + } + h.err = h.enc.WriteToken(t) +} + +type versions []Version + +func (v versions) encode(enc *jsontext.Encoder) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginArray) + for _, e := range v { + h.WriteToken(jsontext.String(fmt.Sprintf("%x", uint32(e)))) + } + h.WriteToken(jsontext.EndArray) + return h.err +} + +type RawInfo struct { + Length int // full packet length, including header and AEAD authentication tag + PayloadLength int // length of the packet payload, excluding AEAD tag +} + +func (i RawInfo) encode(enc *jsontext.Encoder) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("length")) + h.WriteToken(jsontext.Uint(uint64(i.Length))) + if i.PayloadLength != 0 { + h.WriteToken(jsontext.String("payload_length")) + h.WriteToken(jsontext.Uint(uint64(i.PayloadLength))) + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +type PathEndpointInfo struct { + IPv4 netip.AddrPort + IPv6 netip.AddrPort +} + +func (p PathEndpointInfo) encode(enc *jsontext.Encoder) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + if p.IPv4.IsValid() { + h.WriteToken(jsontext.String("ip_v4")) + h.WriteToken(jsontext.String(p.IPv4.Addr().String())) + h.WriteToken(jsontext.String("port_v4")) + h.WriteToken(jsontext.Int(int64(p.IPv4.Port()))) + } + if p.IPv6.IsValid() { + h.WriteToken(jsontext.String("ip_v6")) + h.WriteToken(jsontext.String(p.IPv6.Addr().String())) + h.WriteToken(jsontext.String("port_v6")) + h.WriteToken(jsontext.Int(int64(p.IPv6.Port()))) + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +type StartedConnection struct { + Local PathEndpointInfo + Remote PathEndpointInfo +} + +func (e StartedConnection) Name() string { return "transport:connection_started" } + +func (e StartedConnection) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("local")) + if err := e.Local.encode(enc); err != nil { + return err + } + h.WriteToken(jsontext.String("remote")) + if err := e.Remote.encode(enc); err != nil { + return err + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +type VersionInformation struct { + ClientVersions, ServerVersions []Version + ChosenVersion Version +} + +func (e VersionInformation) Name() string { return "transport:version_information" } + +func (e VersionInformation) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + if len(e.ClientVersions) > 0 { + h.WriteToken(jsontext.String("client_versions")) + if err := versions(e.ClientVersions).encode(enc); err != nil { + return err + } + } + if len(e.ServerVersions) > 0 { + h.WriteToken(jsontext.String("server_versions")) + if err := versions(e.ServerVersions).encode(enc); err != nil { + return err + } + } + h.WriteToken(jsontext.String("chosen_version")) + h.WriteToken(jsontext.String(fmt.Sprintf("%x", uint32(e.ChosenVersion)))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +type ConnectionClosed struct { + Initiator Initiator + + ConnectionError *TransportErrorCode + ApplicationError *ApplicationErrorCode + + Reason string + + Trigger ConnectionCloseTrigger +} + +func (e ConnectionClosed) Name() string { return "transport:connection_closed" } + +func (e ConnectionClosed) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("initiator")) + h.WriteToken(jsontext.String(string(e.Initiator))) + if e.ConnectionError != nil { + h.WriteToken(jsontext.String("connection_error")) + if e.ConnectionError.IsCryptoError() { + h.WriteToken(jsontext.String(fmt.Sprintf("crypto_error_%#x", uint16(*e.ConnectionError)))) + } else { + switch *e.ConnectionError { + case qerr.NoError: + h.WriteToken(jsontext.String("no_error")) + case qerr.InternalError: + h.WriteToken(jsontext.String("internal_error")) + case qerr.ConnectionRefused: + h.WriteToken(jsontext.String("connection_refused")) + case qerr.FlowControlError: + h.WriteToken(jsontext.String("flow_control_error")) + case qerr.StreamLimitError: + h.WriteToken(jsontext.String("stream_limit_error")) + case qerr.StreamStateError: + h.WriteToken(jsontext.String("stream_state_error")) + case qerr.FinalSizeError: + h.WriteToken(jsontext.String("final_size_error")) + case qerr.FrameEncodingError: + h.WriteToken(jsontext.String("frame_encoding_error")) + case qerr.TransportParameterError: + h.WriteToken(jsontext.String("transport_parameter_error")) + case qerr.ConnectionIDLimitError: + h.WriteToken(jsontext.String("connection_id_limit_error")) + case qerr.ProtocolViolation: + h.WriteToken(jsontext.String("protocol_violation")) + case qerr.InvalidToken: + h.WriteToken(jsontext.String("invalid_token")) + case qerr.ApplicationErrorErrorCode: + h.WriteToken(jsontext.String("application_error")) + case qerr.CryptoBufferExceeded: + h.WriteToken(jsontext.String("crypto_buffer_exceeded")) + case qerr.KeyUpdateError: + h.WriteToken(jsontext.String("key_update_error")) + case qerr.AEADLimitReached: + h.WriteToken(jsontext.String("aead_limit_reached")) + case qerr.NoViablePathError: + h.WriteToken(jsontext.String("no_viable_path")) + default: + h.WriteToken(jsontext.String("unknown")) + h.WriteToken(jsontext.String("error_code")) + h.WriteToken(jsontext.Uint(uint64(*e.ConnectionError))) + } + } + } + if e.ApplicationError != nil { + h.WriteToken(jsontext.String("application_error")) + h.WriteToken(jsontext.String("unknown")) + h.WriteToken(jsontext.String("error_code")) + h.WriteToken(jsontext.Uint(uint64(*e.ApplicationError))) + } + if e.ConnectionError != nil || e.ApplicationError != nil { + h.WriteToken(jsontext.String("reason")) + h.WriteToken(jsontext.String(e.Reason)) + } + if e.Trigger != "" { + h.WriteToken(jsontext.String("trigger")) + h.WriteToken(jsontext.String(string(e.Trigger))) + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +type PacketSent struct { + Header PacketHeader + Raw RawInfo + DatagramID DatagramID + Frames []Frame + ECN ECN + IsCoalesced bool + Trigger string + SupportedVersions []Version +} + +func (e PacketSent) Name() string { return "transport:packet_sent" } + +func (e PacketSent) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("header")) + if err := e.Header.encode(enc); err != nil { + return err + } + h.WriteToken(jsontext.String("raw")) + if err := e.Raw.encode(enc); err != nil { + return err + } + if e.DatagramID != 0 { + h.WriteToken(jsontext.String("datagram_id")) + h.WriteToken(jsontext.Uint(uint64(e.DatagramID))) + } + if len(e.Frames) > 0 { + h.WriteToken(jsontext.String("frames")) + if err := frames(e.Frames).encode(enc); err != nil { + return err + } + } + if e.IsCoalesced { + h.WriteToken(jsontext.String("is_coalesced")) + h.WriteToken(jsontext.True) + } + if e.ECN != ECNUnsupported { + h.WriteToken(jsontext.String("ecn")) + h.WriteToken(jsontext.String(string(e.ECN))) + } + if e.Trigger != "" { + h.WriteToken(jsontext.String("trigger")) + h.WriteToken(jsontext.String(e.Trigger)) + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +type PacketReceived struct { + Header PacketHeader + Raw RawInfo + DatagramID DatagramID + Frames []Frame + ECN ECN + IsCoalesced bool + Trigger string +} + +func (e PacketReceived) Name() string { return "transport:packet_received" } + +func (e PacketReceived) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("header")) + if err := e.Header.encode(enc); err != nil { + return err + } + h.WriteToken(jsontext.String("raw")) + if err := e.Raw.encode(enc); err != nil { + return err + } + if e.DatagramID != 0 { + h.WriteToken(jsontext.String("datagram_id")) + h.WriteToken(jsontext.Uint(uint64(e.DatagramID))) + } + if len(e.Frames) > 0 { + h.WriteToken(jsontext.String("frames")) + if err := frames(e.Frames).encode(enc); err != nil { + return err + } + } + if e.IsCoalesced { + h.WriteToken(jsontext.String("is_coalesced")) + h.WriteToken(jsontext.True) + } + if e.ECN != ECNUnsupported { + h.WriteToken(jsontext.String("ecn")) + h.WriteToken(jsontext.String(string(e.ECN))) + } + if e.Trigger != "" { + h.WriteToken(jsontext.String("trigger")) + h.WriteToken(jsontext.String(e.Trigger)) + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +type VersionNegotiationReceived struct { + Header PacketHeaderVersionNegotiation + SupportedVersions []Version +} + +func (e VersionNegotiationReceived) Name() string { return "transport:packet_received" } + +func (e VersionNegotiationReceived) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("header")) + if err := e.Header.encode(enc); err != nil { + return err + } + h.WriteToken(jsontext.String("supported_versions")) + if err := versions(e.SupportedVersions).encode(enc); err != nil { + return err + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +type VersionNegotiationSent struct { + Header PacketHeaderVersionNegotiation + SupportedVersions []Version +} + +func (e VersionNegotiationSent) Name() string { return "transport:packet_sent" } + +func (e VersionNegotiationSent) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("header")) + if err := e.Header.encode(enc); err != nil { + return err + } + h.WriteToken(jsontext.String("supported_versions")) + if err := versions(e.SupportedVersions).encode(enc); err != nil { + return err + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +type PacketBuffered struct { + Header PacketHeader + Raw RawInfo + DatagramID DatagramID +} + +func (e PacketBuffered) Name() string { return "transport:packet_buffered" } + +func (e PacketBuffered) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("header")) + if err := e.Header.encode(enc); err != nil { + return err + } + h.WriteToken(jsontext.String("raw")) + if err := e.Raw.encode(enc); err != nil { + return err + } + if e.DatagramID != 0 { + h.WriteToken(jsontext.String("datagram_id")) + h.WriteToken(jsontext.Uint(uint64(e.DatagramID))) + } + h.WriteToken(jsontext.String("trigger")) + h.WriteToken(jsontext.String("keys_unavailable")) + h.WriteToken(jsontext.EndObject) + return h.err +} + +// PacketDropped is the transport:packet_dropped event. +type PacketDropped struct { + Header PacketHeader + Raw RawInfo + DatagramID DatagramID + Trigger PacketDropReason +} + +func (e PacketDropped) Name() string { return "transport:packet_dropped" } + +func (e PacketDropped) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("header")) + if err := e.Header.encode(enc); err != nil { + return err + } + h.WriteToken(jsontext.String("raw")) + if err := e.Raw.encode(enc); err != nil { + return err + } + if e.DatagramID != 0 { + h.WriteToken(jsontext.String("datagram_id")) + h.WriteToken(jsontext.Uint(uint64(e.DatagramID))) + } + h.WriteToken(jsontext.String("trigger")) + h.WriteToken(jsontext.String(string(e.Trigger))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +type MTUUpdated struct { + Value int + Done bool +} + +func (e MTUUpdated) Name() string { return "recovery:mtu_updated" } + +func (e MTUUpdated) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("mtu")) + h.WriteToken(jsontext.Uint(uint64(e.Value))) + h.WriteToken(jsontext.String("done")) + h.WriteToken(jsontext.Bool(e.Done)) + h.WriteToken(jsontext.EndObject) + return h.err +} + +// MetricsUpdated logs RTT and congestion metrics as defined in the +// recovery:metrics_updated event. +// The PTO count is logged via PTOCountUpdated. +type MetricsUpdated struct { + MinRTT time.Duration + SmoothedRTT time.Duration + LatestRTT time.Duration + RTTVariance time.Duration + CongestionWindow int + BytesInFlight int + PacketsInFlight int +} + +func (e MetricsUpdated) Name() string { return "recovery:metrics_updated" } + +func (e MetricsUpdated) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + if e.MinRTT != 0 { + h.WriteToken(jsontext.String("min_rtt")) + h.WriteToken(jsontext.Float(milliseconds(e.MinRTT))) + } + if e.SmoothedRTT != 0 { + h.WriteToken(jsontext.String("smoothed_rtt")) + h.WriteToken(jsontext.Float(milliseconds(e.SmoothedRTT))) + } + if e.LatestRTT != 0 { + h.WriteToken(jsontext.String("latest_rtt")) + h.WriteToken(jsontext.Float(milliseconds(e.LatestRTT))) + } + if e.RTTVariance != 0 { + h.WriteToken(jsontext.String("rtt_variance")) + h.WriteToken(jsontext.Float(milliseconds(e.RTTVariance))) + } + if e.CongestionWindow != 0 { + h.WriteToken(jsontext.String("congestion_window")) + h.WriteToken(jsontext.Uint(uint64(e.CongestionWindow))) + } + if e.BytesInFlight != 0 { + h.WriteToken(jsontext.String("bytes_in_flight")) + h.WriteToken(jsontext.Uint(uint64(e.BytesInFlight))) + } + if e.PacketsInFlight != 0 { + h.WriteToken(jsontext.String("packets_in_flight")) + h.WriteToken(jsontext.Uint(uint64(e.PacketsInFlight))) + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +// PTOCountUpdated logs the pto_count value of the +// recovery:metrics_updated event. +type PTOCountUpdated struct { + PTOCount uint32 +} + +func (e PTOCountUpdated) Name() string { return "recovery:metrics_updated" } + +func (e PTOCountUpdated) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("pto_count")) + h.WriteToken(jsontext.Uint(uint64(e.PTOCount))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +type PacketLost struct { + Header PacketHeader + Trigger PacketLossReason +} + +func (e PacketLost) Name() string { return "recovery:packet_lost" } + +func (e PacketLost) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("header")) + if err := e.Header.encode(enc); err != nil { + return err + } + h.WriteToken(jsontext.String("trigger")) + h.WriteToken(jsontext.String(string(e.Trigger))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +type SpuriousLoss struct { + EncryptionLevel protocol.EncryptionLevel + PacketNumber protocol.PacketNumber + PacketReordering uint64 + TimeReordering time.Duration +} + +func (e SpuriousLoss) Name() string { return "recovery:spurious_loss" } + +func (e SpuriousLoss) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("packet_number_space")) + h.WriteToken(jsontext.String(encLevelToPacketNumberSpace(e.EncryptionLevel))) + h.WriteToken(jsontext.String("packet_number")) + h.WriteToken(jsontext.Uint(uint64(e.PacketNumber))) + h.WriteToken(jsontext.String("reordering_packets")) + h.WriteToken(jsontext.Uint(e.PacketReordering)) + h.WriteToken(jsontext.String("reordering_time")) + h.WriteToken(jsontext.Float(milliseconds(e.TimeReordering))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +type KeyUpdated struct { + Trigger KeyUpdateTrigger + KeyType KeyType + KeyPhase KeyPhase // only set for 1-RTT keys + // we don't log the keys here, so we don't need `old` and `new`. +} + +func (e KeyUpdated) Name() string { return "security:key_updated" } + +func (e KeyUpdated) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("trigger")) + h.WriteToken(jsontext.String(string(e.Trigger))) + h.WriteToken(jsontext.String("key_type")) + h.WriteToken(jsontext.String(string(e.KeyType))) + if e.KeyType == KeyTypeClient1RTT || e.KeyType == KeyTypeServer1RTT { + h.WriteToken(jsontext.String("key_phase")) + h.WriteToken(jsontext.Uint(uint64(e.KeyPhase))) + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +type KeyDiscarded struct { + KeyType KeyType + KeyPhase KeyPhase // only set for 1-RTT keys +} + +func (e KeyDiscarded) Name() string { return "security:key_discarded" } + +func (e KeyDiscarded) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + if e.KeyType != KeyTypeClient1RTT && e.KeyType != KeyTypeServer1RTT { + h.WriteToken(jsontext.String("trigger")) + h.WriteToken(jsontext.String("tls")) + } + h.WriteToken(jsontext.String("key_type")) + h.WriteToken(jsontext.String(string(e.KeyType))) + if e.KeyType == KeyTypeClient1RTT || e.KeyType == KeyTypeServer1RTT { + h.WriteToken(jsontext.String("key_phase")) + h.WriteToken(jsontext.Uint(uint64(e.KeyPhase))) + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +type ParametersSet struct { + Restore bool + Initiator Initiator + SentBy protocol.Perspective + OriginalDestinationConnectionID protocol.ConnectionID + InitialSourceConnectionID protocol.ConnectionID + RetrySourceConnectionID *protocol.ConnectionID + StatelessResetToken *protocol.StatelessResetToken + DisableActiveMigration bool + MaxIdleTimeout time.Duration + MaxUDPPayloadSize protocol.ByteCount + AckDelayExponent uint8 + MaxAckDelay time.Duration + ActiveConnectionIDLimit uint64 + InitialMaxData protocol.ByteCount + InitialMaxStreamDataBidiLocal protocol.ByteCount + InitialMaxStreamDataBidiRemote protocol.ByteCount + InitialMaxStreamDataUni protocol.ByteCount + InitialMaxStreamsBidi int64 + InitialMaxStreamsUni int64 + PreferredAddress *PreferredAddress + MaxDatagramFrameSize protocol.ByteCount + EnableResetStreamAt bool +} + +func (e ParametersSet) Name() string { + if e.Restore { + return "transport:parameters_restored" + } + return "transport:parameters_set" +} + +func (e ParametersSet) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + if !e.Restore { + h.WriteToken(jsontext.String("initiator")) + h.WriteToken(jsontext.String(string(e.Initiator))) + if e.SentBy == protocol.PerspectiveServer { + h.WriteToken(jsontext.String("original_destination_connection_id")) + h.WriteToken(jsontext.String(e.OriginalDestinationConnectionID.String())) + if e.StatelessResetToken != nil { + h.WriteToken(jsontext.String("stateless_reset_token")) + h.WriteToken(jsontext.String(fmt.Sprintf("%x", e.StatelessResetToken[:]))) + } + if e.RetrySourceConnectionID != nil { + h.WriteToken(jsontext.String("retry_source_connection_id")) + h.WriteToken(jsontext.String((*e.RetrySourceConnectionID).String())) + } + } + h.WriteToken(jsontext.String("initial_source_connection_id")) + h.WriteToken(jsontext.String(e.InitialSourceConnectionID.String())) + } + h.WriteToken(jsontext.String("disable_active_migration")) + h.WriteToken(jsontext.Bool(e.DisableActiveMigration)) + if e.MaxIdleTimeout != 0 { + h.WriteToken(jsontext.String("max_idle_timeout")) + h.WriteToken(jsontext.Float(milliseconds(e.MaxIdleTimeout))) + } + if e.MaxUDPPayloadSize != 0 { + h.WriteToken(jsontext.String("max_udp_payload_size")) + h.WriteToken(jsontext.Int(int64(e.MaxUDPPayloadSize))) + } + if e.AckDelayExponent != 0 { + h.WriteToken(jsontext.String("ack_delay_exponent")) + h.WriteToken(jsontext.Uint(uint64(e.AckDelayExponent))) + } + if e.MaxAckDelay != 0 { + h.WriteToken(jsontext.String("max_ack_delay")) + h.WriteToken(jsontext.Float(milliseconds(e.MaxAckDelay))) + } + if e.ActiveConnectionIDLimit != 0 { + h.WriteToken(jsontext.String("active_connection_id_limit")) + h.WriteToken(jsontext.Uint(e.ActiveConnectionIDLimit)) + } + if e.InitialMaxData != 0 { + h.WriteToken(jsontext.String("initial_max_data")) + h.WriteToken(jsontext.Int(int64(e.InitialMaxData))) + } + if e.InitialMaxStreamDataBidiLocal != 0 { + h.WriteToken(jsontext.String("initial_max_stream_data_bidi_local")) + h.WriteToken(jsontext.Int(int64(e.InitialMaxStreamDataBidiLocal))) + } + if e.InitialMaxStreamDataBidiRemote != 0 { + h.WriteToken(jsontext.String("initial_max_stream_data_bidi_remote")) + h.WriteToken(jsontext.Int(int64(e.InitialMaxStreamDataBidiRemote))) + } + if e.InitialMaxStreamDataUni != 0 { + h.WriteToken(jsontext.String("initial_max_stream_data_uni")) + h.WriteToken(jsontext.Int(int64(e.InitialMaxStreamDataUni))) + } + if e.InitialMaxStreamsBidi != 0 { + h.WriteToken(jsontext.String("initial_max_streams_bidi")) + h.WriteToken(jsontext.Int(e.InitialMaxStreamsBidi)) + } + if e.InitialMaxStreamsUni != 0 { + h.WriteToken(jsontext.String("initial_max_streams_uni")) + h.WriteToken(jsontext.Int(e.InitialMaxStreamsUni)) + } + if e.PreferredAddress != nil { + h.WriteToken(jsontext.String("preferred_address")) + if err := e.PreferredAddress.encode(enc); err != nil { + return err + } + } + if e.MaxDatagramFrameSize != protocol.InvalidByteCount { + h.WriteToken(jsontext.String("max_datagram_frame_size")) + h.WriteToken(jsontext.Int(int64(e.MaxDatagramFrameSize))) + } + if e.EnableResetStreamAt { + h.WriteToken(jsontext.String("reset_stream_at")) + h.WriteToken(jsontext.True) + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +type PreferredAddress struct { + IPv4, IPv6 netip.AddrPort + ConnectionID protocol.ConnectionID + StatelessResetToken protocol.StatelessResetToken +} + +func (a PreferredAddress) encode(enc *jsontext.Encoder) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + if a.IPv4.IsValid() { + h.WriteToken(jsontext.String("ip_v4")) + h.WriteToken(jsontext.String(a.IPv4.Addr().String())) + h.WriteToken(jsontext.String("port_v4")) + h.WriteToken(jsontext.Uint(uint64(a.IPv4.Port()))) + } + if a.IPv6.IsValid() { + h.WriteToken(jsontext.String("ip_v6")) + h.WriteToken(jsontext.String(a.IPv6.Addr().String())) + h.WriteToken(jsontext.String("port_v6")) + h.WriteToken(jsontext.Uint(uint64(a.IPv6.Port()))) + } + h.WriteToken(jsontext.String("connection_id")) + h.WriteToken(jsontext.String(a.ConnectionID.String())) + h.WriteToken(jsontext.String("stateless_reset_token")) + h.WriteToken(jsontext.String(fmt.Sprintf("%x", a.StatelessResetToken))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +type LossTimerUpdated struct { + Type LossTimerUpdateType + TimerType TimerType + EncLevel EncryptionLevel + Time time.Time +} + +func (e LossTimerUpdated) Name() string { return "recovery:loss_timer_updated" } + +func (e LossTimerUpdated) Encode(enc *jsontext.Encoder, t time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("event_type")) + h.WriteToken(jsontext.String(string(e.Type))) + h.WriteToken(jsontext.String("timer_type")) + h.WriteToken(jsontext.String(string(e.TimerType))) + h.WriteToken(jsontext.String("packet_number_space")) + h.WriteToken(jsontext.String(encLevelToPacketNumberSpace(e.EncLevel))) + if e.Type == LossTimerUpdateTypeSet { + h.WriteToken(jsontext.String("delta")) + h.WriteToken(jsontext.Float(milliseconds(e.Time.Sub(t)))) + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +type eventLossTimerCanceled struct{} + +func (e eventLossTimerCanceled) Name() string { return "recovery:loss_timer_updated" } + +func (e eventLossTimerCanceled) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("event_type")) + h.WriteToken(jsontext.String("cancelled")) + h.WriteToken(jsontext.EndObject) + return h.err +} + +type CongestionStateUpdated struct { + State CongestionState +} + +func (e CongestionStateUpdated) Name() string { return "recovery:congestion_state_updated" } + +func (e CongestionStateUpdated) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("new")) + h.WriteToken(jsontext.String(e.State.String())) + h.WriteToken(jsontext.EndObject) + return h.err +} + +type ECNStateUpdated struct { + State ECNState + Trigger string +} + +func (e ECNStateUpdated) Name() string { return "recovery:ecn_state_updated" } + +func (e ECNStateUpdated) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("new")) + h.WriteToken(jsontext.String(string(e.State))) + if e.Trigger != "" { + h.WriteToken(jsontext.String("trigger")) + h.WriteToken(jsontext.String(e.Trigger)) + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +type ALPNInformation struct { + ChosenALPN string +} + +func (e ALPNInformation) Name() string { return "transport:alpn_information" } + +func (e ALPNInformation) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("chosen_alpn")) + h.WriteToken(jsontext.String(e.ChosenALPN)) + h.WriteToken(jsontext.EndObject) + return h.err +} + +// DebugEvent is a generic event that can be used to log arbitrary messages. +type DebugEvent struct { + EventName string + Message string +} + +func (e DebugEvent) Name() string { + if e.EventName == "" { + return "transport:debug" + } + return fmt.Sprintf("transport:%s", e.EventName) +} + +func (e DebugEvent) Encode(enc *jsontext.Encoder, _ time.Time) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("message")) + h.WriteToken(jsontext.String(e.Message)) + h.WriteToken(jsontext.EndObject) + return h.err +} diff --git a/vendor/github.com/quic-go/quic-go/qlog/frame.go b/vendor/github.com/quic-go/quic-go/qlog/frame.go new file mode 100644 index 0000000000..b66fedc954 --- /dev/null +++ b/vendor/github.com/quic-go/quic-go/qlog/frame.go @@ -0,0 +1,481 @@ +package qlog + +import ( + "encoding/hex" + + "github.com/quic-go/quic-go/internal/wire" + "github.com/quic-go/quic-go/qlogwriter/jsontext" +) + +type Frame struct { + Frame any +} + +type frames []Frame + +type ( + // An AckFrame is an ACK frame. + AckFrame = wire.AckFrame + // A ConnectionCloseFrame is a CONNECTION_CLOSE frame. + ConnectionCloseFrame = wire.ConnectionCloseFrame + // A DataBlockedFrame is a DATA_BLOCKED frame. + DataBlockedFrame = wire.DataBlockedFrame + // A HandshakeDoneFrame is a HANDSHAKE_DONE frame. + HandshakeDoneFrame = wire.HandshakeDoneFrame + // A MaxDataFrame is a MAX_DATA frame. + MaxDataFrame = wire.MaxDataFrame + // A MaxStreamDataFrame is a MAX_STREAM_DATA frame. + MaxStreamDataFrame = wire.MaxStreamDataFrame + // A MaxStreamsFrame is a MAX_STREAMS_FRAME. + MaxStreamsFrame = wire.MaxStreamsFrame + // A NewConnectionIDFrame is a NEW_CONNECTION_ID frame. + NewConnectionIDFrame = wire.NewConnectionIDFrame + // A NewTokenFrame is a NEW_TOKEN frame. + NewTokenFrame = wire.NewTokenFrame + // A PathChallengeFrame is a PATH_CHALLENGE frame. + PathChallengeFrame = wire.PathChallengeFrame + // A PathResponseFrame is a PATH_RESPONSE frame. + PathResponseFrame = wire.PathResponseFrame + // A PingFrame is a PING frame. + PingFrame = wire.PingFrame + // A ResetStreamFrame is a RESET_STREAM frame. + ResetStreamFrame = wire.ResetStreamFrame + // A RetireConnectionIDFrame is a RETIRE_CONNECTION_ID frame. + RetireConnectionIDFrame = wire.RetireConnectionIDFrame + // A StopSendingFrame is a STOP_SENDING frame. + StopSendingFrame = wire.StopSendingFrame + // A StreamsBlockedFrame is a STREAMS_BLOCKED frame. + 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 +) + +type AckRange = wire.AckRange + +// A CryptoFrame is a CRYPTO frame. +type CryptoFrame struct { + Offset int64 + Length int64 +} + +// A StreamFrame is a STREAM frame. +type StreamFrame struct { + StreamID StreamID + Offset int64 + Length int64 + Fin bool +} + +// A DatagramFrame is a DATAGRAM frame. +type DatagramFrame struct { + Length int64 +} + +func (fs frames) encode(enc *jsontext.Encoder) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginArray) + for _, f := range fs { + if err := f.Encode(enc); err != nil { + return err + } + } + h.WriteToken(jsontext.EndArray) + return h.err +} + +func (f Frame) Encode(enc *jsontext.Encoder) error { + switch frame := f.Frame.(type) { + case *PingFrame: + return encodePingFrame(enc, frame) + case *AckFrame: + return encodeAckFrame(enc, frame) + case *ResetStreamFrame: + return encodeResetStreamFrame(enc, frame) + case *StopSendingFrame: + return encodeStopSendingFrame(enc, frame) + case *CryptoFrame: + return encodeCryptoFrame(enc, frame) + case *NewTokenFrame: + return encodeNewTokenFrame(enc, frame) + case *StreamFrame: + return encodeStreamFrame(enc, frame) + case *MaxDataFrame: + return encodeMaxDataFrame(enc, frame) + case *MaxStreamDataFrame: + return encodeMaxStreamDataFrame(enc, frame) + case *MaxStreamsFrame: + return encodeMaxStreamsFrame(enc, frame) + case *DataBlockedFrame: + return encodeDataBlockedFrame(enc, frame) + case *StreamDataBlockedFrame: + return encodeStreamDataBlockedFrame(enc, frame) + case *StreamsBlockedFrame: + return encodeStreamsBlockedFrame(enc, frame) + case *NewConnectionIDFrame: + return encodeNewConnectionIDFrame(enc, frame) + case *RetireConnectionIDFrame: + return encodeRetireConnectionIDFrame(enc, frame) + case *PathChallengeFrame: + return encodePathChallengeFrame(enc, frame) + case *PathResponseFrame: + return encodePathResponseFrame(enc, frame) + case *ConnectionCloseFrame: + return encodeConnectionCloseFrame(enc, frame) + case *HandshakeDoneFrame: + return encodeHandshakeDoneFrame(enc, frame) + case *DatagramFrame: + return encodeDatagramFrame(enc, frame) + case *AckFrequencyFrame: + return encodeAckFrequencyFrame(enc, frame) + case *ImmediateAckFrame: + return encodeImmediateAckFrame(enc, frame) + default: + panic("unknown frame type") + } +} + +func encodePingFrame(enc *jsontext.Encoder, _ *PingFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("ping")) + h.WriteToken(jsontext.EndObject) + return h.err +} + +type ackRanges []wire.AckRange + +func (ars ackRanges) encode(enc *jsontext.Encoder) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginArray) + for _, r := range ars { + if err := ackRange(r).encode(enc); err != nil { + return err + } + } + h.WriteToken(jsontext.EndArray) + return h.err +} + +type ackRange wire.AckRange + +func (ar ackRange) encode(enc *jsontext.Encoder) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginArray) + h.WriteToken(jsontext.Int(int64(ar.Smallest))) + if ar.Smallest != ar.Largest { + h.WriteToken(jsontext.Int(int64(ar.Largest))) + } + h.WriteToken(jsontext.EndArray) + return h.err +} + +func encodeAckFrame(enc *jsontext.Encoder, f *AckFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("ack")) + if f.DelayTime > 0 { + h.WriteToken(jsontext.String("ack_delay")) + h.WriteToken(jsontext.Float(milliseconds(f.DelayTime))) + } + h.WriteToken(jsontext.String("acked_ranges")) + if err := ackRanges(f.AckRanges).encode(enc); err != nil { + return err + } + hasECN := f.ECT0 > 0 || f.ECT1 > 0 || f.ECNCE > 0 + if hasECN { + h.WriteToken(jsontext.String("ect0")) + h.WriteToken(jsontext.Uint(f.ECT0)) + h.WriteToken(jsontext.String("ect1")) + h.WriteToken(jsontext.Uint(f.ECT1)) + h.WriteToken(jsontext.String("ce")) + h.WriteToken(jsontext.Uint(f.ECNCE)) + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeResetStreamFrame(enc *jsontext.Encoder, f *ResetStreamFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + if f.ReliableSize > 0 { + h.WriteToken(jsontext.String("reset_stream_at")) + } else { + h.WriteToken(jsontext.String("reset_stream")) + } + h.WriteToken(jsontext.String("stream_id")) + h.WriteToken(jsontext.Int(int64(f.StreamID))) + h.WriteToken(jsontext.String("error_code")) + h.WriteToken(jsontext.Int(int64(f.ErrorCode))) + h.WriteToken(jsontext.String("final_size")) + h.WriteToken(jsontext.Int(int64(f.FinalSize))) + if f.ReliableSize > 0 { + h.WriteToken(jsontext.String("reliable_size")) + h.WriteToken(jsontext.Int(int64(f.ReliableSize))) + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeStopSendingFrame(enc *jsontext.Encoder, f *StopSendingFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("stop_sending")) + h.WriteToken(jsontext.String("stream_id")) + h.WriteToken(jsontext.Int(int64(f.StreamID))) + h.WriteToken(jsontext.String("error_code")) + h.WriteToken(jsontext.Int(int64(f.ErrorCode))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeCryptoFrame(enc *jsontext.Encoder, f *CryptoFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("crypto")) + h.WriteToken(jsontext.String("offset")) + h.WriteToken(jsontext.Int(f.Offset)) + h.WriteToken(jsontext.String("length")) + h.WriteToken(jsontext.Int(f.Length)) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeNewTokenFrame(enc *jsontext.Encoder, f *NewTokenFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("new_token")) + h.WriteToken(jsontext.String("token")) + if err := (Token{Raw: f.Token}).encode(enc); err != nil { + return err + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeStreamFrame(enc *jsontext.Encoder, f *StreamFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("stream")) + h.WriteToken(jsontext.String("stream_id")) + h.WriteToken(jsontext.Int(int64(f.StreamID))) + h.WriteToken(jsontext.String("offset")) + h.WriteToken(jsontext.Int(f.Offset)) + h.WriteToken(jsontext.String("length")) + h.WriteToken(jsontext.Int(f.Length)) + if f.Fin { + h.WriteToken(jsontext.String("fin")) + h.WriteToken(jsontext.True) + } + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeMaxDataFrame(enc *jsontext.Encoder, f *MaxDataFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("max_data")) + h.WriteToken(jsontext.String("maximum")) + h.WriteToken(jsontext.Int(int64(f.MaximumData))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeMaxStreamDataFrame(enc *jsontext.Encoder, f *MaxStreamDataFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("max_stream_data")) + h.WriteToken(jsontext.String("stream_id")) + h.WriteToken(jsontext.Int(int64(f.StreamID))) + h.WriteToken(jsontext.String("maximum")) + h.WriteToken(jsontext.Int(int64(f.MaximumStreamData))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeMaxStreamsFrame(enc *jsontext.Encoder, f *MaxStreamsFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("max_streams")) + h.WriteToken(jsontext.String("stream_type")) + h.WriteToken(jsontext.String(streamType(f.Type).String())) + h.WriteToken(jsontext.String("maximum")) + h.WriteToken(jsontext.Int(int64(f.MaxStreamNum))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeDataBlockedFrame(enc *jsontext.Encoder, f *DataBlockedFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("data_blocked")) + h.WriteToken(jsontext.String("limit")) + h.WriteToken(jsontext.Int(int64(f.MaximumData))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeStreamDataBlockedFrame(enc *jsontext.Encoder, f *StreamDataBlockedFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("stream_data_blocked")) + h.WriteToken(jsontext.String("stream_id")) + h.WriteToken(jsontext.Int(int64(f.StreamID))) + h.WriteToken(jsontext.String("limit")) + h.WriteToken(jsontext.Int(int64(f.MaximumStreamData))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeStreamsBlockedFrame(enc *jsontext.Encoder, f *StreamsBlockedFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("streams_blocked")) + h.WriteToken(jsontext.String("stream_type")) + h.WriteToken(jsontext.String(streamType(f.Type).String())) + h.WriteToken(jsontext.String("limit")) + h.WriteToken(jsontext.Int(int64(f.StreamLimit))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeNewConnectionIDFrame(enc *jsontext.Encoder, f *NewConnectionIDFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("new_connection_id")) + h.WriteToken(jsontext.String("sequence_number")) + h.WriteToken(jsontext.Uint(f.SequenceNumber)) + h.WriteToken(jsontext.String("retire_prior_to")) + h.WriteToken(jsontext.Uint(f.RetirePriorTo)) + h.WriteToken(jsontext.String("length")) + h.WriteToken(jsontext.Int(int64(f.ConnectionID.Len()))) + h.WriteToken(jsontext.String("connection_id")) + h.WriteToken(jsontext.String(f.ConnectionID.String())) + h.WriteToken(jsontext.String("stateless_reset_token")) + h.WriteToken(jsontext.String(hex.EncodeToString(f.StatelessResetToken[:]))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeRetireConnectionIDFrame(enc *jsontext.Encoder, f *RetireConnectionIDFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("retire_connection_id")) + h.WriteToken(jsontext.String("sequence_number")) + h.WriteToken(jsontext.Uint(f.SequenceNumber)) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodePathChallengeFrame(enc *jsontext.Encoder, f *PathChallengeFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("path_challenge")) + h.WriteToken(jsontext.String("data")) + h.WriteToken(jsontext.String(hex.EncodeToString(f.Data[:]))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodePathResponseFrame(enc *jsontext.Encoder, f *PathResponseFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("path_response")) + h.WriteToken(jsontext.String("data")) + h.WriteToken(jsontext.String(hex.EncodeToString(f.Data[:]))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeConnectionCloseFrame(enc *jsontext.Encoder, f *ConnectionCloseFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("connection_close")) + h.WriteToken(jsontext.String("error_space")) + errorSpace := "transport" + if f.IsApplicationError { + errorSpace = "application" + } + h.WriteToken(jsontext.String(errorSpace)) + errName := transportError(f.ErrorCode).String() + if len(errName) > 0 { + h.WriteToken(jsontext.String("error_code")) + h.WriteToken(jsontext.String(errName)) + } else { + h.WriteToken(jsontext.String("error_code")) + h.WriteToken(jsontext.Uint(f.ErrorCode)) + } + h.WriteToken(jsontext.String("raw_error_code")) + h.WriteToken(jsontext.Uint(f.ErrorCode)) + h.WriteToken(jsontext.String("reason")) + h.WriteToken(jsontext.String(f.ReasonPhrase)) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeHandshakeDoneFrame(enc *jsontext.Encoder, _ *HandshakeDoneFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("handshake_done")) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeDatagramFrame(enc *jsontext.Encoder, f *DatagramFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("datagram")) + h.WriteToken(jsontext.String("length")) + h.WriteToken(jsontext.Int(f.Length)) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeAckFrequencyFrame(enc *jsontext.Encoder, f *AckFrequencyFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("ack_frequency")) + h.WriteToken(jsontext.String("sequence_number")) + h.WriteToken(jsontext.Uint(f.SequenceNumber)) + h.WriteToken(jsontext.String("ack_eliciting_threshold")) + h.WriteToken(jsontext.Uint(f.AckElicitingThreshold)) + h.WriteToken(jsontext.String("request_max_ack_delay")) + h.WriteToken(jsontext.Float(milliseconds(f.RequestMaxAckDelay))) + h.WriteToken(jsontext.String("reordering_threshold")) + h.WriteToken(jsontext.Int(int64(f.ReorderingThreshold))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +func encodeImmediateAckFrame(enc *jsontext.Encoder, _ *ImmediateAckFrame) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("frame_type")) + h.WriteToken(jsontext.String("immediate_ack")) + h.WriteToken(jsontext.EndObject) + return h.err +} diff --git a/vendor/github.com/quic-go/quic-go/qlog/packet_header.go b/vendor/github.com/quic-go/quic-go/qlog/packet_header.go new file mode 100644 index 0000000000..149ebcb6a9 --- /dev/null +++ b/vendor/github.com/quic-go/quic-go/qlog/packet_header.go @@ -0,0 +1,96 @@ +package qlog + +import ( + "encoding/hex" + + "github.com/quic-go/quic-go/internal/protocol" + "github.com/quic-go/quic-go/qlogwriter/jsontext" +) + +type Token struct { + Raw []byte +} + +func (t Token) encode(enc *jsontext.Encoder) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("data")) + h.WriteToken(jsontext.String(hex.EncodeToString(t.Raw))) + h.WriteToken(jsontext.EndObject) + return h.err +} + +// PacketHeader is a QUIC packet header. +type PacketHeader struct { + PacketType PacketType + KeyPhaseBit KeyPhaseBit + PacketNumber PacketNumber + Version Version + SrcConnectionID ConnectionID + DestConnectionID ConnectionID + Token *Token +} + +func (h PacketHeader) encode(enc *jsontext.Encoder) error { + helper := encoderHelper{enc: enc} + helper.WriteToken(jsontext.BeginObject) + helper.WriteToken(jsontext.String("packet_type")) + helper.WriteToken(jsontext.String(string(h.PacketType))) + if h.PacketType != PacketTypeRetry && h.PacketType != PacketTypeVersionNegotiation && h.PacketType != "" && + h.PacketNumber != protocol.InvalidPacketNumber { + helper.WriteToken(jsontext.String("packet_number")) + helper.WriteToken(jsontext.Int(int64(h.PacketNumber))) + } + if h.Version != 0 { + helper.WriteToken(jsontext.String("version")) + helper.WriteToken(jsontext.String(version(h.Version).String())) + } + if h.PacketType != PacketType1RTT { + helper.WriteToken(jsontext.String("scil")) + helper.WriteToken(jsontext.Int(int64(h.SrcConnectionID.Len()))) + if h.SrcConnectionID.Len() > 0 { + helper.WriteToken(jsontext.String("scid")) + helper.WriteToken(jsontext.String(h.SrcConnectionID.String())) + } + } + helper.WriteToken(jsontext.String("dcil")) + helper.WriteToken(jsontext.Int(int64(h.DestConnectionID.Len()))) + if h.DestConnectionID.Len() > 0 { + helper.WriteToken(jsontext.String("dcid")) + helper.WriteToken(jsontext.String(h.DestConnectionID.String())) + } + if h.KeyPhaseBit == KeyPhaseZero || h.KeyPhaseBit == KeyPhaseOne { + helper.WriteToken(jsontext.String("key_phase_bit")) + helper.WriteToken(jsontext.String(h.KeyPhaseBit.String())) + } + if h.Token != nil { + helper.WriteToken(jsontext.String("token")) + if err := h.Token.encode(enc); err != nil { + return err + } + } + helper.WriteToken(jsontext.EndObject) + return helper.err +} + +type PacketHeaderVersionNegotiation struct { + SrcConnectionID ArbitraryLenConnectionID + DestConnectionID ArbitraryLenConnectionID +} + +func (h PacketHeaderVersionNegotiation) encode(enc *jsontext.Encoder) error { + helper := encoderHelper{enc: enc} + helper.WriteToken(jsontext.BeginObject) + helper.WriteToken(jsontext.String("packet_type")) + helper.WriteToken(jsontext.String("version_negotiation")) + helper.WriteToken(jsontext.String("scil")) + helper.WriteToken(jsontext.Int(int64(h.SrcConnectionID.Len()))) + helper.WriteToken(jsontext.String("scid")) + helper.WriteToken(jsontext.String(h.SrcConnectionID.String())) + helper.WriteToken(jsontext.String("dcil")) + helper.WriteToken(jsontext.Int(int64(h.DestConnectionID.Len()))) + helper.WriteToken(jsontext.String("dcid")) + helper.WriteToken(jsontext.String(h.DestConnectionID.String())) + helper.WriteToken(jsontext.EndObject) + return helper.err +} diff --git a/vendor/github.com/quic-go/quic-go/qlog/qlog_dir.go b/vendor/github.com/quic-go/quic-go/qlog/qlog_dir.go new file mode 100644 index 0000000000..83bb72b3fe --- /dev/null +++ b/vendor/github.com/quic-go/quic-go/qlog/qlog_dir.go @@ -0,0 +1,61 @@ +package qlog + +import ( + "bufio" + "context" + "fmt" + "log" + "os" + "slices" + "strings" + + "github.com/quic-go/quic-go/internal/utils" + "github.com/quic-go/quic-go/qlogwriter" +) + +// EventSchema is the qlog event schema for QUIC +const EventSchema = "urn:ietf:params:qlog:events:quic-12" + +// DefaultConnectionTracer creates a qlog file in the qlog directory specified by the QLOGDIR environment variable. +// File names are _.sqlog. +// Returns nil if QLOGDIR is not set. +func DefaultConnectionTracer(_ context.Context, isClient bool, connID ConnectionID) qlogwriter.Trace { + return defaultConnectionTracerWithSchemas(isClient, connID, []string{EventSchema}) +} + +func DefaultConnectionTracerWithSchemas(_ context.Context, isClient bool, connID ConnectionID, eventSchemas []string) qlogwriter.Trace { + if !slices.Contains(eventSchemas, EventSchema) { + eventSchemas = append([]string{EventSchema}, eventSchemas...) + } + return defaultConnectionTracerWithSchemas(isClient, connID, eventSchemas) +} + +func defaultConnectionTracerWithSchemas(isClient bool, connID ConnectionID, eventSchemas []string) qlogwriter.Trace { + qlogDir := os.Getenv("QLOGDIR") + if qlogDir == "" { + return nil + } + if _, err := os.Stat(qlogDir); os.IsNotExist(err) { + if err := os.MkdirAll(qlogDir, 0o755); err != nil { + log.Fatalf("failed to create qlog dir %s: %v", qlogDir, err) + } + } + label := "server" + if isClient { + label = "client" + } + path := fmt.Sprintf("%s/%s_%s.sqlog", strings.TrimRight(qlogDir, "/"), connID, label) + f, err := os.Create(path) + if err != nil { + log.Printf("Failed to create qlog file %s: %s", path, err.Error()) + return nil + } + fileSeq := qlogwriter.NewConnectionFileSeq( + utils.NewBufferedWriteCloser(bufio.NewWriter(f), f), + isClient, + connID, + eventSchemas, + ) + go fileSeq.Run() + return fileSeq +} diff --git a/vendor/github.com/quic-go/quic-go/qlog/types.go b/vendor/github.com/quic-go/quic-go/qlog/types.go new file mode 100644 index 0000000000..dfa4066d27 --- /dev/null +++ b/vendor/github.com/quic-go/quic-go/qlog/types.go @@ -0,0 +1,304 @@ +package qlog + +import ( + "fmt" + "hash/crc32" + + "github.com/quic-go/quic-go/internal/protocol" + "github.com/quic-go/quic-go/internal/qerr" +) + +type ( + ConnectionID = protocol.ConnectionID + ArbitraryLenConnectionID = protocol.ArbitraryLenConnectionID + Version = protocol.Version + PacketNumber = protocol.PacketNumber + EncryptionLevel = protocol.EncryptionLevel + KeyPhaseBit = protocol.KeyPhaseBit + KeyPhase = protocol.KeyPhase + StreamID = protocol.StreamID + TransportErrorCode = qerr.TransportErrorCode + ApplicationErrorCode = qerr.ApplicationErrorCode +) + +const ( + // KeyPhaseZero is key phase bit 0 + KeyPhaseZero = protocol.KeyPhaseZero + // KeyPhaseOne is key phase bit 1 + KeyPhaseOne = protocol.KeyPhaseOne +) + +// ECN represents the Explicit Congestion Notification value. +type ECN string + +const ( + // ECNUnsupported means that no ECN value was set / received + ECNUnsupported ECN = "" + // ECTNot is Not-ECT + ECTNot ECN = "Not-ECT" + // ECT0 is ECT(0) + ECT0 ECN = "ECT(0)" + // ECT1 is ECT(1) + ECT1 ECN = "ECT(1)" + // ECNCE is CE + ECNCE ECN = "CE" +) + +type Initiator string + +const ( + InitiatorLocal Initiator = "local" + InitiatorRemote Initiator = "remote" +) + +type streamType protocol.StreamType + +func (s streamType) String() string { + switch protocol.StreamType(s) { + case protocol.StreamTypeUni: + return "unidirectional" + case protocol.StreamTypeBidi: + return "bidirectional" + default: + return "unknown stream type" + } +} + +type version protocol.Version + +func (v version) String() string { + return fmt.Sprintf("%x", uint32(v)) +} + +func encLevelToPacketNumberSpace(encLevel protocol.EncryptionLevel) string { + switch encLevel { + case protocol.EncryptionInitial: + return "initial" + case protocol.EncryptionHandshake: + return "handshake" + case protocol.Encryption0RTT, protocol.Encryption1RTT: + return "application_data" + default: + return "unknown encryption level" + } +} + +// KeyType represents the type of cryptographic key used in QUIC connections. +type KeyType string + +const ( + // KeyTypeServerInitial represents the server's initial secret key. + KeyTypeServerInitial KeyType = "server_initial_secret" + // KeyTypeClientInitial represents the client's initial secret key. + KeyTypeClientInitial KeyType = "client_initial_secret" + // KeyTypeServerHandshake represents the server's handshake secret key. + KeyTypeServerHandshake KeyType = "server_handshake_secret" + // KeyTypeClientHandshake represents the client's handshake secret key. + KeyTypeClientHandshake KeyType = "client_handshake_secret" + // KeyTypeServer0RTT represents the server's 0-RTT secret key. + KeyTypeServer0RTT KeyType = "server_0rtt_secret" + // KeyTypeClient0RTT represents the client's 0-RTT secret key. + KeyTypeClient0RTT KeyType = "client_0rtt_secret" + // KeyTypeServer1RTT represents the server's 1-RTT secret key. + KeyTypeServer1RTT KeyType = "server_1rtt_secret" + // KeyTypeClient1RTT represents the client's 1-RTT secret key. + KeyTypeClient1RTT KeyType = "client_1rtt_secret" +) + +// KeyUpdateTrigger describes what caused a key update event. +type KeyUpdateTrigger string + +const ( + // KeyUpdateTLS indicates the key update was triggered by TLS. + KeyUpdateTLS KeyUpdateTrigger = "tls" + // KeyUpdateRemote indicates the key update was triggered by the remote peer. + KeyUpdateRemote KeyUpdateTrigger = "remote_update" + // KeyUpdateLocal indicates the key update was triggered locally. + KeyUpdateLocal KeyUpdateTrigger = "local_update" +) + +type transportError uint64 + +func (e transportError) String() string { + switch qerr.TransportErrorCode(e) { + case qerr.NoError: + return "no_error" + case qerr.InternalError: + return "internal_error" + case qerr.ConnectionRefused: + return "connection_refused" + case qerr.FlowControlError: + return "flow_control_error" + case qerr.StreamLimitError: + return "stream_limit_error" + case qerr.StreamStateError: + return "stream_state_error" + case qerr.FinalSizeError: + return "final_size_error" + case qerr.FrameEncodingError: + return "frame_encoding_error" + case qerr.TransportParameterError: + return "transport_parameter_error" + case qerr.ConnectionIDLimitError: + return "connection_id_limit_error" + case qerr.ProtocolViolation: + return "protocol_violation" + case qerr.InvalidToken: + return "invalid_token" + case qerr.ApplicationErrorErrorCode: + return "application_error" + case qerr.CryptoBufferExceeded: + return "crypto_buffer_exceeded" + case qerr.KeyUpdateError: + return "key_update_error" + case qerr.AEADLimitReached: + return "aead_limit_reached" + case qerr.NoViablePathError: + return "no_viable_path" + default: + return "" + } +} + +type PacketType string + +const ( + // PacketTypeInitial represents an Initial packet + PacketTypeInitial PacketType = "initial" + // PacketTypeHandshake represents a Handshake packet + PacketTypeHandshake PacketType = "handshake" + // PacketTypeRetry represents a Retry packet + PacketTypeRetry PacketType = "retry" + // PacketType0RTT represents a 0-RTT packet + PacketType0RTT PacketType = "0RTT" + // PacketTypeVersionNegotiation represents a Version Negotiation packet + PacketTypeVersionNegotiation PacketType = "version_negotiation" + // PacketTypeStatelessReset represents a Stateless Reset packet + PacketTypeStatelessReset PacketType = "stateless_reset" + // PacketType1RTT represents a 1-RTT packet + PacketType1RTT PacketType = "1RTT" + // // PacketTypeNotDetermined represents a packet type that could not be determined + // PacketTypeNotDetermined packetType = "" +) + +func EncryptionLevelToPacketType(l EncryptionLevel) PacketType { + switch l { + case protocol.EncryptionInitial: + return PacketTypeInitial + case protocol.EncryptionHandshake: + return PacketTypeHandshake + case protocol.Encryption0RTT: + return PacketType0RTT + case protocol.Encryption1RTT: + return PacketType1RTT + default: + panic(fmt.Sprintf("unknown encryption level: %d", l)) + } +} + +type PacketLossReason string + +const ( + // PacketLossReorderingThreshold is used when a packet is declared lost due to reordering threshold + PacketLossReorderingThreshold PacketLossReason = "reordering_threshold" + // PacketLossTimeThreshold is used when a packet is declared lost due to time threshold + PacketLossTimeThreshold PacketLossReason = "time_threshold" +) + +type PacketDropReason string + +const ( + // PacketDropKeyUnavailable is used when a packet is dropped because keys are unavailable + PacketDropKeyUnavailable PacketDropReason = "key_unavailable" + // PacketDropUnknownConnectionID is used when a packet is dropped because the connection ID is unknown + PacketDropUnknownConnectionID PacketDropReason = "unknown_connection_id" + // PacketDropHeaderParseError is used when a packet is dropped because header parsing failed + PacketDropHeaderParseError PacketDropReason = "header_parse_error" + // PacketDropPayloadDecryptError is used when a packet is dropped because decrypting the payload failed + PacketDropPayloadDecryptError PacketDropReason = "payload_decrypt_error" + // PacketDropProtocolViolation is used when a packet is dropped due to a protocol violation + PacketDropProtocolViolation PacketDropReason = "protocol_violation" + // PacketDropDOSPrevention is used when a packet is dropped to mitigate a DoS attack + PacketDropDOSPrevention PacketDropReason = "dos_prevention" + // PacketDropUnsupportedVersion is used when a packet is dropped because the version is not supported + PacketDropUnsupportedVersion PacketDropReason = "unsupported_version" + // PacketDropUnexpectedPacket is used when an unexpected packet is received + PacketDropUnexpectedPacket PacketDropReason = "unexpected_packet" + // PacketDropUnexpectedSourceConnectionID is used when a packet with an unexpected source connection ID is received + PacketDropUnexpectedSourceConnectionID PacketDropReason = "unexpected_source_connection_id" + // PacketDropUnexpectedVersion is used when a packet with an unexpected version is received + PacketDropUnexpectedVersion PacketDropReason = "unexpected_version" + // PacketDropDuplicate is used when a duplicate packet is received + PacketDropDuplicate PacketDropReason = "duplicate" +) + +type LossTimerUpdateType string + +const ( + LossTimerUpdateTypeSet LossTimerUpdateType = "set" + LossTimerUpdateTypeExpired LossTimerUpdateType = "expired" + LossTimerUpdateTypeCancelled LossTimerUpdateType = "cancelled" +) + +type TimerType string + +const ( + // TimerTypeACK represents an ACK timer + TimerTypeACK TimerType = "ack" + // TimerTypePTO represents a PTO (Probe Timeout) timer + TimerTypePTO TimerType = "pto" + // TimerTypePathProbe represents a path probe timer + TimerTypePathProbe TimerType = "path_probe" +) + +type CongestionState string + +const ( + // CongestionStateSlowStart is the slow start phase of Reno / Cubic + CongestionStateSlowStart CongestionState = "slow_start" + // CongestionStateCongestionAvoidance is the congestion avoidance phase of Reno / Cubic + CongestionStateCongestionAvoidance CongestionState = "congestion_avoidance" + // CongestionStateRecovery is the recovery phase of Reno / Cubic + CongestionStateRecovery CongestionState = "recovery" + // CongestionStateApplicationLimited means that the congestion controller is application limited + CongestionStateApplicationLimited CongestionState = "application_limited" +) + +func (s CongestionState) String() string { + return string(s) +} + +// ECNState is the state of the ECN state machine (see Appendix A.4 of RFC 9000) +type ECNState string + +const ( + // ECNStateTesting is the testing state + ECNStateTesting ECNState = "testing" + // ECNStateUnknown is the unknown state + ECNStateUnknown ECNState = "unknown" + // ECNStateFailed is the failed state + ECNStateFailed ECNState = "failed" + // ECNStateCapable is the capable state + ECNStateCapable ECNState = "capable" +) + +type ConnectionCloseTrigger string + +const ( + // IdleTimeout indicates the connection was closed due to idle timeout + ConnectionCloseTriggerIdleTimeout ConnectionCloseTrigger = "idle_timeout" + // Application indicates the connection was closed by the application + ConnectionCloseTriggerApplication ConnectionCloseTrigger = "application" + // VersionMismatch indicates the connection was closed due to a QUIC version mismatch + ConnectionCloseTriggerVersionMismatch ConnectionCloseTrigger = "version_mismatch" + // StatelessReset indicates the connection was closed due to receiving a stateless reset from the peer + ConnectionCloseTriggerStatelessReset ConnectionCloseTrigger = "stateless_reset" +) + +// DatagramID is a unique identifier for a datagram +type DatagramID uint32 + +// CalculateDatagramID computes a DatagramID for a given packet +func CalculateDatagramID(packet []byte) DatagramID { + return DatagramID(crc32.ChecksumIEEE(packet)) +} diff --git a/vendor/github.com/quic-go/quic-go/qlogwriter/jsontext/encoder.go b/vendor/github.com/quic-go/quic-go/qlogwriter/jsontext/encoder.go new file mode 100644 index 0000000000..4f715bbdeb --- /dev/null +++ b/vendor/github.com/quic-go/quic-go/qlogwriter/jsontext/encoder.go @@ -0,0 +1,324 @@ +// Package jsontext provides a fast JSON encoder providing only the necessary features +// for qlog encoding. No efforts are made to add any features beyond qlog's requirements. +// +// The API aims to be compatible with the standard library's encoding/json/jsontext package. +package jsontext + +import ( + "fmt" + "io" + "strconv" + "unsafe" +) + +type kind uint8 + +const ( + kindString kind = iota + kindInt + kindUint + kindFloat + kindBool + kindNull + kindObjectStart + kindObjectEnd + kindArrayStart + kindArrayEnd +) + +// Token represents a JSON token. +type Token struct { + kind kind + str string + i64 int64 + u64 uint64 + f64 float64 + b bool +} + +// String creates a string token. +func String(s string) Token { + return Token{kind: kindString, str: s} +} + +// Int creates an int token. +func Int(i int64) Token { + return Token{kind: kindInt, i64: i} +} + +// Uint creates a uint token. +func Uint(u uint64) Token { + return Token{kind: kindUint, u64: u} +} + +// Float creates a float token. +func Float(f float64) Token { + return Token{kind: kindFloat, f64: f} +} + +// Bool creates a bool token. +func Bool(b bool) Token { + return Token{kind: kindBool, b: b} +} + +// Null is a null token. +var Null Token = Token{kind: kindNull} + +// BeginObject is the begin object token. +var BeginObject Token = Token{kind: kindObjectStart} + +// EndObject is the end object token. +var EndObject Token = Token{kind: kindObjectEnd} + +// BeginArray is the begin array token. +var BeginArray Token = Token{kind: kindArrayStart} + +// EndArray is the end array token. +var EndArray Token = Token{kind: kindArrayEnd} + +// True is a true token. +var True Token = Bool(true) + +// False is a false token. +var False Token = Bool(false) + +var hexDigits = [16]byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'} + +var ( + commaByte = []byte(",") + quoteByte = []byte(`"`) + colonByte = []byte(":") + trueByte = []byte("true") + falseByte = []byte("false") + nullByte = []byte("null") + openObjectByte = []byte("{") + closeObjectByte = []byte("}") + openArrayByte = []byte("[") + closeArrayByte = []byte("]") + newlineByte = []byte("\n") + escapeQuote = []byte(`\"`) + escapeBackslash = []byte(`\\`) + escapeBackspace = []byte(`\b`) + escapeFormfeed = []byte(`\f`) + escapeNewline = []byte(`\n`) + escapeCarriage = []byte(`\r`) + escapeTab = []byte(`\t`) + escapeUnicode = []byte(`\u00`) +) + +type context struct { + isObject bool + needsComma bool + expectKey bool +} + +// Encoder encodes JSON to an io.Writer. +type Encoder struct { + w io.Writer + buf [64]byte // scratch buffer for number formatting + stack []context +} + +// NewEncoder creates a new Encoder. +func NewEncoder(w io.Writer) *Encoder { + stack := make([]context, 0, 8) + stack = append(stack, context{isObject: false, needsComma: false, expectKey: false}) + return &Encoder{ + w: w, + stack: stack, + } +} + +// WriteToken writes a token to the encoder. +func (e *Encoder) WriteToken(t Token) error { + if len(e.stack) == 0 { + return fmt.Errorf("empty stack") + } + curr := &e.stack[len(e.stack)-1] + isClosing := t.kind == kindObjectEnd || t.kind == kindArrayEnd + if !isClosing && curr.needsComma { + if _, err := e.w.Write(commaByte); err != nil { + return err + } + curr.needsComma = false + } + var err error + switch t.kind { + case kindString: + data := stringToBytes(t.str) + needsEscape := false + for _, b := range data { + if b == '"' || b == '\\' || b < 0x20 { + needsEscape = true + break + } + } + if !needsEscape { + if _, err = e.w.Write(quoteByte); err != nil { + return err + } + if _, err = e.w.Write(data); err != nil { + return err + } + if _, err = e.w.Write(quoteByte); err != nil { + return err + } + } else { + if _, err = e.w.Write(quoteByte); err != nil { + return err + } + for i := 0; i < len(t.str); i++ { + c := t.str[i] + switch c { + case '"': + if _, err = e.w.Write(escapeQuote); err != nil { + return err + } + case '\\': + if _, err = e.w.Write(escapeBackslash); err != nil { + return err + } + case '\b': + if _, err = e.w.Write(escapeBackspace); err != nil { + return err + } + case '\f': + if _, err = e.w.Write(escapeFormfeed); err != nil { + return err + } + case '\n': + if _, err = e.w.Write(escapeNewline); err != nil { + return err + } + case '\r': + if _, err = e.w.Write(escapeCarriage); err != nil { + return err + } + case '\t': + if _, err = e.w.Write(escapeTab); err != nil { + return err + } + default: + if c < 0x20 { + if _, err = e.w.Write(escapeUnicode); err != nil { + return err + } + if _, err = e.w.Write([]byte{hexDigits[c>>4], hexDigits[c&0xf]}); err != nil { + return err + } + } else { + if _, err = e.w.Write([]byte{c}); err != nil { + return err + } + } + } + } + if _, err = e.w.Write(quoteByte); err != nil { + return err + } + } + if curr.isObject { + if curr.expectKey { + // key + if _, err = e.w.Write(colonByte); err != nil { + return err + } + curr.expectKey = false + return nil // do not call afterValue for keys + } else { + // value + e.afterValue() + } + } else { + e.afterValue() + } + case kindInt: + b := strconv.AppendInt(e.buf[:0], t.i64, 10) + if _, err = e.w.Write(b); err != nil { + return err + } + e.afterValue() + case kindUint: + b := strconv.AppendUint(e.buf[:0], t.u64, 10) + if _, err = e.w.Write(b); err != nil { + return err + } + e.afterValue() + case kindFloat: + b := strconv.AppendFloat(e.buf[:0], t.f64, 'g', -1, 64) + if _, err = e.w.Write(b); err != nil { + return err + } + e.afterValue() + case kindBool: + if t.b { + if _, err = e.w.Write(trueByte); err != nil { + return err + } + } else { + if _, err = e.w.Write(falseByte); err != nil { + return err + } + } + e.afterValue() + case kindNull: + if _, err = e.w.Write(nullByte); err != nil { + return err + } + e.afterValue() + case kindObjectStart: + if _, err = e.w.Write(openObjectByte); err != nil { + return err + } + e.stack = append(e.stack, context{isObject: true, needsComma: false, expectKey: true}) + return nil + case kindObjectEnd: + if _, err = e.w.Write(closeObjectByte); err != nil { + return err + } + e.stack = e.stack[:len(e.stack)-1] + e.afterValue() + if len(e.stack) == 1 { + if _, err = e.w.Write(newlineByte); err != nil { + return err + } + } + return nil + case kindArrayStart: + if _, err = e.w.Write(openArrayByte); err != nil { + return err + } + e.stack = append(e.stack, context{isObject: false, needsComma: false, expectKey: false}) + return nil + case kindArrayEnd: + if _, err = e.w.Write(closeArrayByte); err != nil { + return err + } + e.stack = e.stack[:len(e.stack)-1] + e.afterValue() + if len(e.stack) == 1 { + if _, err = e.w.Write(newlineByte); err != nil { + return err + } + } + return nil + default: + return fmt.Errorf("unknown token kind") + } + return err +} + +// afterValue updates the state after encoding a value +func (e *Encoder) afterValue() { + if len(e.stack) > 1 { + curr := &e.stack[len(e.stack)-1] + curr.needsComma = true + if curr.isObject { + curr.expectKey = true + } + } +} + +func stringToBytes(s string) []byte { + return unsafe.Slice(unsafe.StringData(s), len(s)) +} diff --git a/vendor/github.com/quic-go/quic-go/qlogwriter/trace.go b/vendor/github.com/quic-go/quic-go/qlogwriter/trace.go new file mode 100644 index 0000000000..eebcbaadc3 --- /dev/null +++ b/vendor/github.com/quic-go/quic-go/qlogwriter/trace.go @@ -0,0 +1,124 @@ +package qlogwriter + +import ( + "runtime/debug" + "time" + + "github.com/quic-go/quic-go/internal/protocol" + "github.com/quic-go/quic-go/qlogwriter/jsontext" +) + +type ConnectionID = protocol.ConnectionID + +// Setting of this only works when quic-go is used as a library. +// When building a binary from this repository, the version can be set using the following go build flag: +// -ldflags="-X github.com/quic-go/quic-go/qlogwriter.quicGoVersion=foobar" +var quicGoVersion = "(devel)" + +func init() { + if quicGoVersion != "(devel)" { // variable set by ldflags + return + } + info, ok := debug.ReadBuildInfo() + if !ok { // no build info available. This happens when quic-go is not used as a library. + return + } + for _, d := range info.Deps { + if d.Path == "github.com/quic-go/quic-go" { + quicGoVersion = d.Version + if d.Replace != nil { + if len(d.Replace.Version) > 0 { + quicGoVersion = d.Version + } else { + quicGoVersion += " (replaced)" + } + } + break + } + } +} + +type encoderHelper struct { + enc *jsontext.Encoder + err error +} + +func (h *encoderHelper) WriteToken(t jsontext.Token) { + if h.err != nil { + return + } + h.err = h.enc.WriteToken(t) +} + +type traceHeader struct { + VantagePointType string + GroupID *ConnectionID + ReferenceTime time.Time + EventSchemas []string +} + +func (l traceHeader) Encode(enc *jsontext.Encoder) error { + h := encoderHelper{enc: enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("file_schema")) + h.WriteToken(jsontext.String("urn:ietf:params:qlog:file:sequential")) + h.WriteToken(jsontext.String("serialization_format")) + h.WriteToken(jsontext.String("application/qlog+json-seq")) + h.WriteToken(jsontext.String("title")) + h.WriteToken(jsontext.String("quic-go qlog")) + h.WriteToken(jsontext.String("code_version")) + h.WriteToken(jsontext.String(quicGoVersion)) + + h.WriteToken(jsontext.String("trace")) + // trace + h.WriteToken(jsontext.BeginObject) + if len(l.EventSchemas) > 0 { + h.WriteToken(jsontext.String("event_schemas")) + h.WriteToken(jsontext.BeginArray) + for _, schema := range l.EventSchemas { + h.WriteToken(jsontext.String(schema)) + } + h.WriteToken(jsontext.EndArray) + } + + h.WriteToken(jsontext.String("vantage_point")) + // -- vantage_point + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("type")) + h.WriteToken(jsontext.String(l.VantagePointType)) + // -- end vantage_point + h.WriteToken(jsontext.EndObject) + + h.WriteToken(jsontext.String("common_fields")) + // -- common_fields + h.WriteToken(jsontext.BeginObject) + if l.GroupID != nil { + h.WriteToken(jsontext.String("group_id")) + h.WriteToken(jsontext.String(l.GroupID.String())) + } + h.WriteToken(jsontext.String("reference_time")) + // ---- reference_time + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("clock_type")) + h.WriteToken(jsontext.String("monotonic")) + h.WriteToken(jsontext.String("epoch")) + h.WriteToken(jsontext.String("unknown")) + h.WriteToken(jsontext.String("wall_clock_time")) + h.WriteToken(jsontext.String(l.ReferenceTime.Format(time.RFC3339Nano))) + // ---- end reference_time + h.WriteToken(jsontext.EndObject) + // -- end common_fields + h.WriteToken(jsontext.EndObject) + // end trace + h.WriteToken(jsontext.EndObject) + + // The following fields are not required by the qlog draft anymore, + // but qvis still requires them to be present. + h.WriteToken(jsontext.String("qlog_format")) + h.WriteToken(jsontext.String("JSON-SEQ")) + h.WriteToken(jsontext.String("qlog_version")) + h.WriteToken(jsontext.String("0.3")) + + h.WriteToken(jsontext.EndObject) + return h.err +} diff --git a/vendor/github.com/quic-go/quic-go/qlogwriter/writer.go b/vendor/github.com/quic-go/quic-go/qlogwriter/writer.go new file mode 100644 index 0000000000..d728f596fa --- /dev/null +++ b/vendor/github.com/quic-go/quic-go/qlogwriter/writer.go @@ -0,0 +1,229 @@ +package qlogwriter + +import ( + "bytes" + "fmt" + "io" + "log" + "slices" + "sync" + "time" + + "github.com/quic-go/quic-go/qlogwriter/jsontext" +) + +// Trace represents a qlog trace that can have multiple event producers. +// Each producer can record events to the trace independently. +// When the last producer is closed, the underlying trace is closed as well. +type Trace interface { + // AddProducer creates a new Recorder for this trace. + // Each Recorder can record events independently. + AddProducer() Recorder + + // SupportsSchemas returns true if the trace supports the given schema. + SupportsSchemas(schema string) bool +} + +// Recorder is used to record events to a qlog trace. +// It is safe for concurrent use by multiple goroutines. +type Recorder interface { + // RecordEvent records a single Event to the trace. + // It must not be called after Close. + RecordEvent(Event) + // Close signals that this producer is done recording events. + // When all producers are closed, the underlying trace is closed. + // It must not be called concurrently with RecordEvent. + io.Closer +} + +// Event represents a qlog event that can be encoded to JSON. +// Each event must provide its name and a method to encode itself using a jsontext.Encoder. +type Event interface { + // Name returns the name of the event, as it should appear in the qlog output + Name() string + // Encode writes the event's data to the provided jsontext.Encoder + Encode(encoder *jsontext.Encoder, eventTime time.Time) error +} + +// RecordSeparator is the record separator byte for the JSON-SEQ format +const RecordSeparator byte = 0x1e + +var recordSeparator = []byte{RecordSeparator} + +type event struct { + Time time.Time + Event Event +} + +const eventChanSize = 50 + +// FileSeq represents a qlog trace using the JSON-SEQ format, +// https://www.ietf.org/archive/id/draft-ietf-quic-qlog-main-schema-12.html#section-5 +// qlog event producers can be created by calling AddProducer. +// The underlying io.WriteCloser is closed when the last producer is removed. +type FileSeq struct { + w io.WriteCloser + enc *jsontext.Encoder + referenceTime time.Time + + runStopped chan struct{} + encodeErr error + events chan event + done chan struct{} + + mx sync.Mutex + producers int + closed bool + + eventSchemas []string +} + +var _ Trace = &FileSeq{} + +// NewFileSeq creates a new JSON-SEQ qlog trace to log transport events. +func NewFileSeq(w io.WriteCloser) *FileSeq { + return newFileSeq(w, "transport", nil, nil) +} + +// NewConnectionFileSeq creates a new qlog trace to log connection events. +func NewConnectionFileSeq(w io.WriteCloser, isClient bool, odcid ConnectionID, eventSchemas []string) *FileSeq { + pers := "server" + if isClient { + pers = "client" + } + return newFileSeq(w, pers, &odcid, eventSchemas) +} + +func newFileSeq(w io.WriteCloser, pers string, odcid *ConnectionID, eventSchemas []string) *FileSeq { + now := time.Now() + buf := &bytes.Buffer{} + enc := jsontext.NewEncoder(buf) + if _, err := buf.Write(recordSeparator); err != nil { + panic(fmt.Sprintf("qlog encoding into a bytes.Buffer failed: %s", err)) + } + if err := (&traceHeader{ + VantagePointType: pers, + GroupID: odcid, + ReferenceTime: now, + EventSchemas: eventSchemas, + }).Encode(enc); err != nil { + panic(fmt.Sprintf("qlog encoding into a bytes.Buffer failed: %s", err)) + } + _, encodeErr := w.Write(buf.Bytes()) + + return &FileSeq{ + w: w, + referenceTime: now, + enc: jsontext.NewEncoder(w), + runStopped: make(chan struct{}), + encodeErr: encodeErr, + events: make(chan event, eventChanSize), + done: make(chan struct{}), + eventSchemas: eventSchemas, + } +} + +func (t *FileSeq) SupportsSchemas(schema string) bool { + return slices.Contains(t.eventSchemas, schema) +} + +func (t *FileSeq) AddProducer() Recorder { + t.mx.Lock() + defer t.mx.Unlock() + if t.closed { + return nil + } + + t.producers++ + + return &Writer{t: t} +} + +func (t *FileSeq) record(eventTime time.Time, details Event) { + t.mx.Lock() + + if t.closed { + t.mx.Unlock() + return + } + t.mx.Unlock() + + t.events <- event{Time: eventTime, Event: details} +} + +func (t *FileSeq) Run() { + defer close(t.runStopped) + + for { + select { + case <-t.done: + for { + select { + case e := <-t.events: + t.encodeEvent(e) + default: + if t.encodeErr != nil { + log.Printf("exporting qlog failed: %s\n", t.encodeErr) + } + return + } + } + case e := <-t.events: + t.encodeEvent(e) + } + } +} + +func (t *FileSeq) encodeEvent(e event) { + if t.encodeErr != nil { + return + } + if _, err := t.w.Write(recordSeparator); err != nil { + t.encodeErr = err + return + } + h := encoderHelper{enc: t.enc} + h.WriteToken(jsontext.BeginObject) + h.WriteToken(jsontext.String("time")) + h.WriteToken(jsontext.Float(float64(e.Time.Sub(t.referenceTime).Nanoseconds()) / 1e6)) + h.WriteToken(jsontext.String("name")) + h.WriteToken(jsontext.String(e.Event.Name())) + h.WriteToken(jsontext.String("data")) + if err := e.Event.Encode(t.enc, e.Time); err != nil { + t.encodeErr = err + return + } + h.WriteToken(jsontext.EndObject) + if h.err != nil { + t.encodeErr = h.err + } +} + +func (t *FileSeq) removeProducer() { + t.mx.Lock() + t.producers-- + last := t.producers == 0 + if last { + t.closed = true + } + t.mx.Unlock() + + if last { + close(t.done) + <-t.runStopped // wait for Run to drain and exit + _ = t.w.Close() + } +} + +type Writer struct { + t *FileSeq +} + +func (w *Writer) Close() error { + w.t.removeProducer() + return nil +} + +func (w *Writer) RecordEvent(ev Event) { + w.t.record(time.Now(), ev) +} 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 5c34536454..8ea10acdab 100644 --- a/vendor/github.com/quic-go/quic-go/quicvarint/io.go +++ b/vendor/github.com/quic-go/quic-go/quicvarint/io.go @@ -13,6 +13,31 @@ type Reader interface { var _ Reader = &bytes.Reader{} +// A Peeker can peek bytes without consuming them. +type Peeker interface { + Peek(b []byte) (int, error) +} + +// Peek reads a number in the QUIC varint format without consuming bytes. +func Peek(p Peeker) (uint64, error) { + var b [8]byte + + // first peek 1 byte to determine the varint length + if _, err := p.Peek(b[:1]); err != nil { + return 0, err + } + + l := 1 << (b[0] >> 6) // 1, 2, 4, or 8 bytes + if l == 1 { + return uint64(b[0] & 0b00111111), nil + } + if _, err := p.Peek(b[:l]); err != nil { + return 0, err + } + val, _, err := Parse(b[:l]) + return val, err +} + type byteReader struct { io.Reader } @@ -58,7 +83,7 @@ type byteWriter struct { var _ Writer = &byteWriter{} // NewWriter returns a Writer for w. -// If r already implements both io.ByteWriter and io.Writer, NewWriter returns w. +// If w already implements both io.ByteWriter and io.Writer, NewWriter returns w. // Otherwise, w is wrapped to add the missing interfaces. func NewWriter(w io.Writer) Writer { if w, ok := w.(Writer); ok { diff --git a/vendor/github.com/quic-go/quic-go/quicvarint/varint.go b/vendor/github.com/quic-go/quic-go/quicvarint/varint.go index 0a19eaafe9..52fb153c7a 100644 --- a/vendor/github.com/quic-go/quic-go/quicvarint/varint.go +++ b/vendor/github.com/quic-go/quic-go/quicvarint/varint.go @@ -20,6 +20,14 @@ const ( maxVarInt8 = 4611686018427387903 ) +type varintLengthError struct { + Num uint64 +} + +func (e *varintLengthError) Error() string { + return fmt.Sprintf("value doesn't fit into 62 bits: %d", e.Num) +} + // Read reads a number in the QUIC varint format from r. func Read(r io.ByteReader) (uint64, error) { firstByte, err := r.ReadByte() @@ -118,7 +126,7 @@ func Append(b []byte, i uint64) []byte { uint8(i >> 24), uint8(i >> 16), uint8(i >> 8), uint8(i), }...) } - panic(fmt.Sprintf("%#x doesn't fit into 62 bits", i)) + panic(&varintLengthError{Num: i}) } // AppendWithLen append i in the QUIC varint format with the desired length. @@ -151,6 +159,8 @@ func AppendWithLen(b []byte, i uint64, length int) []byte { } // Len determines the number of bytes that will be needed to write the number i. +// +//gcassert:inline func Len(i uint64) int { if i <= maxVarInt1 { return 1 @@ -166,8 +176,5 @@ func Len(i uint64) int { } // Don't use a fmt.Sprintf here to format the error message. // The function would then exceed the inlining budget. - panic(struct { - message string - num uint64 - }{"value doesn't fit into 62 bits: ", i}) + panic(&varintLengthError{Num: i}) } 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 b875ecded7..10a8777f45 100644 --- a/vendor/github.com/quic-go/quic-go/receive_stream.go +++ b/vendor/github.com/quic-go/quic-go/receive_stream.go @@ -131,7 +131,7 @@ func (s *ReceiveStream) readImpl(p []byte) (hasStreamWindowUpdate bool, hasConnW s.errorRead = true return false, false, 0, io.EOF } - if s.cancelledLocally || (s.cancelledRemotely && s.readPos >= s.reliableSize) { + if s.cancelledLocally || s.isRemoteCancellationEffective() { s.errorRead = true return false, false, 0, s.cancelErr } @@ -154,22 +154,14 @@ func (s *ReceiveStream) readImpl(p []byte) (hasStreamWindowUpdate bool, hasConnW if s.closeForShutdownErr != nil { return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, s.closeForShutdownErr } - if s.cancelledLocally || (s.cancelledRemotely && s.readPos >= s.reliableSize) { + if s.cancelledLocally || s.isRemoteCancellationEffective() { s.errorRead = true return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, s.cancelErr } deadline := s.deadline - if !deadline.IsZero() { - if !monotime.Now().Before(deadline) { - return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, errDeadline - } - if deadlineTimer == nil { - deadlineTimer = time.NewTimer(monotime.Until(deadline)) - defer deadlineTimer.Stop() - } else { - deadlineTimer.Reset(monotime.Until(deadline)) - } + if !deadline.IsZero() && !monotime.Now().Before(deadline) { + return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, errDeadline } if s.currentFrame != nil || s.currentFrameIsLast { @@ -180,15 +172,19 @@ func (s *ReceiveStream) readImpl(p []byte) (hasStreamWindowUpdate bool, hasConnW if deadline.IsZero() { <-s.readChan } else { + if deadlineTimer == nil { + deadlineTimer = time.NewTimer(monotime.Until(deadline)) + defer deadlineTimer.Stop() + } else { + deadlineTimer.Reset(monotime.Until(deadline)) + } select { case <-s.readChan: case <-deadlineTimer.C: } } s.mutex.Lock() - if s.currentFrame == nil { - s.dequeueNextFrame() - } + s.dequeueNextFrame() } if bytesRead > len(p) { @@ -201,7 +197,7 @@ func (s *ReceiveStream) readImpl(p []byte) (hasStreamWindowUpdate bool, hasConnW // when a RESET_STREAM was received, the flow controller was already // informed about the final offset for this stream - if !s.cancelledRemotely || s.readPos < s.reliableSize { + if !s.isRemoteCancellationEffective() { hasStream, hasConn := s.flowController.AddBytesRead(protocol.ByteCount(m)) if hasStream { s.queuedMaxStreamData = true @@ -216,7 +212,7 @@ func (s *ReceiveStream) readImpl(p []byte) (hasStreamWindowUpdate bool, hasConnW s.readPos += protocol.ByteCount(m) bytesRead += m - if s.cancelledRemotely && s.readPos >= s.reliableSize { + if s.isRemoteCancellationEffective() { s.flowController.Abandon() } @@ -229,13 +225,134 @@ func (s *ReceiveStream) readImpl(p []byte) (hasStreamWindowUpdate bool, hasConnW return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, io.EOF } } - if s.cancelledRemotely && s.readPos >= s.reliableSize { + if s.isRemoteCancellationEffective() { s.errorRead = true return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, s.cancelErr } return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, nil } +// isRemoteCancellationEffective returns whether the stream was cancelled remotely +// and all reliable data has been read. +func (s *ReceiveStream) isRemoteCancellationEffective() bool { + return s.cancelledRemotely && s.readPos >= s.reliableSize +} + +// Peek fills b with stream data, without consuming the stream data. +// It blocks until len(b) bytes are available, or an error occurs. +// It respects the stream deadline set by SetReadDeadline. +// If the stream ends before len(b) bytes are available, +// it returns the number of bytes peeked along with io.EOF. +func (s *ReceiveStream) Peek(b []byte) (int, error) { + if len(b) == 0 { + return 0, nil + } + + // prevent concurrent use with Read + s.readOnce <- struct{}{} + defer func() { <-s.readOnce }() + + return s.peekImpl(b) +} + +func (s *ReceiveStream) peekImpl(b []byte) (int, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + var deadlineTimer *time.Timer + + for { + if s.currentFrameIsLast && s.currentFrame == nil { + return 0, io.EOF + } + if s.cancelledLocally || s.isRemoteCancellationEffective() { + return 0, s.cancelErr + } + if s.closeForShutdownErr != nil { + return 0, s.closeForShutdownErr + } + + deadline := s.deadline + if !deadline.IsZero() && !monotime.Now().Before(deadline) { + return 0, errDeadline + } + + if s.currentFrame == nil || s.readPosInFrame >= len(s.currentFrame) { + s.dequeueNextFrame() + } + + if s.currentFrame != nil && s.readPosInFrame < len(s.currentFrame) { + availableInCurrentFrame := len(s.currentFrame) - s.readPosInFrame + + if availableInCurrentFrame >= len(b) { + copy(b, s.currentFrame[s.readPosInFrame:]) + return len(b), nil + } + + offset := s.readPos + protocol.ByteCount(availableInCurrentFrame) + // First peek, then copy. + // This avoids copying data if there's not enough data in the queue. + if err := s.frameQueue.Peek(offset, b[availableInCurrentFrame:]); err == nil { + copy(b[:availableInCurrentFrame], s.currentFrame[s.readPosInFrame:]) + return len(b), nil + } + + if s.currentFrameIsLast { + copy(b[:availableInCurrentFrame], s.currentFrame[s.readPosInFrame:]) + return availableInCurrentFrame, io.EOF + } + + // If the stream was remotely cancelled and the request extends beyond the reliable size, + // return the data available with the cancel error (once it's all received). + if s.cancelledRemotely && s.readPos+protocol.ByteCount(len(b)) > s.reliableSize { + total := int(s.reliableSize - s.readPos) + needed := total - availableInCurrentFrame + // only return once all available data is contiguous + if needed <= 0 || s.frameQueue.Peek(offset, b[availableInCurrentFrame:total]) == nil { + copy(b[:availableInCurrentFrame], s.currentFrame[s.readPosInFrame:]) + return total, s.cancelErr + } + } + + // If the request extends beyond the stream's final offset, + // return the data available with EOF (once it's all received). + if s.readPos+protocol.ByteCount(len(b)) > s.finalOffset { + total := int(s.finalOffset - s.readPos) + needed := total - availableInCurrentFrame + // only return once all available data is contiguous + if needed <= 0 || s.frameQueue.Peek(offset, b[availableInCurrentFrame:total]) == nil { + copy(b[:availableInCurrentFrame], s.currentFrame[s.readPosInFrame:]) + return total, io.EOF + } + } + } + + if s.currentFrameIsLast || s.readPos >= s.finalOffset { + return 0, io.EOF + } + + s.mutex.Unlock() + if deadline.IsZero() { + <-s.readChan + } else { + if deadlineTimer == nil { + deadlineTimer = time.NewTimer(monotime.Until(deadline)) + defer deadlineTimer.Stop() + } else { + deadlineTimer.Reset(monotime.Until(deadline)) + } + select { + case <-s.readChan: + case <-deadlineTimer.C: + } + } + s.mutex.Lock() + if s.currentFrame == nil || s.readPosInFrame >= len(s.currentFrame) { + s.dequeueNextFrame() + } + } +} + func (s *ReceiveStream) dequeueNextFrame() { var offset protocol.ByteCount // We're done with the last frame. Release the buffer. diff --git a/vendor/github.com/quic-go/quic-go/server.go b/vendor/github.com/quic-go/quic-go/server.go index 142de36ab0..7bbf930d1a 100644 --- a/vendor/github.com/quic-go/quic-go/server.go +++ b/vendor/github.com/quic-go/quic-go/server.go @@ -15,7 +15,8 @@ import ( "github.com/quic-go/quic-go/internal/qerr" "github.com/quic-go/quic-go/internal/utils" "github.com/quic-go/quic-go/internal/wire" - "github.com/quic-go/quic-go/logging" + "github.com/quic-go/quic-go/qlog" + "github.com/quic-go/quic-go/qlogwriter" ) // ErrServerClosed is returned by the [Listener] or [EarlyListener]'s Accept method after a call to Close. @@ -86,7 +87,7 @@ type baseServer struct { *handshake.TokenGenerator, bool, /* client address validated by an address validation token */ time.Duration, - *logging.ConnectionTracer, + qlogwriter.Trace, utils.Logger, protocol.Version, ) *wrappedConn @@ -114,7 +115,7 @@ type baseServer struct { connQueue chan *Conn - tracer *logging.Tracer + qlogger qlogwriter.Recorder logger utils.Logger } @@ -243,7 +244,7 @@ func newServer( connContext func(context.Context, *ClientInfo) (context.Context, error), tlsConf *tls.Config, config *Config, - tracer *logging.Tracer, + qlogger qlogwriter.Recorder, onClose func(), tokenGeneratorKey TokenGeneratorKey, maxTokenAge time.Duration, @@ -272,7 +273,7 @@ func newServer( connectionRefusedQueue: make(chan rejectedPacket, 4), retryQueue: make(chan rejectedPacket, 8), newConn: newConnection, - tracer: tracer, + qlogger: qlogger, logger: utils.DefaultLogger.WithPrefix("server"), acceptEarlyConns: acceptEarly, disableVersionNegotiation: disableVersionNegotiation, @@ -397,8 +398,11 @@ func (s *baseServer) handlePacket(p receivedPacket) { return default: s.logger.Debugf("Dropping packet from %s (%d bytes). Server receive queue full.", p.remoteAddr, p.Size()) - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropDOSPrevention) + if s.qlogger != nil { + s.qlogger.RecordEvent(qlog.PacketDropped{ + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropDOSPrevention, + }) } } } @@ -410,8 +414,12 @@ func (s *baseServer) handlePacketImpl(p receivedPacket) bool /* is the buffer st if wire.IsVersionNegotiationPacket(p.data) { s.logger.Debugf("Dropping Version Negotiation packet.") - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeVersionNegotiation, p.Size(), logging.PacketDropUnexpectedPacket) + if s.qlogger != nil { + s.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{PacketType: qlog.PacketTypeVersionNegotiation}, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropUnexpectedPacket, + }) } return false } @@ -423,24 +431,35 @@ func (s *baseServer) handlePacketImpl(p receivedPacket) bool /* is the buffer st // drop the packet if we failed to parse the protocol version if err != nil { s.logger.Debugf("Dropping a packet with an unknown version") - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropUnexpectedPacket) + if s.qlogger != nil { + s.qlogger.RecordEvent(qlog.PacketDropped{ + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropUnexpectedPacket, + }) } return false } // send a Version Negotiation Packet if the client is speaking a different protocol version if !protocol.IsSupportedVersion(s.config.Versions, v) { if s.disableVersionNegotiation { - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropUnexpectedVersion) + if s.qlogger != nil { + s.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{Version: v}, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropUnexpectedVersion, + }) } return false } if p.Size() < protocol.MinUnknownVersionPacketSize { s.logger.Debugf("Dropping a packet with an unsupported version number %d that is too small (%d bytes)", v, p.Size()) - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropUnexpectedPacket) + if s.qlogger != nil { + s.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{Version: v}, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropUnexpectedPacket, + }) } return false } @@ -449,8 +468,15 @@ func (s *baseServer) handlePacketImpl(p receivedPacket) bool /* is the buffer st if wire.Is0RTTPacket(p.data) { if !s.acceptEarlyConns { - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketType0RTT, p.Size(), logging.PacketDropUnexpectedPacket) + if s.qlogger != nil { + s.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketType0RTT, + PacketNumber: protocol.InvalidPacketNumber, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropUnexpectedPacket, + }) } return false } @@ -461,16 +487,27 @@ func (s *baseServer) handlePacketImpl(p receivedPacket) bool /* is the buffer st // The header will then be parsed again. hdr, _, _, err := wire.ParsePacket(p.data) if err != nil { - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropHeaderParseError) + if s.qlogger != nil { + s.qlogger.RecordEvent(qlog.PacketDropped{ + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropHeaderParseError, + }) } s.logger.Debugf("Error parsing packet: %s", err) return false } if hdr.Type == protocol.PacketTypeInitial && p.Size() < protocol.MinInitialPacketSize { s.logger.Debugf("Dropping a packet that is too small to be a valid Initial (%d bytes)", p.Size()) - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeInitial, p.Size(), logging.PacketDropUnexpectedPacket) + if s.qlogger != nil { + s.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketTypeInitial, + PacketNumber: protocol.InvalidPacketNumber, + Version: v, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropUnexpectedPacket, + }) } return false } @@ -480,8 +517,27 @@ func (s *baseServer) handlePacketImpl(p receivedPacket) bool /* is the buffer st // There's little point in sending a Stateless Reset, since the client // might not have received the token yet. s.logger.Debugf("Dropping long header packet of type %s (%d bytes)", hdr.Type, len(p.data)) - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeFromHeader(hdr), p.Size(), logging.PacketDropUnexpectedPacket) + if s.qlogger != nil { + var pt qlog.PacketType + switch hdr.Type { + case protocol.PacketTypeInitial: + pt = qlog.PacketTypeInitial + case protocol.PacketTypeHandshake: + pt = qlog.PacketTypeHandshake + case protocol.PacketType0RTT: + pt = qlog.PacketType0RTT + case protocol.PacketTypeRetry: + pt = qlog.PacketTypeRetry + } + s.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: pt, + PacketNumber: protocol.InvalidPacketNumber, + Version: v, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropUnexpectedPacket, + }) } return false } @@ -499,8 +555,17 @@ func (s *baseServer) handlePacketImpl(p receivedPacket) bool /* is the buffer st func (s *baseServer) handle0RTTPacket(p receivedPacket) bool { connID, err := wire.ParseConnectionID(p.data, 0) if err != nil { - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketType0RTT, p.Size(), logging.PacketDropHeaderParseError) + if s.qlogger != nil { + v, _ := wire.ParseVersion(p.data) + s.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketType0RTT, + PacketNumber: protocol.InvalidPacketNumber, + Version: v, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropHeaderParseError, + }) } return false } @@ -513,8 +578,17 @@ func (s *baseServer) handle0RTTPacket(p receivedPacket) bool { if q, ok := s.zeroRTTQueues[connID]; ok { if len(q.packets) >= protocol.Max0RTTQueueLen { - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketType0RTT, p.Size(), logging.PacketDropDOSPrevention) + if s.qlogger != nil { + v, _ := wire.ParseVersion(p.data) + s.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketType0RTT, + PacketNumber: protocol.InvalidPacketNumber, + Version: v, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropDOSPrevention, + }) } return false } @@ -523,8 +597,17 @@ func (s *baseServer) handle0RTTPacket(p receivedPacket) bool { } if len(s.zeroRTTQueues) >= protocol.Max0RTTQueues { - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketType0RTT, p.Size(), logging.PacketDropDOSPrevention) + if s.qlogger != nil { + v, _ := wire.ParseVersion(p.data) + s.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketType0RTT, + PacketNumber: protocol.InvalidPacketNumber, + Version: v, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropDOSPrevention, + }) } return false } @@ -551,8 +634,17 @@ func (s *baseServer) cleanupZeroRTTQueues(now monotime.Time) { continue } for _, p := range q.packets { - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketType0RTT, p.Size(), logging.PacketDropDOSPrevention) + if s.qlogger != nil { + v, _ := wire.ParseVersion(p.data) + s.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketType0RTT, + PacketNumber: protocol.InvalidPacketNumber, + Version: v, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropDOSPrevention, + }) } p.buffer.Release() } @@ -586,8 +678,16 @@ func (s *baseServer) validateToken(token *handshake.Token, addr net.Addr) bool { func (s *baseServer) handleInitialImpl(p receivedPacket, hdr *wire.Header) error { if len(hdr.Token) == 0 && hdr.DestConnectionID.Len() < protocol.MinConnectionIDLenInitial { - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeInitial, p.Size(), logging.PacketDropUnexpectedPacket) + if s.qlogger != nil { + s.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketTypeInitial, + PacketNumber: protocol.InvalidPacketNumber, + Version: hdr.Version, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropUnexpectedPacket, + }) } p.buffer.Release() return errors.New("too short connection ID") @@ -700,15 +800,14 @@ func (s *baseServer) handleInitialImpl(p receivedPacket, hdr *wire.Header) error } else { cancel = cancel1 } - ctx = context.WithValue(ctx, ConnectionTracingKey, nextConnTracingID()) - var tracer *logging.ConnectionTracer + var qlogTrace qlogwriter.Trace if config.Tracer != nil { // Use the same connection ID that is passed to the client's GetLogWriter callback. connID := hdr.DestConnectionID if origDestConnID.Len() > 0 { connID = origDestConnID } - tracer = config.Tracer(ctx, protocol.PerspectiveServer, connID) + qlogTrace = config.Tracer(ctx, false, connID) } connID, err := s.connIDGenerator.GenerateConnectionID() if err != nil { @@ -732,7 +831,7 @@ func (s *baseServer) handleInitialImpl(p receivedPacket, hdr *wire.Header) error s.tokenGenerator, clientAddrVerified, rtt, - tracer, + qlogTrace, s.logger, hdr.Version, ) @@ -843,8 +942,20 @@ func (s *baseServer) sendRetryPacket(p rejectedPacket) error { // append the Retry integrity tag tag := handshake.GetRetryIntegrityTag(buf.Data, hdr.DestConnectionID, hdr.Version) buf.Data = append(buf.Data, tag[:]...) - if s.tracer != nil && s.tracer.SentPacket != nil { - s.tracer.SentPacket(p.remoteAddr, &replyHdr.Header, protocol.ByteCount(len(buf.Data)), nil) + if s.qlogger != nil { + s.qlogger.RecordEvent(qlog.PacketSent{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketTypeRetry, + SrcConnectionID: replyHdr.SrcConnectionID, + DestConnectionID: replyHdr.DestConnectionID, + Version: replyHdr.Version, + Token: &qlog.Token{Raw: token}, + }, + Raw: qlog.RawInfo{ + Length: len(buf.Data), + PayloadLength: int(replyHdr.Length), + }, + }) } _, err = s.conn.WritePacket(buf.Data, p.remoteAddr, p.info.OOB(), 0, protocol.ECNUnsupported) return err @@ -862,15 +973,30 @@ func (s *baseServer) maybeSendInvalidToken(p rejectedPacket) { // Only send INVALID_TOKEN if we can unprotect the packet. // This makes sure that we won't send it for packets that were corrupted. if err != nil { - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeInitial, p.Size(), logging.PacketDropHeaderParseError) + if s.qlogger != nil { + s.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketTypeInitial, + PacketNumber: protocol.InvalidPacketNumber, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropHeaderParseError, + }) } return } hdrLen := extHdr.ParsedLen() if _, err := opener.Open(data[hdrLen:hdrLen], data[hdrLen:], extHdr.PacketNumber, data[:hdrLen]); err != nil { - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeInitial, p.Size(), logging.PacketDropPayloadDecryptError) + if s.qlogger != nil { + s.qlogger.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketTypeInitial, + PacketNumber: protocol.InvalidPacketNumber, + Version: hdr.Version, + }, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropPayloadDecryptError, + }) } return } @@ -928,8 +1054,21 @@ func (s *baseServer) sendError(remoteAddr net.Addr, hdr *wire.Header, sealer han replyHdr.Log(s.logger) wire.LogFrame(s.logger, ccf, true) - if s.tracer != nil && s.tracer.SentPacket != nil { - s.tracer.SentPacket(remoteAddr, &replyHdr.Header, protocol.ByteCount(len(b.Data)), []logging.Frame{ccf}) + if s.qlogger != nil { + s.qlogger.RecordEvent(qlog.PacketSent{ + Header: qlog.PacketHeader{ + PacketType: qlog.PacketTypeInitial, + SrcConnectionID: replyHdr.SrcConnectionID, + DestConnectionID: replyHdr.DestConnectionID, + PacketNumber: replyHdr.PacketNumber, + Version: replyHdr.Version, + }, + Raw: qlog.RawInfo{ + Length: len(b.Data), + PayloadLength: int(replyHdr.Length), + }, + Frames: []qlog.Frame{{Frame: ccf}}, + }) } _, err = s.conn.WritePacket(b.Data, remoteAddr, info.OOB(), 0, protocol.ECNUnsupported) return err @@ -957,8 +1096,11 @@ func (s *baseServer) maybeSendVersionNegotiationPacket(p receivedPacket) { _, src, dest, err := wire.ParseArbitraryLenConnectionIDs(p.data) if err != nil { // should never happen s.logger.Debugf("Dropping a packet with an unknown version for which we failed to parse connection IDs") - if s.tracer != nil && s.tracer.DroppedPacket != nil { - s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropUnexpectedPacket) + if s.qlogger != nil { + s.qlogger.RecordEvent(qlog.PacketDropped{ + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropUnexpectedPacket, + }) } return } @@ -966,8 +1108,14 @@ func (s *baseServer) maybeSendVersionNegotiationPacket(p receivedPacket) { s.logger.Debugf("Client offered version %s, sending Version Negotiation", v) data := wire.ComposeVersionNegotiation(dest, src, s.config.Versions) - if s.tracer != nil && s.tracer.SentVersionNegotiationPacket != nil { - s.tracer.SentVersionNegotiationPacket(p.remoteAddr, src, dest, s.config.Versions) + if s.qlogger != nil { + s.qlogger.RecordEvent(qlog.VersionNegotiationSent{ + Header: qlog.PacketHeaderVersionNegotiation{ + SrcConnectionID: src, + DestConnectionID: dest, + }, + SupportedVersions: s.config.Versions, + }) } if _, err := s.conn.WritePacket(data, p.remoteAddr, p.info.OOB(), 0, protocol.ECNUnsupported); err != nil { s.logger.Debugf("Error sending Version Negotiation: %s", err) diff --git a/vendor/github.com/quic-go/quic-go/stream.go b/vendor/github.com/quic-go/quic-go/stream.go index 7b5b8d09cf..7248f76e2b 100644 --- a/vendor/github.com/quic-go/quic-go/stream.go +++ b/vendor/github.com/quic-go/quic-go/stream.go @@ -117,6 +117,15 @@ func (s *Stream) Read(p []byte) (int, error) { return s.receiveStr.Read(p) } +// Peek fills b with stream data, without consuming the stream data. +// It blocks until len(b) bytes are available, or an error occurs. +// It respects the stream deadline set by SetReadDeadline. +// If the stream ends before len(b) bytes are available, +// it returns the number of bytes peeked along with io.EOF. +func (s *Stream) Peek(b []byte) (int, error) { + return s.receiveStr.Peek(b) +} + // Write writes data to the stream. // Write can be made to time out using [Stream.SetWriteDeadline] or [Stream.SetDeadline]. // If the stream was canceled, the error is a [StreamError]. @@ -124,6 +133,14 @@ func (s *Stream) Write(p []byte) (int, error) { return s.sendStr.Write(p) } +// SetReliableBoundary marks the data written to this stream so far as reliable. +// It is valid to call this function multiple times, thereby increasing the reliable size. +// It only has an effect if the peer enabled support for the RESET_STREAM_AT extension, +// otherwise, it is a no-op. +func (s *Stream) SetReliableBoundary() { + s.sendStr.SetReliableBoundary() +} + // CancelWrite aborts sending on this stream. // See [SendStream.CancelWrite] for more details. func (s *Stream) CancelWrite(errorCode StreamErrorCode) { diff --git a/vendor/github.com/quic-go/quic-go/transport.go b/vendor/github.com/quic-go/quic-go/transport.go index 2f105298be..740c9b53b4 100644 --- a/vendor/github.com/quic-go/quic-go/transport.go +++ b/vendor/github.com/quic-go/quic-go/transport.go @@ -14,7 +14,8 @@ import ( "github.com/quic-go/quic-go/internal/protocol" "github.com/quic-go/quic-go/internal/utils" "github.com/quic-go/quic-go/internal/wire" - "github.com/quic-go/quic-go/logging" + "github.com/quic-go/quic-go/qlog" + "github.com/quic-go/quic-go/qlogwriter" ) // ErrTransportClosed is returned by the [Transport]'s Listen or Dial method after it was closed. @@ -123,15 +124,15 @@ type Transport struct { // The context returned from the callback is used to derive every other context used during the // lifetime of the connection: // * the context passed to crypto/tls (and used on the tls.ClientHelloInfo) - // * the context used in Config.Tracer + // * the context used in Config.QlogTrace // * the context returned from Conn.Context // * the context returned from SendStream.Context // It is not used for dialed connections. ConnContext func(context.Context, *ClientInfo) (context.Context, error) // A Tracer traces events that don't belong to a single QUIC connection. - // Tracer.Close is called when the transport is closed. - Tracer *logging.Tracer + // Recorder.Close is called when the transport is closed. + Tracer qlogwriter.Recorder mutex sync.Mutex handlers map[protocol.ConnectionID]packetHandler @@ -282,21 +283,15 @@ func (t *Transport) doDial( return nil, err } - tracingID := nextConnTracingID() - ctx = context.WithValue(ctx, ConnectionTracingKey, tracingID) - t.mutex.Lock() if t.closeErr != nil { t.mutex.Unlock() return nil, t.closeErr } - var tracer *logging.ConnectionTracer + var qlogTrace qlogwriter.Trace if config.Tracer != nil { - tracer = config.Tracer(ctx, protocol.PerspectiveClient, destConnID) - } - if tracer != nil && tracer.StartedConnection != nil { - tracer.StartedConnection(sendConn.LocalAddr(), sendConn.RemoteAddr(), srcConnID, destConnID) + qlogTrace = config.Tracer(ctx, true, destConnID) } logger := utils.DefaultLogger.WithPrefix("client") @@ -315,7 +310,7 @@ func (t *Transport) doDial( initialPacketNumber, use0RTT, hasNegotiatedVersion, - tracer, + qlogTrace, logger, version, ) @@ -525,7 +520,7 @@ func (t *Transport) close(e error) { t.mutex.Unlock() // closing connections requires releasing transport mutex wg.Wait() - if t.Tracer != nil && t.Tracer.Close != nil { + if t.Tracer != nil { t.Tracer.Close() } } @@ -579,8 +574,11 @@ func (t *Transport) handlePacket(p receivedPacket) { connID, err := wire.ParseConnectionID(p.data, t.connIDLen) if err != nil { t.logger.Debugf("error parsing connection ID on packet from %s: %s", p.remoteAddr, err) - if t.Tracer != nil && t.Tracer.DroppedPacket != nil { - t.Tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropHeaderParseError) + if t.Tracer != nil { + t.Tracer.RecordEvent(qlog.PacketDropped{ + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropHeaderParseError, + }) } p.buffer.MaybeRelease() return @@ -603,8 +601,12 @@ func (t *Transport) handlePacket(p receivedPacket) { } if !wire.IsLongHeaderPacket(p.data[0]) { if statelessResetQueued := t.maybeSendStatelessReset(p); !statelessResetQueued { - if t.Tracer != nil && t.Tracer.DroppedPacket != nil { - t.Tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropUnknownConnectionID) + if t.Tracer != nil { + t.Tracer.RecordEvent(qlog.PacketDropped{ + Header: qlog.PacketHeader{PacketType: qlog.PacketType1RTT}, + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropUnknownConnectionID, + }) } p.buffer.Release() } @@ -615,8 +617,11 @@ func (t *Transport) handlePacket(p receivedPacket) { defer t.mutex.Unlock() if t.server == nil { // no server set t.logger.Debugf("received a packet with an unexpected connection ID %s", connID) - if t.Tracer != nil && t.Tracer.DroppedPacket != nil { - t.Tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropUnknownConnectionID) + if t.Tracer != nil { + t.Tracer.RecordEvent(qlog.PacketDropped{ + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropUnknownConnectionID, + }) } p.buffer.MaybeRelease() return @@ -694,8 +699,11 @@ func (t *Transport) handleNonQUICPacket(p receivedPacket) { select { case t.nonQUICPackets <- p: default: - if t.Tracer != nil && t.Tracer.DroppedPacket != nil { - t.Tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropDOSPrevention) + if t.Tracer != nil { + t.Tracer.RecordEvent(qlog.PacketDropped{ + Raw: qlog.RawInfo{Length: int(p.Size())}, + Trigger: qlog.PacketDropDOSPrevention, + }) } } } diff --git a/vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go b/vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go deleted file mode 100644 index 150f887e7a..0000000000 --- a/vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go +++ /dev/null @@ -1,78 +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 lazyregexp is a thin wrapper over regexp, allowing the use of global -// regexp variables without forcing them to be compiled at init. -package lazyregexp - -import ( - "os" - "regexp" - "strings" - "sync" -) - -// Regexp is a wrapper around [regexp.Regexp], where the underlying regexp will be -// compiled the first time it is needed. -type Regexp struct { - str string - once sync.Once - rx *regexp.Regexp -} - -func (r *Regexp) re() *regexp.Regexp { - r.once.Do(r.build) - return r.rx -} - -func (r *Regexp) build() { - r.rx = regexp.MustCompile(r.str) - r.str = "" -} - -func (r *Regexp) FindSubmatch(s []byte) [][]byte { - return r.re().FindSubmatch(s) -} - -func (r *Regexp) FindStringSubmatch(s string) []string { - return r.re().FindStringSubmatch(s) -} - -func (r *Regexp) FindStringSubmatchIndex(s string) []int { - return r.re().FindStringSubmatchIndex(s) -} - -func (r *Regexp) ReplaceAllString(src, repl string) string { - return r.re().ReplaceAllString(src, repl) -} - -func (r *Regexp) FindString(s string) string { - return r.re().FindString(s) -} - -func (r *Regexp) FindAllString(s string, n int) []string { - return r.re().FindAllString(s, n) -} - -func (r *Regexp) MatchString(s string) bool { - return r.re().MatchString(s) -} - -func (r *Regexp) SubexpNames() []string { - return r.re().SubexpNames() -} - -var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test") - -// New creates a new lazy regexp, delaying the compiling work until it is first -// needed. If the code is being run as part of tests, the regexp compiling will -// happen immediately. -func New(str string) *Regexp { - lr := &Regexp{str: str} - if inTest { - // In tests, always compile the regexps early. - lr.re() - } - return lr -} diff --git a/vendor/golang.org/x/mod/module/module.go b/vendor/golang.org/x/mod/module/module.go deleted file mode 100644 index 739c13f48f..0000000000 --- a/vendor/golang.org/x/mod/module/module.go +++ /dev/null @@ -1,840 +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 module defines the module.Version type along with support code. -// -// The [module.Version] type is a simple Path, Version pair: -// -// type Version struct { -// Path string -// Version string -// } -// -// There are no restrictions imposed directly by use of this structure, -// but additional checking functions, most notably [Check], verify that -// a particular path, version pair is valid. -// -// # Escaped Paths -// -// Module paths appear as substrings of file system paths -// (in the download cache) and of web server URLs in the proxy protocol. -// In general we cannot rely on file systems to be case-sensitive, -// nor can we rely on web servers, since they read from file systems. -// That is, we cannot rely on the file system to keep rsc.io/QUOTE -// and rsc.io/quote separate. Windows and macOS don't. -// Instead, we must never require two different casings of a file path. -// Because we want the download cache to match the proxy protocol, -// and because we want the proxy protocol to be possible to serve -// from a tree of static files (which might be stored on a case-insensitive -// file system), the proxy protocol must never require two different casings -// of a URL path either. -// -// One possibility would be to make the escaped form be the lowercase -// hexadecimal encoding of the actual path bytes. This would avoid ever -// needing different casings of a file path, but it would be fairly illegible -// to most programmers when those paths appeared in the file system -// (including in file paths in compiler errors and stack traces) -// in web server logs, and so on. Instead, we want a safe escaped form that -// leaves most paths unaltered. -// -// The safe escaped form is to replace every uppercase letter -// with an exclamation mark followed by the letter's lowercase equivalent. -// -// For example, -// -// github.com/Azure/azure-sdk-for-go -> github.com/!azure/azure-sdk-for-go. -// github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy -// github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus. -// -// Import paths that avoid upper-case letters are left unchanged. -// Note that because import paths are ASCII-only and avoid various -// problematic punctuation (like : < and >), the escaped form is also ASCII-only -// and avoids the same problematic punctuation. -// -// Import paths have never allowed exclamation marks, so there is no -// need to define how to escape a literal !. -// -// # Unicode Restrictions -// -// Today, paths are disallowed from using Unicode. -// -// Although paths are currently disallowed from using Unicode, -// we would like at some point to allow Unicode letters as well, to assume that -// file systems and URLs are Unicode-safe (storing UTF-8), and apply -// the !-for-uppercase convention for escaping them in the file system. -// But there are at least two subtle considerations. -// -// First, note that not all case-fold equivalent distinct runes -// form an upper/lower pair. -// For example, U+004B ('K'), U+006B ('k'), and U+212A ('K' for Kelvin) -// are three distinct runes that case-fold to each other. -// When we do add Unicode letters, we must not assume that upper/lower -// are the only case-equivalent pairs. -// Perhaps the Kelvin symbol would be disallowed entirely, for example. -// Or perhaps it would escape as "!!k", or perhaps as "(212A)". -// -// Second, it would be nice to allow Unicode marks as well as letters, -// but marks include combining marks, and then we must deal not -// only with case folding but also normalization: both U+00E9 ('é') -// and U+0065 U+0301 ('e' followed by combining acute accent) -// look the same on the page and are treated by some file systems -// as the same path. If we do allow Unicode marks in paths, there -// must be some kind of normalization to allow only one canonical -// encoding of any character used in an import path. -package module - -// IMPORTANT NOTE -// -// This file essentially defines the set of valid import paths for the go command. -// There are many subtle considerations, including Unicode ambiguity, -// security, network, and file system representations. -// -// This file also defines the set of valid module path and version combinations, -// another topic with many subtle considerations. -// -// Changes to the semantics in this file require approval from rsc. - -import ( - "cmp" - "errors" - "fmt" - "path" - "slices" - "strings" - "unicode" - "unicode/utf8" - - "golang.org/x/mod/semver" -) - -// A Version (for clients, a module.Version) is defined by a module path and version pair. -// These are stored in their plain (unescaped) form. -type Version struct { - // Path is a module path, like "golang.org/x/text" or "rsc.io/quote/v2". - Path string - - // Version is usually a semantic version in canonical form. - // There are three exceptions to this general rule. - // First, the top-level target of a build has no specific version - // and uses Version = "". - // Second, during MVS calculations the version "none" is used - // to represent the decision to take no version of a given module. - // Third, filesystem paths found in "replace" directives are - // represented by a path with an empty version. - Version string `json:",omitempty"` -} - -// String returns a representation of the Version suitable for logging -// (Path@Version, or just Path if Version is empty). -func (m Version) String() string { - if m.Version == "" { - return m.Path - } - return m.Path + "@" + m.Version -} - -// A ModuleError indicates an error specific to a module. -type ModuleError struct { - Path string - Version string - Err error -} - -// VersionError returns a [ModuleError] derived from a [Version] and error, -// or err itself if it is already such an error. -func VersionError(v Version, err error) error { - var mErr *ModuleError - if errors.As(err, &mErr) && mErr.Path == v.Path && mErr.Version == v.Version { - return err - } - return &ModuleError{ - Path: v.Path, - Version: v.Version, - Err: err, - } -} - -func (e *ModuleError) Error() string { - if v, ok := e.Err.(*InvalidVersionError); ok { - return fmt.Sprintf("%s@%s: invalid %s: %v", e.Path, v.Version, v.noun(), v.Err) - } - if e.Version != "" { - return fmt.Sprintf("%s@%s: %v", e.Path, e.Version, e.Err) - } - return fmt.Sprintf("module %s: %v", e.Path, e.Err) -} - -func (e *ModuleError) Unwrap() error { return e.Err } - -// An InvalidVersionError indicates an error specific to a version, with the -// module path unknown or specified externally. -// -// A [ModuleError] may wrap an InvalidVersionError, but an InvalidVersionError -// must not wrap a ModuleError. -type InvalidVersionError struct { - Version string - Pseudo bool - Err error -} - -// noun returns either "version" or "pseudo-version", depending on whether -// e.Version is a pseudo-version. -func (e *InvalidVersionError) noun() string { - if e.Pseudo { - return "pseudo-version" - } - return "version" -} - -func (e *InvalidVersionError) Error() string { - return fmt.Sprintf("%s %q invalid: %s", e.noun(), e.Version, e.Err) -} - -func (e *InvalidVersionError) Unwrap() error { return e.Err } - -// An InvalidPathError indicates a module, import, or file path doesn't -// satisfy all naming constraints. See [CheckPath], [CheckImportPath], -// and [CheckFilePath] for specific restrictions. -type InvalidPathError struct { - Kind string // "module", "import", or "file" - Path string - Err error -} - -func (e *InvalidPathError) Error() string { - return fmt.Sprintf("malformed %s path %q: %v", e.Kind, e.Path, e.Err) -} - -func (e *InvalidPathError) Unwrap() error { return e.Err } - -// Check checks that a given module path, version pair is valid. -// In addition to the path being a valid module path -// and the version being a valid semantic version, -// the two must correspond. -// For example, the path "yaml/v2" only corresponds to -// semantic versions beginning with "v2.". -func Check(path, version string) error { - if err := CheckPath(path); err != nil { - return err - } - if !semver.IsValid(version) { - return &ModuleError{ - Path: path, - Err: &InvalidVersionError{Version: version, Err: errors.New("not a semantic version")}, - } - } - _, pathMajor, _ := SplitPathVersion(path) - if err := CheckPathMajor(version, pathMajor); err != nil { - return &ModuleError{Path: path, Err: err} - } - return nil -} - -// firstPathOK reports whether r can appear in the first element of a module path. -// The first element of the path must be an LDH domain name, at least for now. -// To avoid case ambiguity, the domain name must be entirely lower case. -func firstPathOK(r rune) bool { - return r == '-' || r == '.' || - '0' <= r && r <= '9' || - 'a' <= r && r <= 'z' -} - -// modPathOK reports whether r can appear in a module path element. -// Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~. -// -// This matches what "go get" has historically recognized in import paths, -// and avoids confusing sequences like '%20' or '+' that would change meaning -// if used in a URL. -// -// TODO(rsc): We would like to allow Unicode letters, but that requires additional -// care in the safe encoding (see "escaped paths" above). -func modPathOK(r rune) bool { - if r < utf8.RuneSelf { - return r == '-' || r == '.' || r == '_' || r == '~' || - '0' <= r && r <= '9' || - 'A' <= r && r <= 'Z' || - 'a' <= r && r <= 'z' - } - return false -} - -// importPathOK reports whether r can appear in a package import path element. -// -// Import paths are intermediate between module paths and file paths: we -// disallow characters that would be confusing or ambiguous as arguments to -// 'go get' (such as '@' and ' ' ), but allow certain characters that are -// otherwise-unambiguous on the command line and historically used for some -// binary names (such as '++' as a suffix for compiler binaries and wrappers). -func importPathOK(r rune) bool { - return modPathOK(r) || r == '+' -} - -// fileNameOK reports whether r can appear in a file name. -// For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters. -// If we expand the set of allowed characters here, we have to -// work harder at detecting potential case-folding and normalization collisions. -// See note about "escaped paths" above. -func fileNameOK(r rune) bool { - if r < utf8.RuneSelf { - // Entire set of ASCII punctuation, from which we remove characters: - // ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ - // We disallow some shell special characters: " ' * < > ? ` | - // (Note that some of those are disallowed by the Windows file system as well.) - // We also disallow path separators / : and \ (fileNameOK is only called on path element characters). - // We allow spaces (U+0020) in file names. - const allowed = "!#$%&()+,-.=@[]^_{}~ " - if '0' <= r && r <= '9' || 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' { - return true - } - return strings.ContainsRune(allowed, r) - } - // It may be OK to add more ASCII punctuation here, but only carefully. - // For example Windows disallows < > \, and macOS disallows :, so we must not allow those. - return unicode.IsLetter(r) -} - -// CheckPath checks that a module path is valid. -// A valid module path is a valid import path, as checked by [CheckImportPath], -// with three additional constraints. -// First, the leading path element (up to the first slash, if any), -// by convention a domain name, must contain only lower-case ASCII letters, -// ASCII digits, dots (U+002E), and dashes (U+002D); -// it must contain at least one dot and cannot start with a dash. -// Second, for a final path element of the form /vN, where N looks numeric -// (ASCII digits and dots) must not begin with a leading zero, must not be /v1, -// and must not contain any dots. For paths beginning with "gopkg.in/", -// this second requirement is replaced by a requirement that the path -// follow the gopkg.in server's conventions. -// Third, no path element may begin with a dot. -func CheckPath(path string) (err error) { - defer func() { - if err != nil { - err = &InvalidPathError{Kind: "module", Path: path, Err: err} - } - }() - - if err := checkPath(path, modulePath); err != nil { - return err - } - i := strings.Index(path, "/") - if i < 0 { - i = len(path) - } - if i == 0 { - return fmt.Errorf("leading slash") - } - if !strings.Contains(path[:i], ".") { - return fmt.Errorf("missing dot in first path element") - } - if path[0] == '-' { - return fmt.Errorf("leading dash in first path element") - } - for _, r := range path[:i] { - if !firstPathOK(r) { - return fmt.Errorf("invalid char %q in first path element", r) - } - } - if _, _, ok := SplitPathVersion(path); !ok { - return fmt.Errorf("invalid version") - } - return nil -} - -// CheckImportPath checks that an import path is valid. -// -// A valid import path consists of one or more valid path elements -// separated by slashes (U+002F). (It must not begin with nor end in a slash.) -// -// A valid path element is a non-empty string made up of -// ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~. -// It must not end with a dot (U+002E), nor contain two dots in a row. -// -// The element prefix up to the first dot must not be a reserved file name -// on Windows, regardless of case (CON, com1, NuL, and so on). The element -// must not have a suffix of a tilde followed by one or more ASCII digits -// (to exclude paths elements that look like Windows short-names). -// -// CheckImportPath may be less restrictive in the future, but see the -// top-level package documentation for additional information about -// subtleties of Unicode. -func CheckImportPath(path string) error { - if err := checkPath(path, importPath); err != nil { - return &InvalidPathError{Kind: "import", Path: path, Err: err} - } - return nil -} - -// pathKind indicates what kind of path we're checking. Module paths, -// import paths, and file paths have different restrictions. -type pathKind int - -const ( - modulePath pathKind = iota - importPath - filePath -) - -// checkPath checks that a general path is valid. kind indicates what -// specific constraints should be applied. -// -// checkPath returns an error describing why the path is not valid. -// Because these checks apply to module, import, and file paths, -// and because other checks may be applied, the caller is expected to wrap -// this error with [InvalidPathError]. -func checkPath(path string, kind pathKind) error { - if !utf8.ValidString(path) { - return fmt.Errorf("invalid UTF-8") - } - if path == "" { - return fmt.Errorf("empty string") - } - if path[0] == '-' && kind != filePath { - return fmt.Errorf("leading dash") - } - if strings.Contains(path, "//") { - return fmt.Errorf("double slash") - } - if path[len(path)-1] == '/' { - return fmt.Errorf("trailing slash") - } - elemStart := 0 - for i, r := range path { - if r == '/' { - if err := checkElem(path[elemStart:i], kind); err != nil { - return err - } - elemStart = i + 1 - } - } - if err := checkElem(path[elemStart:], kind); err != nil { - return err - } - return nil -} - -// checkElem checks whether an individual path element is valid. -func checkElem(elem string, kind pathKind) error { - if elem == "" { - return fmt.Errorf("empty path element") - } - if strings.Count(elem, ".") == len(elem) { - return fmt.Errorf("invalid path element %q", elem) - } - if elem[0] == '.' && kind == modulePath { - return fmt.Errorf("leading dot in path element") - } - if elem[len(elem)-1] == '.' { - return fmt.Errorf("trailing dot in path element") - } - for _, r := range elem { - ok := false - switch kind { - case modulePath: - ok = modPathOK(r) - case importPath: - ok = importPathOK(r) - case filePath: - ok = fileNameOK(r) - default: - panic(fmt.Sprintf("internal error: invalid kind %v", kind)) - } - if !ok { - return fmt.Errorf("invalid char %q", r) - } - } - - // Windows disallows a bunch of path elements, sadly. - // See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file - short := elem - if i := strings.Index(short, "."); i >= 0 { - short = short[:i] - } - for _, bad := range badWindowsNames { - if strings.EqualFold(bad, short) { - return fmt.Errorf("%q disallowed as path element component on Windows", short) - } - } - - if kind == filePath { - // don't check for Windows short-names in file names. They're - // only an issue for import paths. - return nil - } - - // Reject path components that look like Windows short-names. - // Those usually end in a tilde followed by one or more ASCII digits. - if tilde := strings.LastIndexByte(short, '~'); tilde >= 0 && tilde < len(short)-1 { - suffix := short[tilde+1:] - suffixIsDigits := true - for _, r := range suffix { - if r < '0' || r > '9' { - suffixIsDigits = false - break - } - } - if suffixIsDigits { - return fmt.Errorf("trailing tilde and digits in path element") - } - } - - return nil -} - -// CheckFilePath checks that a slash-separated file path is valid. -// The definition of a valid file path is the same as the definition -// of a valid import path except that the set of allowed characters is larger: -// all Unicode letters, ASCII digits, the ASCII space character (U+0020), -// and the ASCII punctuation characters -// “!#$%&()+,-.=@[]^_{}~”. -// (The excluded punctuation characters, " * < > ? ` ' | / \ and :, -// have special meanings in certain shells or operating systems.) -// -// CheckFilePath may be less restrictive in the future, but see the -// top-level package documentation for additional information about -// subtleties of Unicode. -func CheckFilePath(path string) error { - if err := checkPath(path, filePath); err != nil { - return &InvalidPathError{Kind: "file", Path: path, Err: err} - } - return nil -} - -// badWindowsNames are the reserved file path elements on Windows. -// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file -var badWindowsNames = []string{ - "CON", - "PRN", - "AUX", - "NUL", - "COM1", - "COM2", - "COM3", - "COM4", - "COM5", - "COM6", - "COM7", - "COM8", - "COM9", - "LPT1", - "LPT2", - "LPT3", - "LPT4", - "LPT5", - "LPT6", - "LPT7", - "LPT8", - "LPT9", -} - -// SplitPathVersion returns prefix and major version such that prefix+pathMajor == path -// and version is either empty or "/vN" for N >= 2. -// As a special case, gopkg.in paths are recognized directly; -// they require ".vN" instead of "/vN", and for all N, not just N >= 2. -// SplitPathVersion returns with ok = false when presented with -// a path whose last path element does not satisfy the constraints -// applied by [CheckPath], such as "example.com/pkg/v1" or "example.com/pkg/v1.2". -func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) { - if strings.HasPrefix(path, "gopkg.in/") { - return splitGopkgIn(path) - } - - i := len(path) - dot := false - for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9' || path[i-1] == '.') { - if path[i-1] == '.' { - dot = true - } - i-- - } - if i <= 1 || i == len(path) || path[i-1] != 'v' || path[i-2] != '/' { - return path, "", true - } - prefix, pathMajor = path[:i-2], path[i-2:] - if dot || len(pathMajor) <= 2 || pathMajor[2] == '0' || pathMajor == "/v1" { - return path, "", false - } - return prefix, pathMajor, true -} - -// splitGopkgIn is like SplitPathVersion but only for gopkg.in paths. -func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) { - if !strings.HasPrefix(path, "gopkg.in/") { - return path, "", false - } - i := len(path) - if strings.HasSuffix(path, "-unstable") { - i -= len("-unstable") - } - for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9') { - i-- - } - if i <= 1 || path[i-1] != 'v' || path[i-2] != '.' { - // All gopkg.in paths must end in vN for some N. - return path, "", false - } - prefix, pathMajor = path[:i-2], path[i-2:] - if len(pathMajor) <= 2 || pathMajor[2] == '0' && pathMajor != ".v0" { - return path, "", false - } - return prefix, pathMajor, true -} - -// MatchPathMajor reports whether the semantic version v -// matches the path major version pathMajor. -// -// MatchPathMajor returns true if and only if [CheckPathMajor] returns nil. -func MatchPathMajor(v, pathMajor string) bool { - return CheckPathMajor(v, pathMajor) == nil -} - -// CheckPathMajor returns a non-nil error if the semantic version v -// does not match the path major version pathMajor. -func CheckPathMajor(v, pathMajor string) error { - // TODO(jayconrod): return errors or panic for invalid inputs. This function - // (and others) was covered by integration tests for cmd/go, and surrounding - // code protected against invalid inputs like non-canonical versions. - if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") { - pathMajor = strings.TrimSuffix(pathMajor, "-unstable") - } - if strings.HasPrefix(v, "v0.0.0-") && pathMajor == ".v1" { - // Allow old bug in pseudo-versions that generated v0.0.0- pseudoversion for gopkg .v1. - // For example, gopkg.in/yaml.v2@v2.2.1's go.mod requires gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405. - return nil - } - m := semver.Major(v) - if pathMajor == "" { - if m == "v0" || m == "v1" || semver.Build(v) == "+incompatible" { - return nil - } - pathMajor = "v0 or v1" - } else if pathMajor[0] == '/' || pathMajor[0] == '.' { - if m == pathMajor[1:] { - return nil - } - pathMajor = pathMajor[1:] - } - return &InvalidVersionError{ - Version: v, - Err: fmt.Errorf("should be %s, not %s", pathMajor, semver.Major(v)), - } -} - -// PathMajorPrefix returns the major-version tag prefix implied by pathMajor. -// An empty PathMajorPrefix allows either v0 or v1. -// -// Note that [MatchPathMajor] may accept some versions that do not actually begin -// with this prefix: namely, it accepts a 'v0.0.0-' prefix for a '.v1' -// pathMajor, even though that pathMajor implies 'v1' tagging. -func PathMajorPrefix(pathMajor string) string { - if pathMajor == "" { - return "" - } - if pathMajor[0] != '/' && pathMajor[0] != '.' { - panic("pathMajor suffix " + pathMajor + " passed to PathMajorPrefix lacks separator") - } - if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") { - pathMajor = strings.TrimSuffix(pathMajor, "-unstable") - } - m := pathMajor[1:] - if m != semver.Major(m) { - panic("pathMajor suffix " + pathMajor + "passed to PathMajorPrefix is not a valid major version") - } - return m -} - -// CanonicalVersion returns the canonical form of the version string v. -// It is the same as [semver.Canonical] except that it preserves the special build suffix "+incompatible". -func CanonicalVersion(v string) string { - cv := semver.Canonical(v) - if semver.Build(v) == "+incompatible" { - cv += "+incompatible" - } - return cv -} - -// Sort sorts the list by Path, breaking ties by comparing [Version] fields. -// The Version fields are interpreted as semantic versions (using [semver.Compare]) -// optionally followed by a tie-breaking suffix introduced by a slash character, -// like in "v0.0.1/go.mod". -func Sort(list []Version) { - slices.SortFunc(list, func(i, j Version) int { - if i.Path != j.Path { - return strings.Compare(i.Path, j.Path) - } - // To help go.sum formatting, allow version/file. - // Compare semver prefix by semver rules, - // file by string order. - vi := i.Version - vj := j.Version - var fi, fj string - if k := strings.Index(vi, "/"); k >= 0 { - vi, fi = vi[:k], vi[k:] - } - if k := strings.Index(vj, "/"); k >= 0 { - vj, fj = vj[:k], vj[k:] - } - if vi != vj { - return semver.Compare(vi, vj) - } - return cmp.Compare(fi, fj) - }) -} - -// EscapePath returns the escaped form of the given module path. -// It fails if the module path is invalid. -func EscapePath(path string) (escaped string, err error) { - if err := CheckPath(path); err != nil { - return "", err - } - - return escapeString(path) -} - -// EscapeVersion returns the escaped form of the given module version. -// Versions are allowed to be in non-semver form but must be valid file names -// and not contain exclamation marks. -func EscapeVersion(v string) (escaped string, err error) { - if err := checkElem(v, filePath); err != nil || strings.Contains(v, "!") { - return "", &InvalidVersionError{ - Version: v, - Err: fmt.Errorf("disallowed version string"), - } - } - return escapeString(v) -} - -func escapeString(s string) (escaped string, err error) { - haveUpper := false - for _, r := range s { - if r == '!' || r >= utf8.RuneSelf { - // This should be disallowed by CheckPath, but diagnose anyway. - // The correctness of the escaping loop below depends on it. - return "", fmt.Errorf("internal error: inconsistency in EscapePath") - } - if 'A' <= r && r <= 'Z' { - haveUpper = true - } - } - - if !haveUpper { - return s, nil - } - - var buf []byte - for _, r := range s { - if 'A' <= r && r <= 'Z' { - buf = append(buf, '!', byte(r+'a'-'A')) - } else { - buf = append(buf, byte(r)) - } - } - return string(buf), nil -} - -// UnescapePath returns the module path for the given escaped path. -// It fails if the escaped path is invalid or describes an invalid path. -func UnescapePath(escaped string) (path string, err error) { - path, ok := unescapeString(escaped) - if !ok { - return "", fmt.Errorf("invalid escaped module path %q", escaped) - } - if err := CheckPath(path); err != nil { - return "", fmt.Errorf("invalid escaped module path %q: %v", escaped, err) - } - return path, nil -} - -// UnescapeVersion returns the version string for the given escaped version. -// It fails if the escaped form is invalid or describes an invalid version. -// Versions are allowed to be in non-semver form but must be valid file names -// and not contain exclamation marks. -func UnescapeVersion(escaped string) (v string, err error) { - v, ok := unescapeString(escaped) - if !ok { - return "", fmt.Errorf("invalid escaped version %q", escaped) - } - if err := checkElem(v, filePath); err != nil { - return "", fmt.Errorf("invalid escaped version %q: %v", v, err) - } - return v, nil -} - -func unescapeString(escaped string) (string, bool) { - var buf []byte - - bang := false - for _, r := range escaped { - if r >= utf8.RuneSelf { - return "", false - } - if bang { - bang = false - if r < 'a' || 'z' < r { - return "", false - } - buf = append(buf, byte(r+'A'-'a')) - continue - } - if r == '!' { - bang = true - continue - } - if 'A' <= r && r <= 'Z' { - return "", false - } - buf = append(buf, byte(r)) - } - if bang { - return "", false - } - return string(buf), true -} - -// MatchPrefixPatterns reports whether any path prefix of target matches one of -// the glob patterns (as defined by [path.Match]) in the comma-separated globs -// list. This implements the algorithm used when matching a module path to the -// GOPRIVATE environment variable, as described by 'go help module-private'. -// -// It ignores any empty or malformed patterns in the list. -// Trailing slashes on patterns are ignored. -func MatchPrefixPatterns(globs, target string) bool { - for globs != "" { - // Extract next non-empty glob in comma-separated list. - var glob string - if before, after, ok := strings.Cut(globs, ","); ok { - glob, globs = before, after - } else { - glob, globs = globs, "" - } - glob = strings.TrimSuffix(glob, "/") - if glob == "" { - continue - } - - // A glob with N+1 path elements (N slashes) needs to be matched - // against the first N+1 path elements of target, - // which end just before the N+1'th slash. - n := strings.Count(glob, "/") - prefix := target - // Walk target, counting slashes, truncating at the N+1'th slash. - for i := 0; i < len(target); i++ { - if target[i] == '/' { - if n == 0 { - prefix = target[:i] - break - } - n-- - } - } - if n > 0 { - // Not enough prefix elements. - continue - } - matched, _ := path.Match(glob, prefix) - if matched { - return true - } - } - return false -} diff --git a/vendor/golang.org/x/mod/module/pseudo.go b/vendor/golang.org/x/mod/module/pseudo.go deleted file mode 100644 index 9cf19d3254..0000000000 --- a/vendor/golang.org/x/mod/module/pseudo.go +++ /dev/null @@ -1,250 +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. - -// Pseudo-versions -// -// Code authors are expected to tag the revisions they want users to use, -// including prereleases. However, not all authors tag versions at all, -// and not all commits a user might want to try will have tags. -// A pseudo-version is a version with a special form that allows us to -// address an untagged commit and order that version with respect to -// other versions we might encounter. -// -// A pseudo-version takes one of the general forms: -// -// (1) vX.0.0-yyyymmddhhmmss-abcdef123456 -// (2) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456 -// (3) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible -// (4) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456 -// (5) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible -// -// If there is no recently tagged version with the right major version vX, -// then form (1) is used, creating a space of pseudo-versions at the bottom -// of the vX version range, less than any tagged version, including the unlikely v0.0.0. -// -// If the most recent tagged version before the target commit is vX.Y.Z or vX.Y.Z+incompatible, -// then the pseudo-version uses form (2) or (3), making it a prerelease for the next -// possible semantic version after vX.Y.Z. The leading 0 segment in the prerelease string -// ensures that the pseudo-version compares less than possible future explicit prereleases -// like vX.Y.(Z+1)-rc1 or vX.Y.(Z+1)-1. -// -// If the most recent tagged version before the target commit is vX.Y.Z-pre or vX.Y.Z-pre+incompatible, -// then the pseudo-version uses form (4) or (5), making it a slightly later prerelease. - -package module - -import ( - "errors" - "fmt" - "strings" - "time" - - "golang.org/x/mod/internal/lazyregexp" - "golang.org/x/mod/semver" -) - -var pseudoVersionRE = lazyregexp.New(`^v[0-9]+\.(0\.0-|\d+\.\d+-([^+]*\.)?0\.)\d{14}-[A-Za-z0-9]+(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$`) - -const PseudoVersionTimestampFormat = "20060102150405" - -// PseudoVersion returns a pseudo-version for the given major version ("v1") -// preexisting older tagged version ("" or "v1.2.3" or "v1.2.3-pre"), revision time, -// and revision identifier (usually a 12-byte commit hash prefix). -func PseudoVersion(major, older string, t time.Time, rev string) string { - if major == "" { - major = "v0" - } - segment := fmt.Sprintf("%s-%s", t.UTC().Format(PseudoVersionTimestampFormat), rev) - build := semver.Build(older) - older = semver.Canonical(older) - if older == "" { - return major + ".0.0-" + segment // form (1) - } - if semver.Prerelease(older) != "" { - return older + ".0." + segment + build // form (4), (5) - } - - // Form (2), (3). - // Extract patch from vMAJOR.MINOR.PATCH - i := strings.LastIndex(older, ".") + 1 - v, patch := older[:i], older[i:] - - // Reassemble. - return v + incDecimal(patch) + "-0." + segment + build -} - -// ZeroPseudoVersion returns a pseudo-version with a zero timestamp and -// revision, which may be used as a placeholder. -func ZeroPseudoVersion(major string) string { - return PseudoVersion(major, "", time.Time{}, "000000000000") -} - -// incDecimal returns the decimal string incremented by 1. -func incDecimal(decimal string) string { - // Scan right to left turning 9s to 0s until you find a digit to increment. - digits := []byte(decimal) - i := len(digits) - 1 - for ; i >= 0 && digits[i] == '9'; i-- { - digits[i] = '0' - } - if i >= 0 { - digits[i]++ - } else { - // digits is all zeros - digits[0] = '1' - digits = append(digits, '0') - } - return string(digits) -} - -// decDecimal returns the decimal string decremented by 1, or the empty string -// if the decimal is all zeroes. -func decDecimal(decimal string) string { - // Scan right to left turning 0s to 9s until you find a digit to decrement. - digits := []byte(decimal) - i := len(digits) - 1 - for ; i >= 0 && digits[i] == '0'; i-- { - digits[i] = '9' - } - if i < 0 { - // decimal is all zeros - return "" - } - if i == 0 && digits[i] == '1' && len(digits) > 1 { - digits = digits[1:] - } else { - digits[i]-- - } - return string(digits) -} - -// IsPseudoVersion reports whether v is a pseudo-version. -func IsPseudoVersion(v string) bool { - return strings.Count(v, "-") >= 2 && semver.IsValid(v) && pseudoVersionRE.MatchString(v) -} - -// IsZeroPseudoVersion returns whether v is a pseudo-version with a zero base, -// timestamp, and revision, as returned by [ZeroPseudoVersion]. -func IsZeroPseudoVersion(v string) bool { - return v == ZeroPseudoVersion(semver.Major(v)) -} - -// PseudoVersionTime returns the time stamp of the pseudo-version v. -// It returns an error if v is not a pseudo-version or if the time stamp -// embedded in the pseudo-version is not a valid time. -func PseudoVersionTime(v string) (time.Time, error) { - _, timestamp, _, _, err := parsePseudoVersion(v) - if err != nil { - return time.Time{}, err - } - t, err := time.Parse("20060102150405", timestamp) - if err != nil { - return time.Time{}, &InvalidVersionError{ - Version: v, - Pseudo: true, - Err: fmt.Errorf("malformed time %q", timestamp), - } - } - return t, nil -} - -// PseudoVersionRev returns the revision identifier of the pseudo-version v. -// It returns an error if v is not a pseudo-version. -func PseudoVersionRev(v string) (rev string, err error) { - _, _, rev, _, err = parsePseudoVersion(v) - return -} - -// PseudoVersionBase returns the canonical parent version, if any, upon which -// the pseudo-version v is based. -// -// If v has no parent version (that is, if it is "vX.0.0-[…]"), -// PseudoVersionBase returns the empty string and a nil error. -func PseudoVersionBase(v string) (string, error) { - base, _, _, build, err := parsePseudoVersion(v) - if err != nil { - return "", err - } - - switch pre := semver.Prerelease(base); pre { - case "": - // vX.0.0-yyyymmddhhmmss-abcdef123456 → "" - if build != "" { - // Pseudo-versions of the form vX.0.0-yyyymmddhhmmss-abcdef123456+incompatible - // are nonsensical: the "vX.0.0-" prefix implies that there is no parent tag, - // but the "+incompatible" suffix implies that the major version of - // the parent tag is not compatible with the module's import path. - // - // There are a few such entries in the index generated by proxy.golang.org, - // but we believe those entries were generated by the proxy itself. - return "", &InvalidVersionError{ - Version: v, - Pseudo: true, - Err: fmt.Errorf("lacks base version, but has build metadata %q", build), - } - } - return "", nil - - case "-0": - // vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z - // vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z+incompatible - base = strings.TrimSuffix(base, pre) - i := strings.LastIndexByte(base, '.') - if i < 0 { - panic("base from parsePseudoVersion missing patch number: " + base) - } - patch := decDecimal(base[i+1:]) - if patch == "" { - // vX.0.0-0 is invalid, but has been observed in the wild in the index - // generated by requests to proxy.golang.org. - // - // NOTE(bcmills): I cannot find a historical bug that accounts for - // pseudo-versions of this form, nor have I seen such versions in any - // actual go.mod files. If we find actual examples of this form and a - // reasonable theory of how they came into existence, it seems fine to - // treat them as equivalent to vX.0.0 (especially since the invalid - // pseudo-versions have lower precedence than the real ones). For now, we - // reject them. - return "", &InvalidVersionError{ - Version: v, - Pseudo: true, - Err: fmt.Errorf("version before %s would have negative patch number", base), - } - } - return base[:i+1] + patch + build, nil - - default: - // vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z-pre - // vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z-pre+incompatible - if !strings.HasSuffix(base, ".0") { - panic(`base from parsePseudoVersion missing ".0" before date: ` + base) - } - return strings.TrimSuffix(base, ".0") + build, nil - } -} - -var errPseudoSyntax = errors.New("syntax error") - -func parsePseudoVersion(v string) (base, timestamp, rev, build string, err error) { - if !IsPseudoVersion(v) { - return "", "", "", "", &InvalidVersionError{ - Version: v, - Pseudo: true, - Err: errPseudoSyntax, - } - } - build = semver.Build(v) - v = strings.TrimSuffix(v, build) - j := strings.LastIndex(v, "-") - v, rev = v[:j], v[j+1:] - i := strings.LastIndex(v, "-") - if j := strings.LastIndex(v, "."); j > i { - base = v[:j] // "vX.Y.Z-pre.0" or "vX.Y.(Z+1)-0" - timestamp = v[j+1:] - } else { - base = v[:i] // "vX.0.0" - timestamp = v[i+1:] - } - return base, timestamp, rev, build, nil -} diff --git a/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go b/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go deleted file mode 100644 index 0fb4e7eea8..0000000000 --- a/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go +++ /dev/null @@ -1,663 +0,0 @@ -// Copyright 2013 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 astutil - -// This file defines utilities for working with source positions. - -import ( - "fmt" - "go/ast" - "go/token" - "sort" -) - -// PathEnclosingInterval returns the node that encloses the source -// interval [start, end), and all its ancestors up to the AST root. -// -// The definition of "enclosing" used by this function considers -// additional whitespace abutting a node to be enclosed by it. -// In this example: -// -// z := x + y // add them -// <-A-> -// <----B-----> -// -// the ast.BinaryExpr(+) node is considered to enclose interval B -// even though its [Pos()..End()) is actually only interval A. -// This behaviour makes user interfaces more tolerant of imperfect -// input. -// -// This function treats tokens as nodes, though they are not included -// in the result. e.g. PathEnclosingInterval("+") returns the -// enclosing ast.BinaryExpr("x + y"). -// -// If start==end, the 1-char interval following start is used instead. -// -// The 'exact' result is true if the interval contains only path[0] -// and perhaps some adjacent whitespace. It is false if the interval -// overlaps multiple children of path[0], or if it contains only -// interior whitespace of path[0]. -// In this example: -// -// z := x + y // add them -// <--C--> <---E--> -// ^ -// D -// -// intervals C, D and E are inexact. C is contained by the -// z-assignment statement, because it spans three of its children (:=, -// x, +). So too is the 1-char interval D, because it contains only -// interior whitespace of the assignment. E is considered interior -// whitespace of the BlockStmt containing the assignment. -// -// The resulting path is never empty; it always contains at least the -// 'root' *ast.File. Ideally PathEnclosingInterval would reject -// intervals that lie wholly or partially outside the range of the -// file, but unfortunately ast.File records only the token.Pos of -// the 'package' keyword, but not of the start of the file itself. -func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) { - // fmt.Printf("EnclosingInterval %d %d\n", start, end) // debugging - - // Precondition: node.[Pos..End) and adjoining whitespace contain [start, end). - var visit func(node ast.Node) bool - visit = func(node ast.Node) bool { - path = append(path, node) - - nodePos := node.Pos() - nodeEnd := node.End() - - // fmt.Printf("visit(%T, %d, %d)\n", node, nodePos, nodeEnd) // debugging - - // Intersect [start, end) with interval of node. - if start < nodePos { - start = nodePos - } - if end > nodeEnd { - end = nodeEnd - } - - // Find sole child that contains [start, end). - children := childrenOf(node) - l := len(children) - for i, child := range children { - // [childPos, childEnd) is unaugmented interval of child. - childPos := child.Pos() - childEnd := child.End() - - // [augPos, augEnd) is whitespace-augmented interval of child. - augPos := childPos - augEnd := childEnd - if i > 0 { - augPos = children[i-1].End() // start of preceding whitespace - } - if i < l-1 { - nextChildPos := children[i+1].Pos() - // Does [start, end) lie between child and next child? - if start >= augEnd && end <= nextChildPos { - return false // inexact match - } - augEnd = nextChildPos // end of following whitespace - } - - // fmt.Printf("\tchild %d: [%d..%d)\tcontains interval [%d..%d)?\n", - // i, augPos, augEnd, start, end) // debugging - - // Does augmented child strictly contain [start, end)? - if augPos <= start && end <= augEnd { - if is[tokenNode](child) { - return true - } - - // childrenOf elides the FuncType node beneath FuncDecl. - // Add it back here for TypeParams, Params, Results, - // all FieldLists). But we don't add it back for the "func" token - // even though it is the tree at FuncDecl.Type.Func. - if decl, ok := node.(*ast.FuncDecl); ok { - if fields, ok := child.(*ast.FieldList); ok && fields != decl.Recv { - path = append(path, decl.Type) - } - } - - return visit(child) - } - - // Does [start, end) overlap multiple children? - // i.e. left-augmented child contains start - // but LR-augmented child does not contain end. - if start < childEnd && end > augEnd { - break - } - } - - // No single child contained [start, end), - // so node is the result. Is it exact? - - // (It's tempting to put this condition before the - // child loop, but it gives the wrong result in the - // case where a node (e.g. ExprStmt) and its sole - // child have equal intervals.) - if start == nodePos && end == nodeEnd { - return true // exact match - } - - return false // inexact: overlaps multiple children - } - - // Ensure [start,end) is nondecreasing. - if start > end { - start, end = end, start - } - - if start < root.End() && end > root.Pos() { - if start == end { - end = start + 1 // empty interval => interval of size 1 - } - exact = visit(root) - - // Reverse the path: - for i, l := 0, len(path); i < l/2; i++ { - path[i], path[l-1-i] = path[l-1-i], path[i] - } - } else { - // Selection lies within whitespace preceding the - // first (or following the last) declaration in the file. - // The result nonetheless always includes the ast.File. - path = append(path, root) - } - - return -} - -// tokenNode is a dummy implementation of ast.Node for a single token. -// They are used transiently by PathEnclosingInterval but never escape -// this package. -type tokenNode struct { - pos token.Pos - end token.Pos -} - -func (n tokenNode) Pos() token.Pos { - return n.pos -} - -func (n tokenNode) End() token.Pos { - return n.end -} - -func tok(pos token.Pos, len int) ast.Node { - return tokenNode{pos, pos + token.Pos(len)} -} - -// childrenOf returns the direct non-nil children of ast.Node n. -// It may include fake ast.Node implementations for bare tokens. -// it is not safe to call (e.g.) ast.Walk on such nodes. -func childrenOf(n ast.Node) []ast.Node { - var children []ast.Node - - // First add nodes for all true subtrees. - ast.Inspect(n, func(node ast.Node) bool { - if node == n { // push n - return true // recur - } - if node != nil { // push child - children = append(children, node) - } - return false // no recursion - }) - - // TODO(adonovan): be more careful about missing (!Pos.Valid) - // tokens in trees produced from invalid input. - - // Then add fake Nodes for bare tokens. - switch n := n.(type) { - case *ast.ArrayType: - children = append(children, - tok(n.Lbrack, len("[")), - tok(n.Elt.End(), len("]"))) - - case *ast.AssignStmt: - children = append(children, - tok(n.TokPos, len(n.Tok.String()))) - - case *ast.BasicLit: - children = append(children, - tok(n.ValuePos, len(n.Value))) - - case *ast.BinaryExpr: - children = append(children, tok(n.OpPos, len(n.Op.String()))) - - case *ast.BlockStmt: - if n.Lbrace.IsValid() { - children = append(children, tok(n.Lbrace, len("{"))) - } - if n.Rbrace.IsValid() { - children = append(children, tok(n.Rbrace, len("}"))) - } - - case *ast.BranchStmt: - children = append(children, - tok(n.TokPos, len(n.Tok.String()))) - - case *ast.CallExpr: - children = append(children, - tok(n.Lparen, len("(")), - tok(n.Rparen, len(")"))) - if n.Ellipsis != 0 { - children = append(children, tok(n.Ellipsis, len("..."))) - } - - case *ast.CaseClause: - if n.List == nil { - children = append(children, - tok(n.Case, len("default"))) - } else { - children = append(children, - tok(n.Case, len("case"))) - } - children = append(children, tok(n.Colon, len(":"))) - - case *ast.ChanType: - switch n.Dir { - case ast.RECV: - children = append(children, tok(n.Begin, len("<-chan"))) - case ast.SEND: - children = append(children, tok(n.Begin, len("chan<-"))) - case ast.RECV | ast.SEND: - children = append(children, tok(n.Begin, len("chan"))) - } - - case *ast.CommClause: - if n.Comm == nil { - children = append(children, - tok(n.Case, len("default"))) - } else { - children = append(children, - tok(n.Case, len("case"))) - } - children = append(children, tok(n.Colon, len(":"))) - - case *ast.Comment: - // nop - - case *ast.CommentGroup: - // nop - - case *ast.CompositeLit: - children = append(children, - tok(n.Lbrace, len("{")), - tok(n.Rbrace, len("{"))) - - case *ast.DeclStmt: - // nop - - case *ast.DeferStmt: - children = append(children, - tok(n.Defer, len("defer"))) - - case *ast.Ellipsis: - children = append(children, - tok(n.Ellipsis, len("..."))) - - case *ast.EmptyStmt: - // nop - - case *ast.ExprStmt: - // nop - - case *ast.Field: - // TODO(adonovan): Field.{Doc,Comment,Tag}? - - case *ast.FieldList: - if n.Opening.IsValid() { - children = append(children, tok(n.Opening, len("("))) - } - if n.Closing.IsValid() { - children = append(children, tok(n.Closing, len(")"))) - } - - case *ast.File: - // TODO test: Doc - children = append(children, - tok(n.Package, len("package"))) - - case *ast.ForStmt: - children = append(children, - tok(n.For, len("for"))) - - case *ast.FuncDecl: - // TODO(adonovan): FuncDecl.Comment? - - // Uniquely, FuncDecl breaks the invariant that - // preorder traversal yields tokens in lexical order: - // in fact, FuncDecl.Recv precedes FuncDecl.Type.Func. - // - // As a workaround, we inline the case for FuncType - // here and order things correctly. - // We also need to insert the elided FuncType just - // before the 'visit' recursion. - // - children = nil // discard ast.Walk(FuncDecl) info subtrees - children = append(children, tok(n.Type.Func, len("func"))) - if n.Recv != nil { - children = append(children, n.Recv) - } - children = append(children, n.Name) - if tparams := n.Type.TypeParams; tparams != nil { - children = append(children, tparams) - } - if n.Type.Params != nil { - children = append(children, n.Type.Params) - } - if n.Type.Results != nil { - children = append(children, n.Type.Results) - } - if n.Body != nil { - children = append(children, n.Body) - } - - case *ast.FuncLit: - // nop - - case *ast.FuncType: - if n.Func != 0 { - children = append(children, - tok(n.Func, len("func"))) - } - - case *ast.GenDecl: - children = append(children, - tok(n.TokPos, len(n.Tok.String()))) - if n.Lparen != 0 { - children = append(children, - tok(n.Lparen, len("(")), - tok(n.Rparen, len(")"))) - } - - case *ast.GoStmt: - children = append(children, - tok(n.Go, len("go"))) - - case *ast.Ident: - children = append(children, - tok(n.NamePos, len(n.Name))) - - case *ast.IfStmt: - children = append(children, - tok(n.If, len("if"))) - - case *ast.ImportSpec: - // TODO(adonovan): ImportSpec.{Doc,EndPos}? - - case *ast.IncDecStmt: - children = append(children, - tok(n.TokPos, len(n.Tok.String()))) - - case *ast.IndexExpr: - children = append(children, - tok(n.Lbrack, len("[")), - tok(n.Rbrack, len("]"))) - - case *ast.IndexListExpr: - children = append(children, - tok(n.Lbrack, len("[")), - tok(n.Rbrack, len("]"))) - - case *ast.InterfaceType: - children = append(children, - tok(n.Interface, len("interface"))) - - case *ast.KeyValueExpr: - children = append(children, - tok(n.Colon, len(":"))) - - case *ast.LabeledStmt: - children = append(children, - tok(n.Colon, len(":"))) - - case *ast.MapType: - children = append(children, - tok(n.Map, len("map"))) - - case *ast.ParenExpr: - children = append(children, - tok(n.Lparen, len("(")), - tok(n.Rparen, len(")"))) - - case *ast.RangeStmt: - children = append(children, - tok(n.For, len("for")), - tok(n.TokPos, len(n.Tok.String()))) - - case *ast.ReturnStmt: - children = append(children, - tok(n.Return, len("return"))) - - case *ast.SelectStmt: - children = append(children, - tok(n.Select, len("select"))) - - case *ast.SelectorExpr: - // nop - - case *ast.SendStmt: - children = append(children, - tok(n.Arrow, len("<-"))) - - case *ast.SliceExpr: - children = append(children, - tok(n.Lbrack, len("[")), - tok(n.Rbrack, len("]"))) - - case *ast.StarExpr: - children = append(children, tok(n.Star, len("*"))) - - case *ast.StructType: - children = append(children, tok(n.Struct, len("struct"))) - - case *ast.SwitchStmt: - children = append(children, tok(n.Switch, len("switch"))) - - case *ast.TypeAssertExpr: - children = append(children, - tok(n.Lparen-1, len(".")), - tok(n.Lparen, len("(")), - tok(n.Rparen, len(")"))) - - case *ast.TypeSpec: - // TODO(adonovan): TypeSpec.{Doc,Comment}? - - case *ast.TypeSwitchStmt: - children = append(children, tok(n.Switch, len("switch"))) - - case *ast.UnaryExpr: - children = append(children, tok(n.OpPos, len(n.Op.String()))) - - case *ast.ValueSpec: - // TODO(adonovan): ValueSpec.{Doc,Comment}? - - case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt: - // nop - } - - // TODO(adonovan): opt: merge the logic of ast.Inspect() into - // the switch above so we can make interleaved callbacks for - // both Nodes and Tokens in the right order and avoid the need - // to sort. - sort.Sort(byPos(children)) - - return children -} - -type byPos []ast.Node - -func (sl byPos) Len() int { - return len(sl) -} -func (sl byPos) Less(i, j int) bool { - return sl[i].Pos() < sl[j].Pos() -} -func (sl byPos) Swap(i, j int) { - sl[i], sl[j] = sl[j], sl[i] -} - -// NodeDescription returns a description of the concrete type of n suitable -// for a user interface. -// -// TODO(adonovan): in some cases (e.g. Field, FieldList, Ident, -// StarExpr) we could be much more specific given the path to the AST -// root. Perhaps we should do that. -func NodeDescription(n ast.Node) string { - switch n := n.(type) { - case *ast.ArrayType: - return "array type" - case *ast.AssignStmt: - return "assignment" - case *ast.BadDecl: - return "bad declaration" - case *ast.BadExpr: - return "bad expression" - case *ast.BadStmt: - return "bad statement" - case *ast.BasicLit: - return "basic literal" - case *ast.BinaryExpr: - return fmt.Sprintf("binary %s operation", n.Op) - case *ast.BlockStmt: - return "block" - case *ast.BranchStmt: - switch n.Tok { - case token.BREAK: - return "break statement" - case token.CONTINUE: - return "continue statement" - case token.GOTO: - return "goto statement" - case token.FALLTHROUGH: - return "fall-through statement" - } - case *ast.CallExpr: - if len(n.Args) == 1 && !n.Ellipsis.IsValid() { - return "function call (or conversion)" - } - return "function call" - case *ast.CaseClause: - return "case clause" - case *ast.ChanType: - return "channel type" - case *ast.CommClause: - return "communication clause" - case *ast.Comment: - return "comment" - case *ast.CommentGroup: - return "comment group" - case *ast.CompositeLit: - return "composite literal" - case *ast.DeclStmt: - return NodeDescription(n.Decl) + " statement" - case *ast.DeferStmt: - return "defer statement" - case *ast.Ellipsis: - return "ellipsis" - case *ast.EmptyStmt: - return "empty statement" - case *ast.ExprStmt: - return "expression statement" - case *ast.Field: - // Can be any of these: - // struct {x, y int} -- struct field(s) - // struct {T} -- anon struct field - // interface {I} -- interface embedding - // interface {f()} -- interface method - // func (A) func(B) C -- receiver, param(s), result(s) - return "field/method/parameter" - case *ast.FieldList: - return "field/method/parameter list" - case *ast.File: - return "source file" - case *ast.ForStmt: - return "for loop" - case *ast.FuncDecl: - return "function declaration" - case *ast.FuncLit: - return "function literal" - case *ast.FuncType: - return "function type" - case *ast.GenDecl: - switch n.Tok { - case token.IMPORT: - return "import declaration" - case token.CONST: - return "constant declaration" - case token.TYPE: - return "type declaration" - case token.VAR: - return "variable declaration" - } - case *ast.GoStmt: - return "go statement" - case *ast.Ident: - return "identifier" - case *ast.IfStmt: - return "if statement" - case *ast.ImportSpec: - return "import specification" - case *ast.IncDecStmt: - if n.Tok == token.INC { - return "increment statement" - } - return "decrement statement" - case *ast.IndexExpr: - return "index expression" - case *ast.IndexListExpr: - return "index list expression" - case *ast.InterfaceType: - return "interface type" - case *ast.KeyValueExpr: - return "key/value association" - case *ast.LabeledStmt: - return "statement label" - case *ast.MapType: - return "map type" - case *ast.Package: - return "package" - case *ast.ParenExpr: - return "parenthesized " + NodeDescription(n.X) - case *ast.RangeStmt: - return "range loop" - case *ast.ReturnStmt: - return "return statement" - case *ast.SelectStmt: - return "select statement" - case *ast.SelectorExpr: - return "selector" - case *ast.SendStmt: - return "channel send" - case *ast.SliceExpr: - return "slice expression" - case *ast.StarExpr: - return "*-operation" // load/store expr or pointer type - case *ast.StructType: - return "struct type" - case *ast.SwitchStmt: - return "switch statement" - case *ast.TypeAssertExpr: - return "type assertion" - case *ast.TypeSpec: - return "type specification" - case *ast.TypeSwitchStmt: - return "type switch" - case *ast.UnaryExpr: - return fmt.Sprintf("unary %s operation", n.Op) - case *ast.ValueSpec: - return "value specification" - - } - panic(fmt.Sprintf("unexpected node type: %T", n)) -} - -func is[T any](x any) bool { - _, ok := x.(T) - return ok -} diff --git a/vendor/golang.org/x/tools/go/ast/astutil/imports.go b/vendor/golang.org/x/tools/go/ast/astutil/imports.go deleted file mode 100644 index adb4711019..0000000000 --- a/vendor/golang.org/x/tools/go/ast/astutil/imports.go +++ /dev/null @@ -1,487 +0,0 @@ -// Copyright 2013 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 astutil contains common utilities for working with the Go AST. -package astutil // import "golang.org/x/tools/go/ast/astutil" - -import ( - "fmt" - "go/ast" - "go/token" - "reflect" - "slices" - "strconv" - "strings" -) - -// AddImport adds the import path to the file f, if absent. -func AddImport(fset *token.FileSet, f *ast.File, path string) (added bool) { - return AddNamedImport(fset, f, "", path) -} - -// AddNamedImport adds the import with the given name and path to the file f, if absent. -// If name is not empty, it is used to rename the import. -// -// For example, calling -// -// AddNamedImport(fset, f, "pathpkg", "path") -// -// adds -// -// import pathpkg "path" -func AddNamedImport(fset *token.FileSet, f *ast.File, name, path string) (added bool) { - if imports(f, name, path) { - return false - } - - newImport := &ast.ImportSpec{ - Path: &ast.BasicLit{ - Kind: token.STRING, - Value: strconv.Quote(path), - }, - } - if name != "" { - newImport.Name = &ast.Ident{Name: name} - } - - // Find an import decl to add to. - // The goal is to find an existing import - // whose import path has the longest shared - // prefix with path. - var ( - bestMatch = -1 // length of longest shared prefix - lastImport = -1 // index in f.Decls of the file's final import decl - impDecl *ast.GenDecl // import decl containing the best match - impIndex = -1 // spec index in impDecl containing the best match - - isThirdPartyPath = isThirdParty(path) - ) - for i, decl := range f.Decls { - gen, ok := decl.(*ast.GenDecl) - if ok && gen.Tok == token.IMPORT { - lastImport = i - // Do not add to import "C", to avoid disrupting the - // association with its doc comment, breaking cgo. - if declImports(gen, "C") { - continue - } - - // Match an empty import decl if that's all that is available. - if len(gen.Specs) == 0 && bestMatch == -1 { - impDecl = gen - } - - // Compute longest shared prefix with imports in this group and find best - // matched import spec. - // 1. Always prefer import spec with longest shared prefix. - // 2. While match length is 0, - // - for stdlib package: prefer first import spec. - // - for third party package: prefer first third party import spec. - // We cannot use last import spec as best match for third party package - // because grouped imports are usually placed last by goimports -local - // flag. - // See issue #19190. - seenAnyThirdParty := false - for j, spec := range gen.Specs { - impspec := spec.(*ast.ImportSpec) - p := importPath(impspec) - n := matchLen(p, path) - if n > bestMatch || (bestMatch == 0 && !seenAnyThirdParty && isThirdPartyPath) { - bestMatch = n - impDecl = gen - impIndex = j - } - seenAnyThirdParty = seenAnyThirdParty || isThirdParty(p) - } - } - } - - // If no import decl found, add one after the last import. - if impDecl == nil { - impDecl = &ast.GenDecl{ - Tok: token.IMPORT, - } - if lastImport >= 0 { - impDecl.TokPos = f.Decls[lastImport].End() - } else { - // There are no existing imports. - // Our new import, preceded by a blank line, goes after the package declaration - // and after the comment, if any, that starts on the same line as the - // package declaration. - impDecl.TokPos = f.Package - - file := fset.File(f.Package) - pkgLine := file.Line(f.Package) - for _, c := range f.Comments { - if file.Line(c.Pos()) > pkgLine { - break - } - // +2 for a blank line - impDecl.TokPos = c.End() + 2 - } - } - f.Decls = append(f.Decls, nil) - copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:]) - f.Decls[lastImport+1] = impDecl - } - - // Insert new import at insertAt. - insertAt := 0 - if impIndex >= 0 { - // insert after the found import - insertAt = impIndex + 1 - } - impDecl.Specs = append(impDecl.Specs, nil) - copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:]) - impDecl.Specs[insertAt] = newImport - pos := impDecl.Pos() - if insertAt > 0 { - // If there is a comment after an existing import, preserve the comment - // position by adding the new import after the comment. - if spec, ok := impDecl.Specs[insertAt-1].(*ast.ImportSpec); ok && spec.Comment != nil { - pos = spec.Comment.End() - } else { - // Assign same position as the previous import, - // so that the sorter sees it as being in the same block. - pos = impDecl.Specs[insertAt-1].Pos() - } - } - if newImport.Name != nil { - newImport.Name.NamePos = pos - } - updateBasicLitPos(newImport.Path, pos) - newImport.EndPos = pos - - // Clean up parens. impDecl contains at least one spec. - if len(impDecl.Specs) == 1 { - // Remove unneeded parens. - impDecl.Lparen = token.NoPos - } else if !impDecl.Lparen.IsValid() { - // impDecl needs parens added. - impDecl.Lparen = impDecl.Specs[0].Pos() - } - - f.Imports = append(f.Imports, newImport) - - if len(f.Decls) <= 1 { - return true - } - - // Merge all the import declarations into the first one. - var first *ast.GenDecl - for i := 0; i < len(f.Decls); i++ { - decl := f.Decls[i] - gen, ok := decl.(*ast.GenDecl) - if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") { - continue - } - if first == nil { - first = gen - continue // Don't touch the first one. - } - // We now know there is more than one package in this import - // declaration. Ensure that it ends up parenthesized. - first.Lparen = first.Pos() - // Move the imports of the other import declaration to the first one. - for _, spec := range gen.Specs { - updateBasicLitPos(spec.(*ast.ImportSpec).Path, first.Pos()) - first.Specs = append(first.Specs, spec) - } - f.Decls = slices.Delete(f.Decls, i, i+1) - i-- - } - - return true -} - -func isThirdParty(importPath string) bool { - // Third party package import path usually contains "." (".com", ".org", ...) - // This logic is taken from golang.org/x/tools/imports package. - return strings.Contains(importPath, ".") -} - -// DeleteImport deletes the import path from the file f, if present. -// If there are duplicate import declarations, all matching ones are deleted. -func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) { - return DeleteNamedImport(fset, f, "", path) -} - -// DeleteNamedImport deletes the import with the given name and path from the file f, if present. -// If there are duplicate import declarations, all matching ones are deleted. -func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (deleted bool) { - var ( - delspecs = make(map[*ast.ImportSpec]bool) - delcomments = make(map[*ast.CommentGroup]bool) - ) - - // Find the import nodes that import path, if any. - for i := 0; i < len(f.Decls); i++ { - gen, ok := f.Decls[i].(*ast.GenDecl) - if !ok || gen.Tok != token.IMPORT { - continue - } - for j := 0; j < len(gen.Specs); j++ { - impspec := gen.Specs[j].(*ast.ImportSpec) - if importName(impspec) != name || importPath(impspec) != path { - continue - } - - // We found an import spec that imports path. - // Delete it. - delspecs[impspec] = true - deleted = true - gen.Specs = slices.Delete(gen.Specs, j, j+1) - - // If this was the last import spec in this decl, - // delete the decl, too. - if len(gen.Specs) == 0 { - f.Decls = slices.Delete(f.Decls, i, i+1) - i-- - break - } else if len(gen.Specs) == 1 { - if impspec.Doc != nil { - delcomments[impspec.Doc] = true - } - if impspec.Comment != nil { - delcomments[impspec.Comment] = true - } - for _, cg := range f.Comments { - // Found comment on the same line as the import spec. - if cg.End() < impspec.Pos() && fset.Position(cg.End()).Line == fset.Position(impspec.Pos()).Line { - delcomments[cg] = true - break - } - } - - spec := gen.Specs[0].(*ast.ImportSpec) - - // Move the documentation right after the import decl. - if spec.Doc != nil { - for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Doc.Pos()).Line { - fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line) - } - } - for _, cg := range f.Comments { - if cg.End() < spec.Pos() && fset.Position(cg.End()).Line == fset.Position(spec.Pos()).Line { - for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Pos()).Line { - fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line) - } - break - } - } - } - if j > 0 { - lastImpspec := gen.Specs[j-1].(*ast.ImportSpec) - lastLine := fset.PositionFor(lastImpspec.Path.ValuePos, false).Line - line := fset.PositionFor(impspec.Path.ValuePos, false).Line - - // We deleted an entry but now there may be - // a blank line-sized hole where the import was. - if line-lastLine > 1 || !gen.Rparen.IsValid() { - // There was a blank line immediately preceding the deleted import, - // so there's no need to close the hole. The right parenthesis is - // invalid after AddImport to an import statement without parenthesis. - // Do nothing. - } else if line != fset.File(gen.Rparen).LineCount() { - // There was no blank line. Close the hole. - fset.File(gen.Rparen).MergeLine(line) - } - } - j-- - } - } - - // Delete imports from f.Imports. - before := len(f.Imports) - f.Imports = slices.DeleteFunc(f.Imports, func(imp *ast.ImportSpec) bool { - _, ok := delspecs[imp] - return ok - }) - if len(f.Imports)+len(delspecs) != before { - // This can happen when the AST is invalid (i.e. imports differ between f.Decls and f.Imports). - panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs)) - } - - // Delete comments from f.Comments. - f.Comments = slices.DeleteFunc(f.Comments, func(cg *ast.CommentGroup) bool { - _, ok := delcomments[cg] - return ok - }) - - return -} - -// RewriteImport rewrites any import of path oldPath to path newPath. -func RewriteImport(fset *token.FileSet, f *ast.File, oldPath, newPath string) (rewrote bool) { - for _, imp := range f.Imports { - if importPath(imp) == oldPath { - rewrote = true - // record old End, because the default is to compute - // it using the length of imp.Path.Value. - imp.EndPos = imp.End() - imp.Path.Value = strconv.Quote(newPath) - } - } - return -} - -// UsesImport reports whether a given import is used. -// The provided File must have been parsed with syntactic object resolution -// (not using go/parser.SkipObjectResolution). -func UsesImport(f *ast.File, path string) (used bool) { - if f.Scope == nil { - panic("file f was not parsed with syntactic object resolution") - } - spec := importSpec(f, path) - if spec == nil { - return - } - - name := spec.Name.String() - switch name { - case "": - // If the package name is not explicitly specified, - // make an educated guess. This is not guaranteed to be correct. - lastSlash := strings.LastIndex(path, "/") - if lastSlash == -1 { - name = path - } else { - name = path[lastSlash+1:] - } - case "_", ".": - // Not sure if this import is used - err on the side of caution. - return true - } - - ast.Walk(visitFn(func(n ast.Node) { - sel, ok := n.(*ast.SelectorExpr) - if ok && isTopName(sel.X, name) { - used = true - } - }), f) - - return -} - -type visitFn func(node ast.Node) - -func (fn visitFn) Visit(node ast.Node) ast.Visitor { - fn(node) - return fn -} - -// imports reports whether f has an import with the specified name and path. -func imports(f *ast.File, name, path string) bool { - for _, s := range f.Imports { - if importName(s) == name && importPath(s) == path { - return true - } - } - return false -} - -// importSpec returns the import spec if f imports path, -// or nil otherwise. -func importSpec(f *ast.File, path string) *ast.ImportSpec { - for _, s := range f.Imports { - if importPath(s) == path { - return s - } - } - return nil -} - -// importName returns the name of s, -// or "" if the import is not named. -func importName(s *ast.ImportSpec) string { - if s.Name == nil { - return "" - } - return s.Name.Name -} - -// importPath returns the unquoted import path of s, -// or "" if the path is not properly quoted. -func importPath(s *ast.ImportSpec) string { - t, err := strconv.Unquote(s.Path.Value) - if err != nil { - return "" - } - return t -} - -// declImports reports whether gen contains an import of path. -func declImports(gen *ast.GenDecl, path string) bool { - if gen.Tok != token.IMPORT { - return false - } - for _, spec := range gen.Specs { - impspec := spec.(*ast.ImportSpec) - if importPath(impspec) == path { - return true - } - } - return false -} - -// matchLen returns the length of the longest path segment prefix shared by x and y. -func matchLen(x, y string) int { - n := 0 - for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ { - if x[i] == '/' { - n++ - } - } - return n -} - -// isTopName returns true if n is a top-level unresolved identifier with the given name. -func isTopName(n ast.Expr, name string) bool { - id, ok := n.(*ast.Ident) - return ok && id.Name == name && id.Obj == nil -} - -// Imports returns the file imports grouped by paragraph. -func Imports(fset *token.FileSet, f *ast.File) [][]*ast.ImportSpec { - var groups [][]*ast.ImportSpec - - for _, decl := range f.Decls { - genDecl, ok := decl.(*ast.GenDecl) - if !ok || genDecl.Tok != token.IMPORT { - break - } - - group := []*ast.ImportSpec{} - - var lastLine int - for _, spec := range genDecl.Specs { - importSpec := spec.(*ast.ImportSpec) - pos := importSpec.Path.ValuePos - line := fset.Position(pos).Line - if lastLine > 0 && pos > 0 && line-lastLine > 1 { - groups = append(groups, group) - group = []*ast.ImportSpec{} - } - group = append(group, importSpec) - lastLine = line - } - groups = append(groups, group) - } - - return groups -} - -// updateBasicLitPos updates lit.Pos, -// ensuring that lit.End (if set) is displaced by the same amount. -// (See https://go.dev/issue/76395.) -func updateBasicLitPos(lit *ast.BasicLit, pos token.Pos) { - len := lit.End() - lit.Pos() - lit.ValuePos = pos - // TODO(adonovan): after go1.26, simplify to: - // lit.ValueEnd = pos + len - v := reflect.ValueOf(lit).Elem().FieldByName("ValueEnd") - if v.IsValid() && v.Int() != 0 { - v.SetInt(int64(pos + len)) - } -} diff --git a/vendor/golang.org/x/tools/go/ast/astutil/rewrite.go b/vendor/golang.org/x/tools/go/ast/astutil/rewrite.go deleted file mode 100644 index 4ad0549304..0000000000 --- a/vendor/golang.org/x/tools/go/ast/astutil/rewrite.go +++ /dev/null @@ -1,490 +0,0 @@ -// Copyright 2017 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 astutil - -import ( - "fmt" - "go/ast" - "reflect" - "sort" -) - -// An ApplyFunc is invoked by Apply for each node n, even if n is nil, -// before and/or after the node's children, using a Cursor describing -// the current node and providing operations on it. -// -// The return value of ApplyFunc controls the syntax tree traversal. -// See Apply for details. -type ApplyFunc func(*Cursor) bool - -// Apply traverses a syntax tree recursively, starting with root, -// and calling pre and post for each node as described below. -// Apply returns the syntax tree, possibly modified. -// -// If pre is not nil, it is called for each node before the node's -// children are traversed (pre-order). If pre returns false, no -// children are traversed, and post is not called for that node. -// -// If post is not nil, and a prior call of pre didn't return false, -// post is called for each node after its children are traversed -// (post-order). If post returns false, traversal is terminated and -// Apply returns immediately. -// -// Only fields that refer to AST nodes are considered children; -// i.e., token.Pos, Scopes, Objects, and fields of basic types -// (strings, etc.) are ignored. -// -// Children are traversed in the order in which they appear in the -// respective node's struct definition. A package's files are -// traversed in the filenames' alphabetical order. -func Apply(root ast.Node, pre, post ApplyFunc) (result ast.Node) { - parent := &struct{ ast.Node }{root} - defer func() { - if r := recover(); r != nil && r != abort { - panic(r) - } - result = parent.Node - }() - a := &application{pre: pre, post: post} - a.apply(parent, "Node", nil, root) - return -} - -var abort = new(int) // singleton, to signal termination of Apply - -// A Cursor describes a node encountered during Apply. -// Information about the node and its parent is available -// from the Node, Parent, Name, and Index methods. -// -// If p is a variable of type and value of the current parent node -// c.Parent(), and f is the field identifier with name c.Name(), -// the following invariants hold: -// -// p.f == c.Node() if c.Index() < 0 -// p.f[c.Index()] == c.Node() if c.Index() >= 0 -// -// The methods Replace, Delete, InsertBefore, and InsertAfter -// can be used to change the AST without disrupting Apply. -// -// This type is not to be confused with [inspector.Cursor] from -// package [golang.org/x/tools/go/ast/inspector], which provides -// stateless navigation of immutable syntax trees. -type Cursor struct { - parent ast.Node - name string - iter *iterator // valid if non-nil - node ast.Node -} - -// Node returns the current Node. -func (c *Cursor) Node() ast.Node { return c.node } - -// Parent returns the parent of the current Node. -func (c *Cursor) Parent() ast.Node { return c.parent } - -// Name returns the name of the parent Node field that contains the current Node. -// If the parent is a *ast.Package and the current Node is a *ast.File, Name returns -// the filename for the current Node. -func (c *Cursor) Name() string { return c.name } - -// Index reports the index >= 0 of the current Node in the slice of Nodes that -// contains it, or a value < 0 if the current Node is not part of a slice. -// The index of the current node changes if InsertBefore is called while -// processing the current node. -func (c *Cursor) Index() int { - if c.iter != nil { - return c.iter.index - } - return -1 -} - -// field returns the current node's parent field value. -func (c *Cursor) field() reflect.Value { - return reflect.Indirect(reflect.ValueOf(c.parent)).FieldByName(c.name) -} - -// Replace replaces the current Node with n. -// The replacement node is not walked by Apply. -func (c *Cursor) Replace(n ast.Node) { - if _, ok := c.node.(*ast.File); ok { - file, ok := n.(*ast.File) - if !ok { - panic("attempt to replace *ast.File with non-*ast.File") - } - c.parent.(*ast.Package).Files[c.name] = file - return - } - - v := c.field() - if i := c.Index(); i >= 0 { - v = v.Index(i) - } - v.Set(reflect.ValueOf(n)) -} - -// Delete deletes the current Node from its containing slice. -// If the current Node is not part of a slice, Delete panics. -// As a special case, if the current node is a package file, -// Delete removes it from the package's Files map. -func (c *Cursor) Delete() { - if _, ok := c.node.(*ast.File); ok { - delete(c.parent.(*ast.Package).Files, c.name) - return - } - - i := c.Index() - if i < 0 { - panic("Delete node not contained in slice") - } - v := c.field() - l := v.Len() - reflect.Copy(v.Slice(i, l), v.Slice(i+1, l)) - v.Index(l - 1).Set(reflect.Zero(v.Type().Elem())) - v.SetLen(l - 1) - c.iter.step-- -} - -// InsertAfter inserts n after the current Node in its containing slice. -// If the current Node is not part of a slice, InsertAfter panics. -// Apply does not walk n. -func (c *Cursor) InsertAfter(n ast.Node) { - i := c.Index() - if i < 0 { - panic("InsertAfter node not contained in slice") - } - v := c.field() - v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem()))) - l := v.Len() - reflect.Copy(v.Slice(i+2, l), v.Slice(i+1, l)) - v.Index(i + 1).Set(reflect.ValueOf(n)) - c.iter.step++ -} - -// InsertBefore inserts n before the current Node in its containing slice. -// If the current Node is not part of a slice, InsertBefore panics. -// Apply will not walk n. -func (c *Cursor) InsertBefore(n ast.Node) { - i := c.Index() - if i < 0 { - panic("InsertBefore node not contained in slice") - } - v := c.field() - v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem()))) - l := v.Len() - reflect.Copy(v.Slice(i+1, l), v.Slice(i, l)) - v.Index(i).Set(reflect.ValueOf(n)) - c.iter.index++ -} - -// application carries all the shared data so we can pass it around cheaply. -type application struct { - pre, post ApplyFunc - cursor Cursor - iter iterator -} - -func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast.Node) { - // convert typed nil into untyped nil - if v := reflect.ValueOf(n); v.Kind() == reflect.Pointer && v.IsNil() { - n = nil - } - - // avoid heap-allocating a new cursor for each apply call; reuse a.cursor instead - saved := a.cursor - a.cursor.parent = parent - a.cursor.name = name - a.cursor.iter = iter - a.cursor.node = n - - if a.pre != nil && !a.pre(&a.cursor) { - a.cursor = saved - return - } - - // walk children - // (the order of the cases matches the order of the corresponding node types in go/ast) - switch n := n.(type) { - case nil: - // nothing to do - - // Comments and fields - case *ast.Comment: - // nothing to do - - case *ast.CommentGroup: - if n != nil { - a.applyList(n, "List") - } - - case *ast.Field: - a.apply(n, "Doc", nil, n.Doc) - a.applyList(n, "Names") - a.apply(n, "Type", nil, n.Type) - a.apply(n, "Tag", nil, n.Tag) - a.apply(n, "Comment", nil, n.Comment) - - case *ast.FieldList: - a.applyList(n, "List") - - // Expressions - case *ast.BadExpr, *ast.Ident, *ast.BasicLit: - // nothing to do - - case *ast.Ellipsis: - a.apply(n, "Elt", nil, n.Elt) - - case *ast.FuncLit: - a.apply(n, "Type", nil, n.Type) - a.apply(n, "Body", nil, n.Body) - - case *ast.CompositeLit: - a.apply(n, "Type", nil, n.Type) - a.applyList(n, "Elts") - - case *ast.ParenExpr: - a.apply(n, "X", nil, n.X) - - case *ast.SelectorExpr: - a.apply(n, "X", nil, n.X) - a.apply(n, "Sel", nil, n.Sel) - - case *ast.IndexExpr: - a.apply(n, "X", nil, n.X) - a.apply(n, "Index", nil, n.Index) - - case *ast.IndexListExpr: - a.apply(n, "X", nil, n.X) - a.applyList(n, "Indices") - - case *ast.SliceExpr: - a.apply(n, "X", nil, n.X) - a.apply(n, "Low", nil, n.Low) - a.apply(n, "High", nil, n.High) - a.apply(n, "Max", nil, n.Max) - - case *ast.TypeAssertExpr: - a.apply(n, "X", nil, n.X) - a.apply(n, "Type", nil, n.Type) - - case *ast.CallExpr: - a.apply(n, "Fun", nil, n.Fun) - a.applyList(n, "Args") - - case *ast.StarExpr: - a.apply(n, "X", nil, n.X) - - case *ast.UnaryExpr: - a.apply(n, "X", nil, n.X) - - case *ast.BinaryExpr: - a.apply(n, "X", nil, n.X) - a.apply(n, "Y", nil, n.Y) - - case *ast.KeyValueExpr: - a.apply(n, "Key", nil, n.Key) - a.apply(n, "Value", nil, n.Value) - - // Types - case *ast.ArrayType: - a.apply(n, "Len", nil, n.Len) - a.apply(n, "Elt", nil, n.Elt) - - case *ast.StructType: - a.apply(n, "Fields", nil, n.Fields) - - case *ast.FuncType: - if tparams := n.TypeParams; tparams != nil { - a.apply(n, "TypeParams", nil, tparams) - } - a.apply(n, "Params", nil, n.Params) - a.apply(n, "Results", nil, n.Results) - - case *ast.InterfaceType: - a.apply(n, "Methods", nil, n.Methods) - - case *ast.MapType: - a.apply(n, "Key", nil, n.Key) - a.apply(n, "Value", nil, n.Value) - - case *ast.ChanType: - a.apply(n, "Value", nil, n.Value) - - // Statements - case *ast.BadStmt: - // nothing to do - - case *ast.DeclStmt: - a.apply(n, "Decl", nil, n.Decl) - - case *ast.EmptyStmt: - // nothing to do - - case *ast.LabeledStmt: - a.apply(n, "Label", nil, n.Label) - a.apply(n, "Stmt", nil, n.Stmt) - - case *ast.ExprStmt: - a.apply(n, "X", nil, n.X) - - case *ast.SendStmt: - a.apply(n, "Chan", nil, n.Chan) - a.apply(n, "Value", nil, n.Value) - - case *ast.IncDecStmt: - a.apply(n, "X", nil, n.X) - - case *ast.AssignStmt: - a.applyList(n, "Lhs") - a.applyList(n, "Rhs") - - case *ast.GoStmt: - a.apply(n, "Call", nil, n.Call) - - case *ast.DeferStmt: - a.apply(n, "Call", nil, n.Call) - - case *ast.ReturnStmt: - a.applyList(n, "Results") - - case *ast.BranchStmt: - a.apply(n, "Label", nil, n.Label) - - case *ast.BlockStmt: - a.applyList(n, "List") - - case *ast.IfStmt: - a.apply(n, "Init", nil, n.Init) - a.apply(n, "Cond", nil, n.Cond) - a.apply(n, "Body", nil, n.Body) - a.apply(n, "Else", nil, n.Else) - - case *ast.CaseClause: - a.applyList(n, "List") - a.applyList(n, "Body") - - case *ast.SwitchStmt: - a.apply(n, "Init", nil, n.Init) - a.apply(n, "Tag", nil, n.Tag) - a.apply(n, "Body", nil, n.Body) - - case *ast.TypeSwitchStmt: - a.apply(n, "Init", nil, n.Init) - a.apply(n, "Assign", nil, n.Assign) - a.apply(n, "Body", nil, n.Body) - - case *ast.CommClause: - a.apply(n, "Comm", nil, n.Comm) - a.applyList(n, "Body") - - case *ast.SelectStmt: - a.apply(n, "Body", nil, n.Body) - - case *ast.ForStmt: - a.apply(n, "Init", nil, n.Init) - a.apply(n, "Cond", nil, n.Cond) - a.apply(n, "Post", nil, n.Post) - a.apply(n, "Body", nil, n.Body) - - case *ast.RangeStmt: - a.apply(n, "Key", nil, n.Key) - a.apply(n, "Value", nil, n.Value) - a.apply(n, "X", nil, n.X) - a.apply(n, "Body", nil, n.Body) - - // Declarations - case *ast.ImportSpec: - a.apply(n, "Doc", nil, n.Doc) - a.apply(n, "Name", nil, n.Name) - a.apply(n, "Path", nil, n.Path) - a.apply(n, "Comment", nil, n.Comment) - - case *ast.ValueSpec: - a.apply(n, "Doc", nil, n.Doc) - a.applyList(n, "Names") - a.apply(n, "Type", nil, n.Type) - a.applyList(n, "Values") - a.apply(n, "Comment", nil, n.Comment) - - case *ast.TypeSpec: - a.apply(n, "Doc", nil, n.Doc) - a.apply(n, "Name", nil, n.Name) - if tparams := n.TypeParams; tparams != nil { - a.apply(n, "TypeParams", nil, tparams) - } - a.apply(n, "Type", nil, n.Type) - a.apply(n, "Comment", nil, n.Comment) - - case *ast.BadDecl: - // nothing to do - - case *ast.GenDecl: - a.apply(n, "Doc", nil, n.Doc) - a.applyList(n, "Specs") - - case *ast.FuncDecl: - a.apply(n, "Doc", nil, n.Doc) - a.apply(n, "Recv", nil, n.Recv) - a.apply(n, "Name", nil, n.Name) - a.apply(n, "Type", nil, n.Type) - a.apply(n, "Body", nil, n.Body) - - // Files and packages - case *ast.File: - a.apply(n, "Doc", nil, n.Doc) - a.apply(n, "Name", nil, n.Name) - a.applyList(n, "Decls") - // Don't walk n.Comments; they have either been walked already if - // they are Doc comments, or they can be easily walked explicitly. - - case *ast.Package: - // collect and sort names for reproducible behavior - var names []string - for name := range n.Files { - names = append(names, name) - } - sort.Strings(names) - for _, name := range names { - a.apply(n, name, nil, n.Files[name]) - } - - default: - panic(fmt.Sprintf("Apply: unexpected node type %T", n)) - } - - if a.post != nil && !a.post(&a.cursor) { - panic(abort) - } - - a.cursor = saved -} - -// An iterator controls iteration over a slice of nodes. -type iterator struct { - index, step int -} - -func (a *application) applyList(parent ast.Node, name string) { - // avoid heap-allocating a new iterator for each applyList call; reuse a.iter instead - saved := a.iter - a.iter.index = 0 - for { - // must reload parent.name each time, since cursor modifications might change it - v := reflect.Indirect(reflect.ValueOf(parent)).FieldByName(name) - if a.iter.index >= v.Len() { - break - } - - // element x may be nil in a bad AST - be cautious - var x ast.Node - if e := v.Index(a.iter.index); e.IsValid() { - x = e.Interface().(ast.Node) - } - - a.iter.step = 1 - a.apply(parent, name, &a.iter, x) - a.iter.index += a.iter.step - } - a.iter = saved -} diff --git a/vendor/golang.org/x/tools/go/ast/astutil/util.go b/vendor/golang.org/x/tools/go/ast/astutil/util.go deleted file mode 100644 index c820b20849..0000000000 --- a/vendor/golang.org/x/tools/go/ast/astutil/util.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2015 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 astutil - -import "go/ast" - -// Unparen returns e with any enclosing parentheses stripped. -// Deprecated: use [ast.Unparen]. -// -//go:fix inline -func Unparen(e ast.Expr) ast.Expr { return ast.Unparen(e) } diff --git a/vendor/golang.org/x/tools/imports/forward.go b/vendor/golang.org/x/tools/imports/forward.go deleted file mode 100644 index 5d120d077c..0000000000 --- a/vendor/golang.org/x/tools/imports/forward.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2019 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 imports implements a Go pretty-printer (like package "go/format") -// that also adds or removes import statements as necessary. -package imports // import "golang.org/x/tools/imports" - -import ( - "log" - "os" - - "golang.org/x/tools/internal/gocommand" - intimp "golang.org/x/tools/internal/imports" -) - -// Options specifies options for processing files. -type Options struct { - Fragment bool // Accept fragment of a source file (no package statement) - AllErrors bool // Report all errors (not just the first 10 on different lines) - - Comments bool // Print comments (true if nil *Options provided) - TabIndent bool // Use tabs for indent (true if nil *Options provided) - TabWidth int // Tab width (8 if nil *Options provided) - - FormatOnly bool // Disable the insertion and deletion of imports -} - -// Debug controls verbose logging. -var Debug = false - -// LocalPrefix is a comma-separated string of import path prefixes, which, if -// set, instructs Process to sort the import paths with the given prefixes -// into another group after 3rd-party packages. -var LocalPrefix string - -// Process formats and adjusts imports for the provided file. -// If opt is nil the defaults are used, and if src is nil the source -// is read from the filesystem. -// -// Note that filename's directory influences which imports can be chosen, -// so it is important that filename be accurate. -// To process data “as if” it were in filename, pass the data as a non-nil src. -func Process(filename string, src []byte, opt *Options) ([]byte, error) { - var err error - if src == nil { - src, err = os.ReadFile(filename) - if err != nil { - return nil, err - } - } - if opt == nil { - opt = &Options{Comments: true, TabIndent: true, TabWidth: 8} - } - intopt := &intimp.Options{ - Env: &intimp.ProcessEnv{ - GocmdRunner: &gocommand.Runner{}, - }, - LocalPrefix: LocalPrefix, - AllErrors: opt.AllErrors, - Comments: opt.Comments, - FormatOnly: opt.FormatOnly, - Fragment: opt.Fragment, - TabIndent: opt.TabIndent, - TabWidth: opt.TabWidth, - } - if Debug { - intopt.Env.Logf = log.Printf - } - return intimp.Process(filename, src, intopt) -} - -// VendorlessPath returns the devendorized version of the import path ipath. -// For example, VendorlessPath("foo/barbendor/a/b") return "a/b". -func VendorlessPath(ipath string) string { - return intimp.VendorlessPath(ipath) -} diff --git a/vendor/golang.org/x/tools/internal/gopathwalk/walk.go b/vendor/golang.org/x/tools/internal/gopathwalk/walk.go deleted file mode 100644 index 5252144d04..0000000000 --- a/vendor/golang.org/x/tools/internal/gopathwalk/walk.go +++ /dev/null @@ -1,336 +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 gopathwalk is like filepath.Walk but specialized for finding Go -// packages, particularly in $GOPATH and $GOROOT. -package gopathwalk - -import ( - "bufio" - "bytes" - "io" - "io/fs" - "os" - "path/filepath" - "runtime" - "slices" - "strings" - "sync" - "time" -) - -// Options controls the behavior of a Walk call. -type Options struct { - // If Logf is non-nil, debug logging is enabled through this function. - Logf func(format string, args ...any) - - // Search module caches. Also disables legacy goimports ignore rules. - ModulesEnabled bool - - // Maximum number of concurrent calls to user-provided callbacks, - // or 0 for GOMAXPROCS. - Concurrency int -} - -// RootType indicates the type of a Root. -type RootType int - -const ( - RootUnknown RootType = iota - RootGOROOT - RootGOPATH - RootCurrentModule - RootModuleCache - RootOther -) - -// A Root is a starting point for a Walk. -type Root struct { - Path string - Type RootType -} - -// Walk concurrently walks Go source directories ($GOROOT, $GOPATH, etc) to find packages. -// -// For each package found, add will be called with the absolute -// paths of the containing source directory and the package directory. -// -// Unlike filepath.WalkDir, Walk follows symbolic links -// (while guarding against cycles). -func Walk(roots []Root, add func(root Root, dir string), opts Options) { - WalkSkip(roots, add, func(Root, string) bool { return false }, opts) -} - -// WalkSkip concurrently walks Go source directories ($GOROOT, $GOPATH, etc) to -// find packages. -// -// For each package found, add will be called with the absolute -// paths of the containing source directory and the package directory. -// For each directory that will be scanned, skip will be called -// with the absolute paths of the containing source directory and the directory. -// If skip returns false on a directory it will be processed. -// -// Unlike filepath.WalkDir, WalkSkip follows symbolic links -// (while guarding against cycles). -func WalkSkip(roots []Root, add func(root Root, dir string), skip func(root Root, dir string) bool, opts Options) { - for _, root := range roots { - walkDir(root, add, skip, opts) - } -} - -// walkDir creates a walker and starts fastwalk with this walker. -func walkDir(root Root, add func(Root, string), skip func(root Root, dir string) bool, opts Options) { - if opts.Logf == nil { - opts.Logf = func(format string, args ...any) {} - } - if _, err := os.Stat(root.Path); os.IsNotExist(err) { - opts.Logf("skipping nonexistent directory: %v", root.Path) - return - } - start := time.Now() - opts.Logf("scanning %s", root.Path) - - concurrency := opts.Concurrency - if concurrency == 0 { - // The walk be either CPU-bound or I/O-bound, depending on what the - // caller-supplied add function does and the details of the user's platform - // and machine. Rather than trying to fine-tune the concurrency level for a - // specific environment, we default to GOMAXPROCS: it is likely to be a good - // choice for a CPU-bound add function, and if it is instead I/O-bound, then - // dealing with I/O saturation is arguably the job of the kernel and/or - // runtime. (Oversaturating I/O seems unlikely to harm performance as badly - // as failing to saturate would.) - concurrency = runtime.GOMAXPROCS(0) - } - w := &walker{ - root: root, - add: add, - skip: skip, - opts: opts, - sem: make(chan struct{}, concurrency), - } - w.init() - - w.sem <- struct{}{} - path := root.Path - if path == "" { - path = "." - } - if fi, err := os.Lstat(path); err == nil { - w.walk(path, nil, fs.FileInfoToDirEntry(fi)) - } else { - w.opts.Logf("scanning directory %v: %v", root.Path, err) - } - <-w.sem - w.walking.Wait() - - opts.Logf("scanned %s in %v", root.Path, time.Since(start)) -} - -// walker is the callback for fastwalk.Walk. -type walker struct { - root Root // The source directory to scan. - add func(Root, string) // The callback that will be invoked for every possible Go package dir. - skip func(Root, string) bool // The callback that will be invoked for every dir. dir is skipped if it returns true. - opts Options // Options passed to Walk by the user. - - walking sync.WaitGroup - sem chan struct{} // Channel of semaphore tokens; send to acquire, receive to release. - ignoredDirs []string - - added sync.Map // map[string]bool -} - -// A symlinkList is a linked list of os.FileInfos for parent directories -// reached via symlinks. -type symlinkList struct { - info os.FileInfo - prev *symlinkList -} - -// init initializes the walker based on its Options -func (w *walker) init() { - var ignoredPaths []string - if w.root.Type == RootModuleCache { - ignoredPaths = []string{"cache"} - } - if !w.opts.ModulesEnabled && w.root.Type == RootGOPATH { - ignoredPaths = w.getIgnoredDirs(w.root.Path) - ignoredPaths = append(ignoredPaths, "v", "mod") - } - - for _, p := range ignoredPaths { - full := filepath.Join(w.root.Path, p) - w.ignoredDirs = append(w.ignoredDirs, full) - w.opts.Logf("Directory added to ignore list: %s", full) - } -} - -// getIgnoredDirs reads an optional config file at /.goimportsignore -// of relative directories to ignore when scanning for go files. -// The provided path is one of the $GOPATH entries with "src" appended. -func (w *walker) getIgnoredDirs(path string) []string { - file := filepath.Join(path, ".goimportsignore") - slurp, err := os.ReadFile(file) - if err != nil { - w.opts.Logf("%v", err) - } else { - w.opts.Logf("Read %s", file) - } - if err != nil { - return nil - } - - var ignoredDirs []string - bs := bufio.NewScanner(bytes.NewReader(slurp)) - for bs.Scan() { - line := strings.TrimSpace(bs.Text()) - if line == "" || strings.HasPrefix(line, "#") { - continue - } - ignoredDirs = append(ignoredDirs, line) - } - return ignoredDirs -} - -// shouldSkipDir reports whether the file should be skipped or not. -func (w *walker) shouldSkipDir(dir string) bool { - if slices.Contains(w.ignoredDirs, dir) { - return true - } - if w.skip != nil { - // Check with the user specified callback. - return w.skip(w.root, dir) - } - return false -} - -// walk walks through the given path. -// -// Errors are logged if w.opts.Logf is non-nil, but otherwise ignored. -func (w *walker) walk(path string, pathSymlinks *symlinkList, d fs.DirEntry) { - if d.Type()&os.ModeSymlink != 0 { - // Walk the symlink's target rather than the symlink itself. - // - // (Note that os.Stat, unlike the lower-lever os.Readlink, - // follows arbitrarily many layers of symlinks, so it will eventually - // reach either a non-symlink or a nonexistent target.) - // - // TODO(bcmills): 'go list all' itself ignores symlinks within GOROOT/src - // and GOPATH/src. Do we really need to traverse them here? If so, why? - - fi, err := os.Stat(path) - if err != nil { - w.opts.Logf("%v", err) - return - } - - // Avoid walking symlink cycles: if we have already followed a symlink to - // this directory as a parent of itself, don't follow it again. - // - // This doesn't catch the first time through a cycle, but it also minimizes - // the number of extra stat calls we make if we *don't* encounter a cycle. - // Since we don't actually expect to encounter symlink cycles in practice, - // this seems like the right tradeoff. - for parent := pathSymlinks; parent != nil; parent = parent.prev { - if os.SameFile(fi, parent.info) { - return - } - } - - pathSymlinks = &symlinkList{ - info: fi, - prev: pathSymlinks, - } - d = fs.FileInfoToDirEntry(fi) - } - - if d.Type().IsRegular() { - if !strings.HasSuffix(path, ".go") { - return - } - - dir := filepath.Dir(path) - if dir == w.root.Path && (w.root.Type == RootGOROOT || w.root.Type == RootGOPATH) { - // Doesn't make sense to have regular files - // directly in your $GOPATH/src or $GOROOT/src. - // - // TODO(bcmills): there are many levels of directory within - // RootModuleCache where this also wouldn't make sense, - // Can we generalize this to any directory without a corresponding - // import path? - return - } - - if _, dup := w.added.LoadOrStore(dir, true); !dup { - w.add(w.root, dir) - } - } - - if !d.IsDir() { - return - } - - base := filepath.Base(path) - if base == "" || base[0] == '.' || base[0] == '_' || - base == "testdata" || - (w.root.Type == RootGOROOT && w.opts.ModulesEnabled && base == "vendor") || - (!w.opts.ModulesEnabled && base == "node_modules") || - w.shouldSkipDir(path) { - return - } - - // Read the directory and walk its entries. - - f, err := os.Open(path) - if err != nil { - w.opts.Logf("%v", err) - return - } - defer f.Close() - - for { - // We impose an arbitrary limit on the number of ReadDir results per - // directory to limit the amount of memory consumed for stale or upcoming - // directory entries. The limit trades off CPU (number of syscalls to read - // the whole directory) against RAM (reachable directory entries other than - // the one currently being processed). - // - // Since we process the directories recursively, we will end up maintaining - // a slice of entries for each level of the directory tree. - // (Compare https://go.dev/issue/36197.) - ents, err := f.ReadDir(1024) - if err != nil { - if err != io.EOF { - w.opts.Logf("%v", err) - } - break - } - - for _, d := range ents { - nextPath := filepath.Join(path, d.Name()) - if d.IsDir() { - select { - case w.sem <- struct{}{}: - // Got a new semaphore token, so we can traverse the directory concurrently. - d := d - w.walking.Add(1) - go func() { - defer func() { - <-w.sem - w.walking.Done() - }() - w.walk(nextPath, pathSymlinks, d) - }() - continue - - default: - // No tokens available, so traverse serially. - } - } - - w.walk(nextPath, pathSymlinks, d) - } - } -} diff --git a/vendor/golang.org/x/tools/internal/imports/fix.go b/vendor/golang.org/x/tools/internal/imports/fix.go deleted file mode 100644 index 1b4dc0cb5d..0000000000 --- a/vendor/golang.org/x/tools/internal/imports/fix.go +++ /dev/null @@ -1,1896 +0,0 @@ -// Copyright 2013 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 imports - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "go/ast" - "go/build" - "go/parser" - "go/token" - "go/types" - "io/fs" - "io/ioutil" - "maps" - "os" - "path" - "path/filepath" - "reflect" - "sort" - "strconv" - "strings" - "sync" - "unicode" - "unicode/utf8" - - "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/gocommand" - "golang.org/x/tools/internal/gopathwalk" - "golang.org/x/tools/internal/stdlib" -) - -// importToGroup is a list of functions which map from an import path to -// a group number. -var importToGroup = []func(localPrefix, importPath string) (num int, ok bool){ - func(localPrefix, importPath string) (num int, ok bool) { - if localPrefix == "" { - return - } - for p := range strings.SplitSeq(localPrefix, ",") { - if strings.HasPrefix(importPath, p) || strings.TrimSuffix(p, "/") == importPath { - return 3, true - } - } - return - }, - func(_, importPath string) (num int, ok bool) { - if strings.HasPrefix(importPath, "appengine") { - return 2, true - } - return - }, - func(_, importPath string) (num int, ok bool) { - firstComponent := strings.Split(importPath, "/")[0] - if strings.Contains(firstComponent, ".") { - return 1, true - } - return - }, -} - -func importGroup(localPrefix, importPath string) int { - for _, fn := range importToGroup { - if n, ok := fn(localPrefix, importPath); ok { - return n - } - } - return 0 -} - -type ImportFixType int - -const ( - AddImport ImportFixType = iota - DeleteImport - SetImportName -) - -type ImportFix struct { - // StmtInfo represents the import statement this fix will add, remove, or change. - StmtInfo ImportInfo - // IdentName is the identifier that this fix will add or remove. - IdentName string - // FixType is the type of fix this is (AddImport, DeleteImport, SetImportName). - FixType ImportFixType - Relevance float64 // see pkg -} - -// parseOtherFiles parses all the Go files in srcDir except filename, including -// test files if filename looks like a test. -// -// It returns an error only if ctx is cancelled. Files with parse errors are -// ignored. -func parseOtherFiles(ctx context.Context, fset *token.FileSet, srcDir, filename string) ([]*ast.File, error) { - // This could use go/packages but it doesn't buy much, and it fails - // with https://golang.org/issue/26296 in LoadFiles mode in some cases. - considerTests := strings.HasSuffix(filename, "_test.go") - - fileBase := filepath.Base(filename) - packageFileInfos, err := os.ReadDir(srcDir) - if err != nil { - return nil, ctx.Err() - } - - var files []*ast.File - for _, fi := range packageFileInfos { - if ctx.Err() != nil { - return nil, ctx.Err() - } - if fi.Name() == fileBase || !strings.HasSuffix(fi.Name(), ".go") { - continue - } - if !considerTests && strings.HasSuffix(fi.Name(), "_test.go") { - continue - } - - f, err := parser.ParseFile(fset, filepath.Join(srcDir, fi.Name()), nil, parser.SkipObjectResolution) - if err != nil { - continue - } - - files = append(files, f) - } - - return files, ctx.Err() -} - -// addGlobals puts the names of package vars into the provided map. -func addGlobals(f *ast.File, globals map[string]bool) { - for _, decl := range f.Decls { - genDecl, ok := decl.(*ast.GenDecl) - if !ok { - continue - } - - for _, spec := range genDecl.Specs { - valueSpec, ok := spec.(*ast.ValueSpec) - if !ok { - continue - } - globals[valueSpec.Names[0].Name] = true - } - } -} - -// collectReferences builds a map of selector expressions, from -// left hand side (X) to a set of right hand sides (Sel). -func collectReferences(f *ast.File) References { - refs := References{} - - var visitor visitFn - visitor = func(node ast.Node) ast.Visitor { - if node == nil { - return visitor - } - switch v := node.(type) { - case *ast.SelectorExpr: - xident, ok := v.X.(*ast.Ident) - if !ok { - break - } - if xident.Obj != nil { - // If the parser can resolve it, it's not a package ref. - break - } - if !ast.IsExported(v.Sel.Name) { - // Whatever this is, it's not exported from a package. - break - } - pkgName := xident.Name - r := refs[pkgName] - if r == nil { - r = make(map[string]bool) - refs[pkgName] = r - } - r[v.Sel.Name] = true - } - return visitor - } - ast.Walk(visitor, f) - return refs -} - -// collectImports returns all the imports in f. -// Unnamed imports (., _) and "C" are ignored. -func collectImports(f *ast.File) []*ImportInfo { - var imports []*ImportInfo - for _, imp := range f.Imports { - var name string - if imp.Name != nil { - name = imp.Name.Name - } - if imp.Path.Value == `"C"` || name == "_" || name == "." { - continue - } - path := strings.Trim(imp.Path.Value, `"`) - imports = append(imports, &ImportInfo{ - Name: name, - ImportPath: path, - }) - } - return imports -} - -// findMissingImport searches pass's candidates for an import that provides -// pkg, containing all of syms. -func (p *pass) findMissingImport(pkg string, syms map[string]bool) *ImportInfo { - for _, candidate := range p.candidates { - pkgInfo, ok := p.knownPackages[candidate.ImportPath] - if !ok { - continue - } - if p.importIdentifier(candidate) != pkg { - continue - } - - allFound := true - for right := range syms { - if !pkgInfo.Exports[right] { - allFound = false - break - } - } - - if allFound { - return candidate - } - } - return nil -} - -// A pass contains all the inputs and state necessary to fix a file's imports. -// It can be modified in some ways during use; see comments below. -type pass struct { - // Inputs. These must be set before a call to load, and not modified after. - fset *token.FileSet // fset used to parse f and its siblings. - f *ast.File // the file being fixed. - srcDir string // the directory containing f. - logf func(string, ...any) - source Source // the environment to use for go commands, etc. - loadRealPackageNames bool // if true, load package names from disk rather than guessing them. - otherFiles []*ast.File // sibling files. - goroot string - - // Intermediate state, generated by load. - existingImports map[string][]*ImportInfo - allRefs References - missingRefs References - - // Inputs to fix. These can be augmented between successive fix calls. - lastTry bool // indicates that this is the last call and fix should clean up as best it can. - candidates []*ImportInfo // candidate imports in priority order. - knownPackages map[string]*PackageInfo // information about all known packages. -} - -// loadPackageNames saves the package names for everything referenced by imports. -func (p *pass) loadPackageNames(ctx context.Context, imports []*ImportInfo) error { - if p.logf != nil { - p.logf("loading package names for %v packages", len(imports)) - defer func() { - p.logf("done loading package names for %v packages", len(imports)) - }() - } - var unknown []string - for _, imp := range imports { - if _, ok := p.knownPackages[imp.ImportPath]; ok { - continue - } - unknown = append(unknown, imp.ImportPath) - } - - names, err := p.source.LoadPackageNames(ctx, p.srcDir, unknown) - if err != nil { - return err - } - - // TODO(rfindley): revisit this. Why do we need to store known packages with - // no exports? The inconsistent data is confusing. - for path, name := range names { - p.knownPackages[path] = &PackageInfo{ - Name: name, - Exports: map[string]bool{}, - } - } - return nil -} - -// WithoutVersion removes a trailing major version, if there is one. -func WithoutVersion(nm string) string { - if v := path.Base(nm); len(v) > 0 && v[0] == 'v' { - if _, err := strconv.Atoi(v[1:]); err == nil { - // this is, for instance, called with rand/v2 and returns rand - if len(v) < len(nm) { - xnm := nm[:len(nm)-len(v)-1] - return path.Base(xnm) - } - } - } - return nm -} - -// importIdentifier returns the identifier that imp will introduce. It will -// guess if the package name has not been loaded, e.g. because the source -// is not available. -func (p *pass) importIdentifier(imp *ImportInfo) string { - if imp.Name != "" { - return imp.Name - } - known := p.knownPackages[imp.ImportPath] - if known != nil && known.Name != "" { - return WithoutVersion(known.Name) - } - return ImportPathToAssumedName(imp.ImportPath) -} - -// load reads in everything necessary to run a pass, and reports whether the -// file already has all the imports it needs. It fills in p.missingRefs with the -// file's missing symbols, if any, or removes unused imports if not. -func (p *pass) load(ctx context.Context) ([]*ImportFix, bool) { - p.knownPackages = map[string]*PackageInfo{} - p.missingRefs = References{} - p.existingImports = map[string][]*ImportInfo{} - - // Load basic information about the file in question. - p.allRefs = collectReferences(p.f) - - // Load stuff from other files in the same package: - // global variables so we know they don't need resolving, and imports - // that we might want to mimic. - globals := map[string]bool{} - for _, otherFile := range p.otherFiles { - // Don't load globals from files that are in the same directory - // but a different package. Using them to suggest imports is OK. - if p.f.Name.Name == otherFile.Name.Name { - addGlobals(otherFile, globals) - } - p.candidates = append(p.candidates, collectImports(otherFile)...) - } - - // Resolve all the import paths we've seen to package names, and store - // f's imports by the identifier they introduce. - imports := collectImports(p.f) - if p.loadRealPackageNames { - err := p.loadPackageNames(ctx, append(imports, p.candidates...)) - if err != nil { - if p.logf != nil { - p.logf("loading package names: %v", err) - } - return nil, false - } - } - for _, imp := range imports { - p.existingImports[p.importIdentifier(imp)] = append(p.existingImports[p.importIdentifier(imp)], imp) - } - - // Find missing references. - for left, rights := range p.allRefs { - if globals[left] { - continue - } - _, ok := p.existingImports[left] - if !ok { - p.missingRefs[left] = rights - continue - } - } - if len(p.missingRefs) != 0 { - return nil, false - } - - return p.fix() -} - -// fix attempts to satisfy missing imports using p.candidates. If it finds -// everything, or if p.lastTry is true, it updates fixes to add the imports it found, -// delete anything unused, and update import names, and returns true. -func (p *pass) fix() ([]*ImportFix, bool) { - // Find missing imports. - var selected []*ImportInfo - for left, rights := range p.missingRefs { - if imp := p.findMissingImport(left, rights); imp != nil { - selected = append(selected, imp) - } - } - - if !p.lastTry && len(selected) != len(p.missingRefs) { - return nil, false - } - - // Found everything, or giving up. Add the new imports and remove any unused. - var fixes []*ImportFix - for _, identifierImports := range p.existingImports { - for _, imp := range identifierImports { - // We deliberately ignore globals here, because we can't be sure - // they're in the same package. People do things like put multiple - // main packages in the same directory, and we don't want to - // remove imports if they happen to have the same name as a var in - // a different package. - if _, ok := p.allRefs[p.importIdentifier(imp)]; !ok { - fixes = append(fixes, &ImportFix{ - StmtInfo: *imp, - IdentName: p.importIdentifier(imp), - FixType: DeleteImport, - }) - continue - } - - // An existing import may need to update its import name to be correct. - if name := p.importSpecName(imp); name != imp.Name { - fixes = append(fixes, &ImportFix{ - StmtInfo: ImportInfo{ - Name: name, - ImportPath: imp.ImportPath, - }, - IdentName: p.importIdentifier(imp), - FixType: SetImportName, - }) - } - } - } - // Collecting fixes involved map iteration, so sort for stability. See - // golang/go#59976. - sortFixes(fixes) - - // collect selected fixes in a separate slice, so that it can be sorted - // separately. Note that these fixes must occur after fixes to existing - // imports. TODO(rfindley): figure out why. - var selectedFixes []*ImportFix - for _, imp := range selected { - selectedFixes = append(selectedFixes, &ImportFix{ - StmtInfo: ImportInfo{ - Name: p.importSpecName(imp), - ImportPath: imp.ImportPath, - }, - IdentName: p.importIdentifier(imp), - FixType: AddImport, - }) - } - sortFixes(selectedFixes) - - return append(fixes, selectedFixes...), true -} - -func sortFixes(fixes []*ImportFix) { - sort.Slice(fixes, func(i, j int) bool { - fi, fj := fixes[i], fixes[j] - if fi.StmtInfo.ImportPath != fj.StmtInfo.ImportPath { - return fi.StmtInfo.ImportPath < fj.StmtInfo.ImportPath - } - if fi.StmtInfo.Name != fj.StmtInfo.Name { - return fi.StmtInfo.Name < fj.StmtInfo.Name - } - if fi.IdentName != fj.IdentName { - return fi.IdentName < fj.IdentName - } - return fi.FixType < fj.FixType - }) -} - -// importSpecName gets the import name of imp in the import spec. -// -// When the import identifier matches the assumed import name, the import name does -// not appear in the import spec. -func (p *pass) importSpecName(imp *ImportInfo) string { - // If we did not load the real package names, or the name is already set, - // we just return the existing name. - if !p.loadRealPackageNames || imp.Name != "" { - return imp.Name - } - - ident := p.importIdentifier(imp) - if ident == ImportPathToAssumedName(imp.ImportPath) { - return "" // ident not needed since the assumed and real names are the same. - } - return ident -} - -// apply will perform the fixes on f in order. -func apply(fset *token.FileSet, f *ast.File, fixes []*ImportFix) { - for _, fix := range fixes { - switch fix.FixType { - case DeleteImport: - astutil.DeleteNamedImport(fset, f, fix.StmtInfo.Name, fix.StmtInfo.ImportPath) - case AddImport: - astutil.AddNamedImport(fset, f, fix.StmtInfo.Name, fix.StmtInfo.ImportPath) - case SetImportName: - // Find the matching import path and change the name. - for _, spec := range f.Imports { - path := strings.Trim(spec.Path.Value, `"`) - if path == fix.StmtInfo.ImportPath { - spec.Name = &ast.Ident{ - Name: fix.StmtInfo.Name, - NamePos: spec.Pos(), - } - } - } - } - } -} - -// assumeSiblingImportsValid assumes that siblings' use of packages is valid, -// adding the exports they use. -func (p *pass) assumeSiblingImportsValid() { - for _, f := range p.otherFiles { - refs := collectReferences(f) - imports := collectImports(f) - importsByName := map[string]*ImportInfo{} - for _, imp := range imports { - importsByName[p.importIdentifier(imp)] = imp - } - for left, rights := range refs { - if imp, ok := importsByName[left]; ok { - if m, ok := stdlib.PackageSymbols[imp.ImportPath]; ok { - // We have the stdlib in memory; no need to guess. - rights = symbolNameSet(m) - } - // TODO(rfindley): we should set package name here, for consistency. - p.addCandidate(imp, &PackageInfo{ - // no name; we already know it. - Exports: rights, - }) - } - } - } -} - -// addCandidate adds a candidate import to p, and merges in the information -// in pkg. -func (p *pass) addCandidate(imp *ImportInfo, pkg *PackageInfo) { - p.candidates = append(p.candidates, imp) - if existing, ok := p.knownPackages[imp.ImportPath]; ok { - if existing.Name == "" { - existing.Name = pkg.Name - } - for export := range pkg.Exports { - existing.Exports[export] = true - } - } else { - p.knownPackages[imp.ImportPath] = pkg - } -} - -// fixImports adds and removes imports from f so that all its references are -// satisfied and there are no unused imports. -// -// This is declared as a variable rather than a function so goimports can -// easily be extended by adding a file with an init function. -// -// DO NOT REMOVE: used internally at Google. -var fixImports = fixImportsDefault - -func fixImportsDefault(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv) error { - fixes, err := getFixes(context.Background(), fset, f, filename, env) - if err != nil { - return err - } - apply(fset, f, fixes) - return nil -} - -// getFixes gets the import fixes that need to be made to f in order to fix the imports. -// It does not modify the ast. -func getFixes(ctx context.Context, fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv) ([]*ImportFix, error) { - source, err := NewProcessEnvSource(env, filename, f.Name.Name) - if err != nil { - return nil, err - } - goEnv, err := env.goEnv() - if err != nil { - return nil, err - } - return getFixesWithSource(ctx, fset, f, filename, goEnv["GOROOT"], env.logf, source) -} - -func getFixesWithSource(ctx context.Context, fset *token.FileSet, f *ast.File, filename string, goroot string, logf func(string, ...any), source Source) ([]*ImportFix, error) { - // This logic is defensively duplicated from getFixes. - abs, err := filepath.Abs(filename) - if err != nil { - return nil, err - } - srcDir := filepath.Dir(abs) - - if logf != nil { - logf("fixImports(filename=%q), srcDir=%q ...", filename, srcDir) - } - - // First pass: looking only at f, and using the naive algorithm to - // derive package names from import paths, see if the file is already - // complete. We can't add any imports yet, because we don't know - // if missing references are actually package vars. - p := &pass{ - fset: fset, - f: f, - srcDir: srcDir, - logf: logf, - goroot: goroot, - source: source, - } - if fixes, done := p.load(ctx); done { - return fixes, nil - } - - otherFiles, err := parseOtherFiles(ctx, fset, srcDir, filename) - if err != nil { - return nil, err - } - - // Second pass: add information from other files in the same package, - // like their package vars and imports. - p.otherFiles = otherFiles - if fixes, done := p.load(ctx); done { - return fixes, nil - } - - // Now we can try adding imports from the stdlib. - p.assumeSiblingImportsValid() - addStdlibCandidates(p, p.missingRefs) - if fixes, done := p.fix(); done { - return fixes, nil - } - - // Third pass: get real package names where we had previously used - // the naive algorithm. - p = &pass{ - fset: fset, - f: f, - srcDir: srcDir, - logf: logf, - goroot: goroot, - source: p.source, // safe to reuse, as it's just a wrapper around env - } - p.loadRealPackageNames = true - p.otherFiles = otherFiles - if fixes, done := p.load(ctx); done { - return fixes, nil - } - - if err := addStdlibCandidates(p, p.missingRefs); err != nil { - return nil, err - } - p.assumeSiblingImportsValid() - if fixes, done := p.fix(); done { - return fixes, nil - } - - // Go look for candidates in $GOPATH, etc. We don't necessarily load - // the real exports of sibling imports, so keep assuming their contents. - if err := addExternalCandidates(ctx, p, p.missingRefs, filename); err != nil { - return nil, err - } - - p.lastTry = true - fixes, _ := p.fix() - return fixes, nil -} - -// MaxRelevance is the highest relevance, used for the standard library. -// Chosen arbitrarily to match pre-existing gopls code. -const MaxRelevance = 7.0 - -// getCandidatePkgs works with the passed callback to find all acceptable packages. -// It deduplicates by import path, and uses a cached stdlib rather than reading -// from disk. -func getCandidatePkgs(ctx context.Context, wrappedCallback *scanCallback, filename, filePkg string, env *ProcessEnv) error { - notSelf := func(p *pkg) bool { - return p.packageName != filePkg || p.dir != filepath.Dir(filename) - } - goenv, err := env.goEnv() - if err != nil { - return err - } - - var mu sync.Mutex // to guard asynchronous access to dupCheck - dupCheck := map[string]struct{}{} - - // Start off with the standard library. - for importPath, symbols := range stdlib.PackageSymbols { - p := &pkg{ - dir: filepath.Join(goenv["GOROOT"], "src", importPath), - importPathShort: importPath, - packageName: path.Base(importPath), - relevance: MaxRelevance, - } - dupCheck[importPath] = struct{}{} - if notSelf(p) && wrappedCallback.dirFound(p) && wrappedCallback.packageNameLoaded(p) { - var exports []stdlib.Symbol - for _, sym := range symbols { - switch sym.Kind { - case stdlib.Func, stdlib.Type, stdlib.Var, stdlib.Const: - exports = append(exports, sym) - } - } - wrappedCallback.exportsLoaded(p, exports) - } - } - - scanFilter := &scanCallback{ - rootFound: func(root gopathwalk.Root) bool { - // Exclude goroot results -- getting them is relatively expensive, not cached, - // and generally redundant with the in-memory version. - return root.Type != gopathwalk.RootGOROOT && wrappedCallback.rootFound(root) - }, - dirFound: wrappedCallback.dirFound, - packageNameLoaded: func(pkg *pkg) bool { - mu.Lock() - defer mu.Unlock() - if _, ok := dupCheck[pkg.importPathShort]; ok { - return false - } - dupCheck[pkg.importPathShort] = struct{}{} - return notSelf(pkg) && wrappedCallback.packageNameLoaded(pkg) - }, - exportsLoaded: func(pkg *pkg, exports []stdlib.Symbol) { - // If we're an x_test, load the package under test's test variant. - if strings.HasSuffix(filePkg, "_test") && pkg.dir == filepath.Dir(filename) { - var err error - _, exports, err = loadExportsFromFiles(ctx, env, pkg.dir, true) - if err != nil { - return - } - } - wrappedCallback.exportsLoaded(pkg, exports) - }, - } - resolver, err := env.GetResolver() - if err != nil { - return err - } - return resolver.scan(ctx, scanFilter) -} - -func ScoreImportPaths(ctx context.Context, env *ProcessEnv, paths []string) (map[string]float64, error) { - result := make(map[string]float64) - resolver, err := env.GetResolver() - if err != nil { - return nil, err - } - for _, path := range paths { - result[path] = resolver.scoreImportPath(ctx, path) - } - return result, nil -} - -func PrimeCache(ctx context.Context, resolver Resolver) error { - // Fully scan the disk for directories, but don't actually read any Go files. - callback := &scanCallback{ - rootFound: func(root gopathwalk.Root) bool { - // See getCandidatePkgs: walking GOROOT is apparently expensive and - // unnecessary. - return root.Type != gopathwalk.RootGOROOT - }, - dirFound: func(pkg *pkg) bool { - return false - }, - // packageNameLoaded and exportsLoaded must never be called. - } - - return resolver.scan(ctx, callback) -} - -func candidateImportName(pkg *pkg) string { - if ImportPathToAssumedName(pkg.importPathShort) != pkg.packageName { - return pkg.packageName - } - return "" -} - -// GetAllCandidates calls wrapped for each package whose name starts with -// searchPrefix, and can be imported from filename with the package name filePkg. -// -// Beware that the wrapped function may be called multiple times concurrently. -// TODO(adonovan): encapsulate the concurrency. -func GetAllCandidates(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error { - callback := &scanCallback{ - rootFound: func(gopathwalk.Root) bool { - return true - }, - dirFound: func(pkg *pkg) bool { - if !CanUse(filename, pkg.dir) { - return false - } - // Try the assumed package name first, then a simpler path match - // in case of packages named vN, which are not uncommon. - return strings.HasPrefix(ImportPathToAssumedName(pkg.importPathShort), searchPrefix) || - strings.HasPrefix(path.Base(pkg.importPathShort), searchPrefix) - }, - packageNameLoaded: func(pkg *pkg) bool { - if !strings.HasPrefix(pkg.packageName, searchPrefix) { - return false - } - wrapped(ImportFix{ - StmtInfo: ImportInfo{ - ImportPath: pkg.importPathShort, - Name: candidateImportName(pkg), - }, - IdentName: pkg.packageName, - FixType: AddImport, - Relevance: pkg.relevance, - }) - return false - }, - } - return getCandidatePkgs(ctx, callback, filename, filePkg, env) -} - -// GetImportPaths calls wrapped for each package whose import path starts with -// searchPrefix, and can be imported from filename with the package name filePkg. -func GetImportPaths(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error { - callback := &scanCallback{ - rootFound: func(gopathwalk.Root) bool { - return true - }, - dirFound: func(pkg *pkg) bool { - if !CanUse(filename, pkg.dir) { - return false - } - return strings.HasPrefix(pkg.importPathShort, searchPrefix) - }, - packageNameLoaded: func(pkg *pkg) bool { - wrapped(ImportFix{ - StmtInfo: ImportInfo{ - ImportPath: pkg.importPathShort, - Name: candidateImportName(pkg), - }, - IdentName: pkg.packageName, - FixType: AddImport, - Relevance: pkg.relevance, - }) - return false - }, - } - return getCandidatePkgs(ctx, callback, filename, filePkg, env) -} - -// A PackageExport is a package and its exports. -type PackageExport struct { - Fix *ImportFix - Exports []stdlib.Symbol -} - -// GetPackageExports returns all known packages with name pkg and their exports. -func GetPackageExports(ctx context.Context, wrapped func(PackageExport), searchPkg, filename, filePkg string, env *ProcessEnv) error { - callback := &scanCallback{ - rootFound: func(gopathwalk.Root) bool { - return true - }, - dirFound: func(pkg *pkg) bool { - return pkgIsCandidate(filename, References{searchPkg: nil}, pkg) - }, - packageNameLoaded: func(pkg *pkg) bool { - return pkg.packageName == searchPkg - }, - exportsLoaded: func(pkg *pkg, exports []stdlib.Symbol) { - sortSymbols(exports) - wrapped(PackageExport{ - Fix: &ImportFix{ - StmtInfo: ImportInfo{ - ImportPath: pkg.importPathShort, - Name: candidateImportName(pkg), - }, - IdentName: pkg.packageName, - FixType: AddImport, - Relevance: pkg.relevance, - }, - Exports: exports, - }) - }, - } - return getCandidatePkgs(ctx, callback, filename, filePkg, env) -} - -// TODO(rfindley): we should depend on GOOS and GOARCH, to provide accurate -// imports when doing cross-platform development. -var requiredGoEnvVars = []string{ - "GO111MODULE", - "GOFLAGS", - "GOINSECURE", - "GOMOD", - "GOMODCACHE", - "GONOPROXY", - "GONOSUMDB", - "GOPATH", - "GOPROXY", - "GOROOT", - "GOSUMDB", - "GOWORK", -} - -// ProcessEnv contains environment variables and settings that affect the use of -// the go command, the go/build package, etc. -// -// ...a ProcessEnv *also* overwrites its Env along with derived state in the -// form of the resolver. And because it is lazily initialized, an env may just -// be broken and unusable, but there is no way for the caller to detect that: -// all queries will just fail. -// -// TODO(rfindley): refactor this package so that this type (perhaps renamed to -// just Env or Config) is an immutable configuration struct, to be exchanged -// for an initialized object via a constructor that returns an error. Perhaps -// the signature should be `func NewResolver(*Env) (*Resolver, error)`, where -// resolver is a concrete type used for resolving imports. Via this -// refactoring, we can avoid the need to call ProcessEnv.init and -// ProcessEnv.GoEnv everywhere, and implicitly fix all the places where this -// these are misused. Also, we'd delegate the caller the decision of how to -// handle a broken environment. -type ProcessEnv struct { - GocmdRunner *gocommand.Runner - - BuildFlags []string - ModFlag string - - // SkipPathInScan returns true if the path should be skipped from scans of - // the RootCurrentModule root type. The function argument is a clean, - // absolute path. - SkipPathInScan func(string) bool - - // Env overrides the OS environment, and can be used to specify - // GOPROXY, GO111MODULE, etc. PATH cannot be set here, because - // exec.Command will not honor it. - // Specifying all of requiredGoEnvVars avoids a call to `go env`. - Env map[string]string - - WorkingDir string - - // If Logf is non-nil, debug logging is enabled through this function. - Logf func(format string, args ...any) - - // If set, ModCache holds a shared cache of directory info to use across - // multiple ProcessEnvs. - ModCache *DirInfoCache - - initialized bool // see TODO above - - // resolver and resolverErr are lazily evaluated (see GetResolver). - // This is unclean, but see the big TODO in the docstring for ProcessEnv - // above: for now, we can't be sure that the ProcessEnv is fully initialized. - resolver Resolver - resolverErr error -} - -func (e *ProcessEnv) goEnv() (map[string]string, error) { - if err := e.init(); err != nil { - return nil, err - } - return e.Env, nil -} - -func (e *ProcessEnv) matchFile(dir, name string) (bool, error) { - bctx, err := e.buildContext() - if err != nil { - return false, err - } - return bctx.MatchFile(dir, name) -} - -// CopyConfig copies the env's configuration into a new env. -func (e *ProcessEnv) CopyConfig() *ProcessEnv { - copy := &ProcessEnv{ - GocmdRunner: e.GocmdRunner, - initialized: e.initialized, - BuildFlags: e.BuildFlags, - Logf: e.Logf, - WorkingDir: e.WorkingDir, - resolver: nil, - Env: map[string]string{}, - } - maps.Copy(copy.Env, e.Env) - return copy -} - -func (e *ProcessEnv) init() error { - if e.initialized { - return nil - } - - foundAllRequired := true - for _, k := range requiredGoEnvVars { - if _, ok := e.Env[k]; !ok { - foundAllRequired = false - break - } - } - if foundAllRequired { - e.initialized = true - return nil - } - - if e.Env == nil { - e.Env = map[string]string{} - } - - goEnv := map[string]string{} - stdout, err := e.invokeGo(context.TODO(), "env", append([]string{"-json"}, requiredGoEnvVars...)...) - if err != nil { - return err - } - if err := json.Unmarshal(stdout.Bytes(), &goEnv); err != nil { - return err - } - maps.Copy(e.Env, goEnv) - e.initialized = true - return nil -} - -func (e *ProcessEnv) env() []string { - var env []string // the gocommand package will prepend os.Environ. - for k, v := range e.Env { - env = append(env, k+"="+v) - } - return env -} - -func (e *ProcessEnv) GetResolver() (Resolver, error) { - if err := e.init(); err != nil { - return nil, err - } - - if e.resolver == nil && e.resolverErr == nil { - // TODO(rfindley): we should only use a gopathResolver here if the working - // directory is actually *in* GOPATH. (I seem to recall an open gopls issue - // for this behavior, but I can't find it). - // - // For gopls, we can optionally explicitly choose a resolver type, since we - // already know the view type. - if e.Env["GOMOD"] == "" && (e.Env["GOWORK"] == "" || e.Env["GOWORK"] == "off") { - e.resolver = newGopathResolver(e) - e.logf("created gopath resolver") - } else if r, err := newModuleResolver(e, e.ModCache); err != nil { - e.resolverErr = err - e.logf("failed to create module resolver: %v", err) - } else { - e.resolver = Resolver(r) - e.logf("created module resolver") - } - } - - return e.resolver, e.resolverErr -} - -// logf logs if e.Logf is non-nil. -func (e *ProcessEnv) logf(format string, args ...any) { - if e.Logf != nil { - e.Logf(format, args...) - } -} - -// buildContext returns the build.Context to use for matching files. -// -// TODO(rfindley): support dynamic GOOS, GOARCH here, when doing cross-platform -// development. -func (e *ProcessEnv) buildContext() (*build.Context, error) { - ctx := build.Default - goenv, err := e.goEnv() - if err != nil { - return nil, err - } - ctx.GOROOT = goenv["GOROOT"] - ctx.GOPATH = goenv["GOPATH"] - - // As of Go 1.14, build.Context has a Dir field - // (see golang.org/issue/34860). - // Populate it only if present. - rc := reflect.ValueOf(&ctx).Elem() - dir := rc.FieldByName("Dir") - if dir.IsValid() && dir.Kind() == reflect.String { - dir.SetString(e.WorkingDir) - } - - // Since Go 1.11, go/build.Context.Import may invoke 'go list' depending on - // the value in GO111MODULE in the process's environment. We always want to - // run in GOPATH mode when calling Import, so we need to prevent this from - // happening. In Go 1.16, GO111MODULE defaults to "on", so this problem comes - // up more frequently. - // - // HACK: setting any of the Context I/O hooks prevents Import from invoking - // 'go list', regardless of GO111MODULE. This is undocumented, but it's - // unlikely to change before GOPATH support is removed. - ctx.ReadDir = ioutil.ReadDir - - return &ctx, nil -} - -func (e *ProcessEnv) invokeGo(ctx context.Context, verb string, args ...string) (*bytes.Buffer, error) { - inv := gocommand.Invocation{ - Verb: verb, - Args: args, - BuildFlags: e.BuildFlags, - Env: e.env(), - Logf: e.Logf, - WorkingDir: e.WorkingDir, - } - return e.GocmdRunner.Run(ctx, inv) -} - -func addStdlibCandidates(pass *pass, refs References) error { - localbase := func(nm string) string { - ans := path.Base(nm) - if ans[0] == 'v' { - // this is called, for instance, with math/rand/v2 and returns rand/v2 - if _, err := strconv.Atoi(ans[1:]); err == nil { - ix := strings.LastIndex(nm, ans) - more := path.Base(nm[:ix]) - ans = path.Join(more, ans) - } - } - return ans - } - add := func(pkg string) { - // Prevent self-imports. - if path.Base(pkg) == pass.f.Name.Name && filepath.Join(pass.goroot, "src", pkg) == pass.srcDir { - return - } - exports := symbolNameSet(stdlib.PackageSymbols[pkg]) - pass.addCandidate( - &ImportInfo{ImportPath: pkg}, - &PackageInfo{Name: localbase(pkg), Exports: exports}) - } - for left := range refs { - if left == "rand" { - // Make sure we try crypto/rand before any version of math/rand as both have Int() - // and our policy is to recommend crypto - add("crypto/rand") - // if the user's no later than go1.21, this should be "math/rand" - // but we have no way of figuring out what the user is using - // TODO: investigate using the toolchain version to disambiguate in the stdlib - add("math/rand/v2") - // math/rand has an overlapping API - // TestIssue66407 fails without this - add("math/rand") - continue - } - for importPath := range stdlib.PackageSymbols { - if path.Base(importPath) == left { - add(importPath) - } - } - } - return nil -} - -// A Resolver does the build-system-specific parts of goimports. -type Resolver interface { - // loadPackageNames loads the package names in importPaths. - loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) - - // scan works with callback to search for packages. See scanCallback for details. - scan(ctx context.Context, callback *scanCallback) error - - // loadExports returns the package name and set of exported symbols in the - // package at dir. loadExports may be called concurrently. - loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []stdlib.Symbol, error) - - // scoreImportPath returns the relevance for an import path. - scoreImportPath(ctx context.Context, path string) float64 - - // ClearForNewScan returns a new Resolver based on the receiver that has - // cleared its internal caches of directory contents. - // - // The new resolver should be primed and then set via - // [ProcessEnv.UpdateResolver]. - ClearForNewScan() Resolver -} - -// A scanCallback controls a call to scan and receives its results. -// In general, minor errors will be silently discarded; a user should not -// expect to receive a full series of calls for everything. -type scanCallback struct { - // rootFound is called before scanning a new root dir. If it returns true, - // the root will be scanned. Returning false will not necessarily prevent - // directories from that root making it to dirFound. - rootFound func(gopathwalk.Root) bool - // dirFound is called when a directory is found that is possibly a Go package. - // pkg will be populated with everything except packageName. - // If it returns true, the package's name will be loaded. - dirFound func(pkg *pkg) bool - // packageNameLoaded is called when a package is found and its name is loaded. - // If it returns true, the package's exports will be loaded. - packageNameLoaded func(pkg *pkg) bool - // exportsLoaded is called when a package's exports have been loaded. - exportsLoaded func(pkg *pkg, exports []stdlib.Symbol) -} - -func addExternalCandidates(ctx context.Context, pass *pass, refs References, filename string) error { - ctx, done := event.Start(ctx, "imports.addExternalCandidates") - defer done() - - results, err := pass.source.ResolveReferences(ctx, filename, refs) - if err != nil { - return err - } - - for _, result := range results { - if result == nil { - continue - } - // Don't offer completions that would shadow predeclared - // names, such as github.com/coreos/etcd/error. - if types.Universe.Lookup(result.Package.Name) != nil { // predeclared - // Ideally we would skip this candidate only - // if the predeclared name is actually - // referenced by the file, but that's a lot - // trickier to compute and would still create - // an import that is likely to surprise the - // user before long. - continue - } - pass.addCandidate(result.Import, result.Package) - } - return nil -} - -// notIdentifier reports whether ch is an invalid identifier character. -func notIdentifier(ch rune) bool { - return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || - '0' <= ch && ch <= '9' || - ch == '_' || - ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch))) -} - -// ImportPathToAssumedName returns the assumed package name of an import path. -// It does this using only string parsing of the import path. -// It picks the last element of the path that does not look like a major -// version, and then picks the valid identifier off the start of that element. -// It is used to determine if a local rename should be added to an import for -// clarity. -// This function could be moved to a standard package and exported if we want -// for use in other tools. -func ImportPathToAssumedName(importPath string) string { - base := path.Base(importPath) - if strings.HasPrefix(base, "v") { - if _, err := strconv.Atoi(base[1:]); err == nil { - dir := path.Dir(importPath) - if dir != "." { - base = path.Base(dir) - } - } - } - base = strings.TrimPrefix(base, "go-") - if i := strings.IndexFunc(base, notIdentifier); i >= 0 { - base = base[:i] - } - return base -} - -// gopathResolver implements resolver for GOPATH workspaces. -type gopathResolver struct { - env *ProcessEnv - cache *DirInfoCache - scanSema chan struct{} // scanSema prevents concurrent scans. -} - -func newGopathResolver(env *ProcessEnv) *gopathResolver { - r := &gopathResolver{ - env: env, - cache: NewDirInfoCache(), - scanSema: make(chan struct{}, 1), - } - r.scanSema <- struct{}{} - return r -} - -func (r *gopathResolver) ClearForNewScan() Resolver { - return newGopathResolver(r.env) -} - -func (r *gopathResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { - names := map[string]string{} - bctx, err := r.env.buildContext() - if err != nil { - return nil, err - } - for _, path := range importPaths { - names[path] = importPathToName(bctx, path, srcDir) - } - return names, nil -} - -// importPathToName finds out the actual package name, as declared in its .go files. -func importPathToName(bctx *build.Context, importPath, srcDir string) string { - // Fast path for standard library without going to disk. - if stdlib.HasPackage(importPath) { - return path.Base(importPath) // stdlib packages always match their paths. - } - - buildPkg, err := bctx.Import(importPath, srcDir, build.FindOnly) - if err != nil { - return "" - } - pkgName, err := packageDirToName(buildPkg.Dir) - if err != nil { - return "" - } - return pkgName -} - -// packageDirToName is a faster version of build.Import if -// the only thing desired is the package name. Given a directory, -// packageDirToName then only parses one file in the package, -// trusting that the files in the directory are consistent. -func packageDirToName(dir string) (packageName string, err error) { - d, err := os.Open(dir) - if err != nil { - return "", err - } - names, err := d.Readdirnames(-1) - d.Close() - if err != nil { - return "", err - } - sort.Strings(names) // to have predictable behavior - var lastErr error - var nfile int - for _, name := range names { - if !strings.HasSuffix(name, ".go") { - continue - } - if strings.HasSuffix(name, "_test.go") { - continue - } - nfile++ - fullFile := filepath.Join(dir, name) - - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, fullFile, nil, parser.PackageClauseOnly) - if err != nil { - lastErr = err - continue - } - pkgName := f.Name.Name - if pkgName == "documentation" { - // Special case from go/build.ImportDir, not - // handled by ctx.MatchFile. - continue - } - if pkgName == "main" { - // Also skip package main, assuming it's a +build ignore generator or example. - // Since you can't import a package main anyway, there's no harm here. - continue - } - return pkgName, nil - } - if lastErr != nil { - return "", lastErr - } - return "", fmt.Errorf("no importable package found in %d Go files", nfile) -} - -type pkg struct { - dir string // absolute file path to pkg directory ("/usr/lib/go/src/net/http") - importPathShort string // vendorless import path ("net/http", "a/b") - packageName string // package name loaded from source if requested - relevance float64 // a weakly-defined score of how relevant a package is. 0 is most relevant. -} - -type pkgDistance struct { - pkg *pkg - distance int // relative distance to target -} - -// byDistanceOrImportPathShortLength sorts by relative distance breaking ties -// on the short import path length and then the import string itself. -type byDistanceOrImportPathShortLength []pkgDistance - -func (s byDistanceOrImportPathShortLength) Len() int { return len(s) } -func (s byDistanceOrImportPathShortLength) Less(i, j int) bool { - di, dj := s[i].distance, s[j].distance - if di == -1 { - return false - } - if dj == -1 { - return true - } - if di != dj { - return di < dj - } - - vi, vj := s[i].pkg.importPathShort, s[j].pkg.importPathShort - if len(vi) != len(vj) { - return len(vi) < len(vj) - } - return vi < vj -} -func (s byDistanceOrImportPathShortLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -func distance(basepath, targetpath string) int { - p, err := filepath.Rel(basepath, targetpath) - if err != nil { - return -1 - } - if p == "." { - return 0 - } - return strings.Count(p, string(filepath.Separator)) + 1 -} - -func (r *gopathResolver) scan(ctx context.Context, callback *scanCallback) error { - add := func(root gopathwalk.Root, dir string) { - // We assume cached directories have not changed. We can skip them and their - // children. - if _, ok := r.cache.Load(dir); ok { - return - } - - importpath := filepath.ToSlash(dir[len(root.Path)+len("/"):]) - info := directoryPackageInfo{ - status: directoryScanned, - dir: dir, - rootType: root.Type, - nonCanonicalImportPath: VendorlessPath(importpath), - } - r.cache.Store(dir, info) - } - processDir := func(info directoryPackageInfo) { - // Skip this directory if we were not able to get the package information successfully. - if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { - return - } - - p := &pkg{ - importPathShort: info.nonCanonicalImportPath, - dir: info.dir, - relevance: MaxRelevance - 1, - } - if info.rootType == gopathwalk.RootGOROOT { - p.relevance = MaxRelevance - } - - if !callback.dirFound(p) { - return - } - var err error - p.packageName, err = r.cache.CachePackageName(info) - if err != nil { - return - } - - if !callback.packageNameLoaded(p) { - return - } - if _, exports, err := r.loadExports(ctx, p, false); err == nil { - callback.exportsLoaded(p, exports) - } - } - stop := r.cache.ScanAndListen(ctx, processDir) - defer stop() - - goenv, err := r.env.goEnv() - if err != nil { - return err - } - var roots []gopathwalk.Root - roots = append(roots, gopathwalk.Root{Path: filepath.Join(goenv["GOROOT"], "src"), Type: gopathwalk.RootGOROOT}) - for _, p := range filepath.SplitList(goenv["GOPATH"]) { - roots = append(roots, gopathwalk.Root{Path: filepath.Join(p, "src"), Type: gopathwalk.RootGOPATH}) - } - // The callback is not necessarily safe to use in the goroutine below. Process roots eagerly. - roots = filterRoots(roots, callback.rootFound) - // We can't cancel walks, because we need them to finish to have a usable - // cache. Instead, run them in a separate goroutine and detach. - scanDone := make(chan struct{}) - go func() { - select { - case <-ctx.Done(): - return - case <-r.scanSema: - } - defer func() { r.scanSema <- struct{}{} }() - gopathwalk.Walk(roots, add, gopathwalk.Options{Logf: r.env.Logf, ModulesEnabled: false}) - close(scanDone) - }() - select { - case <-ctx.Done(): - case <-scanDone: - } - return nil -} - -func (r *gopathResolver) scoreImportPath(ctx context.Context, path string) float64 { - if stdlib.HasPackage(path) { - return MaxRelevance - } - return MaxRelevance - 1 -} - -func filterRoots(roots []gopathwalk.Root, include func(gopathwalk.Root) bool) []gopathwalk.Root { - var result []gopathwalk.Root - for _, root := range roots { - if !include(root) { - continue - } - result = append(result, root) - } - return result -} - -func (r *gopathResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []stdlib.Symbol, error) { - if info, ok := r.cache.Load(pkg.dir); ok && !includeTest { - return r.cache.CacheExports(ctx, r.env, info) - } - return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest) -} - -// VendorlessPath returns the devendorized version of the import path ipath. -// For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b". -func VendorlessPath(ipath string) string { - // Devendorize for use in import statement. - if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 { - return ipath[i+len("/vendor/"):] - } - if strings.HasPrefix(ipath, "vendor/") { - return ipath[len("vendor/"):] - } - return ipath -} - -func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string, includeTest bool) (string, []stdlib.Symbol, error) { - // Look for non-test, buildable .go files which could provide exports. - all, err := os.ReadDir(dir) - if err != nil { - return "", nil, err - } - var files []fs.DirEntry - for _, fi := range all { - name := fi.Name() - if !strings.HasSuffix(name, ".go") || (!includeTest && strings.HasSuffix(name, "_test.go")) { - continue - } - match, err := env.matchFile(dir, fi.Name()) - if err != nil || !match { - continue - } - files = append(files, fi) - } - - if len(files) == 0 { - return "", nil, fmt.Errorf("dir %v contains no buildable, non-test .go files", dir) - } - - var pkgName string - var exports []stdlib.Symbol - fset := token.NewFileSet() - for _, fi := range files { - select { - case <-ctx.Done(): - return "", nil, ctx.Err() - default: - } - - fullFile := filepath.Join(dir, fi.Name()) - // Legacy ast.Object resolution is needed here. - f, err := parser.ParseFile(fset, fullFile, nil, 0) - if err != nil { - env.logf("error parsing %v: %v", fullFile, err) - continue - } - if f.Name.Name == "documentation" { - // Special case from go/build.ImportDir, not - // handled by MatchFile above. - continue - } - if includeTest && strings.HasSuffix(f.Name.Name, "_test") { - // x_test package. We want internal test files only. - continue - } - pkgName = f.Name.Name - for name, obj := range f.Scope.Objects { - if ast.IsExported(name) { - var kind stdlib.Kind - switch obj.Kind { - case ast.Con: - kind = stdlib.Const - case ast.Typ: - kind = stdlib.Type - case ast.Var: - kind = stdlib.Var - case ast.Fun: - kind = stdlib.Func - } - exports = append(exports, stdlib.Symbol{ - Name: name, - Kind: kind, - Version: 0, // unknown; be permissive - }) - } - } - } - sortSymbols(exports) - - env.logf("loaded exports in dir %v (package %v): %v", dir, pkgName, exports) - return pkgName, exports, nil -} - -func sortSymbols(syms []stdlib.Symbol) { - sort.Slice(syms, func(i, j int) bool { - return syms[i].Name < syms[j].Name - }) -} - -// A symbolSearcher searches for a package with a set of symbols, among a set -// of candidates. See [symbolSearcher.search]. -// -// The search occurs within the scope of a single file, with context captured -// in srcDir and xtest. -type symbolSearcher struct { - logf func(string, ...any) - srcDir string // directory containing the file - xtest bool // if set, the file containing is an x_test file - loadExports func(ctx context.Context, pkg *pkg, includeTest bool) (string, []stdlib.Symbol, error) -} - -// search searches the provided candidates for a package containing all -// exported symbols. -// -// If successful, returns the resulting package. -func (s *symbolSearcher) search(ctx context.Context, candidates []pkgDistance, pkgName string, symbols map[string]bool) (*pkg, error) { - // Sort the candidates by their import package length, - // assuming that shorter package names are better than long - // ones. Note that this sorts by the de-vendored name, so - // there's no "penalty" for vendoring. - sort.Sort(byDistanceOrImportPathShortLength(candidates)) - if s.logf != nil { - for i, c := range candidates { - s.logf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), c.pkg.importPathShort, c.pkg.dir) - } - } - - // Arrange rescv so that we can we can await results in order of relevance - // and exit as soon as we find the first match. - // - // Search with bounded concurrency, returning as soon as the first result - // among rescv is non-nil. - rescv := make([]chan *pkg, len(candidates)) - for i := range candidates { - rescv[i] = make(chan *pkg, 1) - } - const maxConcurrentPackageImport = 4 - loadExportsSem := make(chan struct{}, maxConcurrentPackageImport) - - // Ensure that all work is completed at exit. - ctx, cancel := context.WithCancel(ctx) - var wg sync.WaitGroup - defer func() { - cancel() - wg.Wait() - }() - - // Start the search. - wg.Add(1) - go func() { - defer wg.Done() - for i, c := range candidates { - select { - case loadExportsSem <- struct{}{}: - case <-ctx.Done(): - return - } - - i := i - c := c - wg.Add(1) - go func() { - defer func() { - <-loadExportsSem - wg.Done() - }() - if s.logf != nil { - s.logf("loading exports in dir %s (seeking package %s)", c.pkg.dir, pkgName) - } - pkg, err := s.searchOne(ctx, c, symbols) - if err != nil { - if s.logf != nil && ctx.Err() == nil { - s.logf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err) - } - pkg = nil - } - rescv[i] <- pkg // may be nil - }() - } - }() - - // Await the first (best) result. - for _, resc := range rescv { - select { - case r := <-resc: - if r != nil { - return r, nil - } - case <-ctx.Done(): - return nil, ctx.Err() - } - } - return nil, nil -} - -func (s *symbolSearcher) searchOne(ctx context.Context, c pkgDistance, symbols map[string]bool) (*pkg, error) { - if ctx.Err() != nil { - return nil, ctx.Err() - } - // If we're considering the package under test from an x_test, load the - // test variant. - includeTest := s.xtest && c.pkg.dir == s.srcDir - _, exports, err := s.loadExports(ctx, c.pkg, includeTest) - if err != nil { - return nil, err - } - - exportsMap := make(map[string]bool, len(exports)) - for _, sym := range exports { - exportsMap[sym.Name] = true - } - for symbol := range symbols { - if !exportsMap[symbol] { - return nil, nil // no match - } - } - return c.pkg, nil -} - -// pkgIsCandidate reports whether pkg is a candidate for satisfying the -// finding which package pkgIdent in the file named by filename is trying -// to refer to. -// -// This check is purely lexical and is meant to be as fast as possible -// because it's run over all $GOPATH directories to filter out poor -// candidates in order to limit the CPU and I/O later parsing the -// exports in candidate packages. -// -// filename is the file being formatted. -// pkgIdent is the package being searched for, like "client" (if -// searching for "client.New") -func pkgIsCandidate(filename string, refs References, pkg *pkg) bool { - // Check "internal" and "vendor" visibility: - if !CanUse(filename, pkg.dir) { - return false - } - - // Speed optimization to minimize disk I/O: - // - // Use the matchesPath heuristic to filter to package paths that could - // reasonably match a dangling reference. - // - // This permits mismatch naming like directory "go-foo" being package "foo", - // or "pkg.v3" being "pkg", or directory - // "google.golang.org/api/cloudbilling/v1" being package "cloudbilling", but - // doesn't permit a directory "foo" to be package "bar", which is strongly - // discouraged anyway. There's no reason goimports needs to be slow just to - // accommodate that. - for pkgIdent := range refs { - if matchesPath(pkgIdent, pkg.importPathShort) { - return true - } - } - return false -} - -// CanUse reports whether the package in dir is usable from filename, -// respecting the Go "internal" and "vendor" visibility rules. -func CanUse(filename, dir string) bool { - // Fast path check, before any allocations. If it doesn't contain vendor - // or internal, it's not tricky: - // Note that this can false-negative on directories like "notinternal", - // but we check it correctly below. This is just a fast path. - if !strings.Contains(dir, "vendor") && !strings.Contains(dir, "internal") { - return true - } - - dirSlash := filepath.ToSlash(dir) - if !strings.Contains(dirSlash, "/vendor/") && !strings.Contains(dirSlash, "/internal/") && !strings.HasSuffix(dirSlash, "/internal") { - return true - } - // Vendor or internal directory only visible from children of parent. - // That means the path from the current directory to the target directory - // can contain ../vendor or ../internal but not ../foo/vendor or ../foo/internal - // or bar/vendor or bar/internal. - // After stripping all the leading ../, the only okay place to see vendor or internal - // is at the very beginning of the path. - absfile, err := filepath.Abs(filename) - if err != nil { - return false - } - absdir, err := filepath.Abs(dir) - if err != nil { - return false - } - rel, err := filepath.Rel(absfile, absdir) - if err != nil { - return false - } - relSlash := filepath.ToSlash(rel) - if i := strings.LastIndex(relSlash, "../"); i >= 0 { - relSlash = relSlash[i+len("../"):] - } - return !strings.Contains(relSlash, "/vendor/") && !strings.Contains(relSlash, "/internal/") && !strings.HasSuffix(relSlash, "/internal") -} - -// matchesPath reports whether ident may match a potential package name -// referred to by path, using heuristics to filter out unidiomatic package -// names. -// -// Specifically, it checks whether either of the last two '/'- or '\'-delimited -// path segments matches the identifier. The segment-matching heuristic must -// allow for various conventions around segment naming, including go-foo, -// foo-go, and foo.v3. To handle all of these, matching considers both (1) the -// entire segment, ignoring '-' and '.', as well as (2) the last subsegment -// separated by '-' or '.'. So the segment foo-go matches all of the following -// identifiers: foo, go, and foogo. All matches are case insensitive (for ASCII -// identifiers). -// -// See the docstring for [pkgIsCandidate] for an explanation of how this -// heuristic filters potential candidate packages. -func matchesPath(ident, path string) bool { - // Ignore case, for ASCII. - lowerIfASCII := func(b byte) byte { - if 'A' <= b && b <= 'Z' { - return b + ('a' - 'A') - } - return b - } - - // match reports whether path[start:end] matches ident, ignoring [.-]. - match := func(start, end int) bool { - ii := len(ident) - 1 // current byte in ident - pi := end - 1 // current byte in path - for ; pi >= start && ii >= 0; pi-- { - pb := path[pi] - if pb == '-' || pb == '.' { - continue - } - pb = lowerIfASCII(pb) - ib := lowerIfASCII(ident[ii]) - if pb != ib { - return false - } - ii-- - } - return ii < 0 && pi < start // all bytes matched - } - - // segmentEnd and subsegmentEnd hold the end points of the current segment - // and subsegment intervals. - segmentEnd := len(path) - subsegmentEnd := len(path) - - // Count slashes; we only care about the last two segments. - nslash := 0 - - for i := len(path) - 1; i >= 0; i-- { - switch b := path[i]; b { - // TODO(rfindley): we handle backlashes here only because the previous - // heuristic handled backslashes. This is perhaps overly defensive, but is - // the result of many lessons regarding Chesterton's fence and the - // goimports codebase. - // - // However, this function is only ever called with something called an - // 'importPath'. Is it possible that this is a real import path, and - // therefore we need only consider forward slashes? - case '/', '\\': - if match(i+1, segmentEnd) || match(i+1, subsegmentEnd) { - return true - } - nslash++ - if nslash == 2 { - return false // did not match above - } - segmentEnd, subsegmentEnd = i, i // reset - case '-', '.': - if match(i+1, subsegmentEnd) { - return true - } - subsegmentEnd = i - } - } - return match(0, segmentEnd) || match(0, subsegmentEnd) -} - -type visitFn func(node ast.Node) ast.Visitor - -func (fn visitFn) Visit(node ast.Node) ast.Visitor { - return fn(node) -} - -func symbolNameSet(symbols []stdlib.Symbol) map[string]bool { - names := make(map[string]bool) - for _, sym := range symbols { - switch sym.Kind { - case stdlib.Const, stdlib.Var, stdlib.Type, stdlib.Func: - names[sym.Name] = true - } - } - return names -} diff --git a/vendor/golang.org/x/tools/internal/imports/imports.go b/vendor/golang.org/x/tools/internal/imports/imports.go deleted file mode 100644 index b5f5218b5c..0000000000 --- a/vendor/golang.org/x/tools/internal/imports/imports.go +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright 2013 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 imports implements a Go pretty-printer (like package "go/format") -// that also adds or removes import statements as necessary. -package imports - -import ( - "bufio" - "bytes" - "context" - "fmt" - "go/ast" - "go/format" - "go/parser" - "go/printer" - "go/token" - "io" - "regexp" - "strconv" - "strings" - - "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/internal/event" -) - -// Options is golang.org/x/tools/imports.Options with extra internal-only options. -type Options struct { - Env *ProcessEnv // The environment to use. Note: this contains the cached module and filesystem state. - - // LocalPrefix is a comma-separated string of import path prefixes, which, if - // set, instructs Process to sort the import paths with the given prefixes - // into another group after 3rd-party packages. - LocalPrefix string - - Fragment bool // Accept fragment of a source file (no package statement) - AllErrors bool // Report all errors (not just the first 10 on different lines) - - Comments bool // Print comments (true if nil *Options provided) - TabIndent bool // Use tabs for indent (true if nil *Options provided) - TabWidth int // Tab width (8 if nil *Options provided) - - FormatOnly bool // Disable the insertion and deletion of imports -} - -// Process implements golang.org/x/tools/imports.Process with explicit context in opt.Env. -func Process(filename string, src []byte, opt *Options) (formatted []byte, err error) { - fileSet := token.NewFileSet() - var parserMode parser.Mode - if opt.Comments { - parserMode |= parser.ParseComments - } - if opt.AllErrors { - parserMode |= parser.AllErrors - } - file, adjust, err := parse(fileSet, filename, src, parserMode, opt.Fragment) - if err != nil { - return nil, err - } - - if !opt.FormatOnly { - if err := fixImports(fileSet, file, filename, opt.Env); err != nil { - return nil, err - } - } - return formatFile(fileSet, file, src, adjust, opt) -} - -// FixImports returns a list of fixes to the imports that, when applied, -// will leave the imports in the same state as Process. src and opt must -// be specified. -// -// Note that filename's directory influences which imports can be chosen, -// so it is important that filename be accurate. -func FixImports(ctx context.Context, filename string, src []byte, goroot string, logf func(string, ...any), source Source) (fixes []*ImportFix, err error) { - ctx, done := event.Start(ctx, "imports.FixImports") - defer done() - - fileSet := token.NewFileSet() - // TODO(rfindley): these default values for ParseComments and AllErrors were - // extracted from gopls, but are they even needed? - file, _, err := parse(fileSet, filename, src, parser.ParseComments|parser.AllErrors, true) - if err != nil { - return nil, err - } - - return getFixesWithSource(ctx, fileSet, file, filename, goroot, logf, source) -} - -// ApplyFixes applies all of the fixes to the file and formats it. extraMode -// is added in when parsing the file. src and opts must be specified, but no -// env is needed. -func ApplyFixes(fixes []*ImportFix, filename string, src []byte, opt *Options, extraMode parser.Mode) (formatted []byte, err error) { - // Don't use parse() -- we don't care about fragments or statement lists - // here, and we need to work with unparsable files. - fileSet := token.NewFileSet() - parserMode := parser.SkipObjectResolution - if opt.Comments { - parserMode |= parser.ParseComments - } - if opt.AllErrors { - parserMode |= parser.AllErrors - } - parserMode |= extraMode - - file, err := parser.ParseFile(fileSet, filename, src, parserMode) - if file == nil { - return nil, err - } - - // Apply the fixes to the file. - apply(fileSet, file, fixes) - - return formatFile(fileSet, file, src, nil, opt) -} - -// formatFile formats the file syntax tree. -// It may mutate the token.FileSet and the ast.File. -// -// If an adjust function is provided, it is called after formatting -// with the original source (formatFile's src parameter) and the -// formatted file, and returns the postpocessed result. -func formatFile(fset *token.FileSet, file *ast.File, src []byte, adjust func(orig []byte, src []byte) []byte, opt *Options) ([]byte, error) { - mergeImports(file) - sortImports(opt.LocalPrefix, fset.File(file.FileStart), file) - var spacesBefore []string // import paths we need spaces before - for _, impSection := range astutil.Imports(fset, file) { - // Within each block of contiguous imports, see if any - // import lines are in different group numbers. If so, - // we'll need to put a space between them so it's - // compatible with gofmt. - lastGroup := -1 - for _, importSpec := range impSection { - importPath, _ := strconv.Unquote(importSpec.Path.Value) - groupNum := importGroup(opt.LocalPrefix, importPath) - if groupNum != lastGroup && lastGroup != -1 { - spacesBefore = append(spacesBefore, importPath) - } - lastGroup = groupNum - } - - } - - printerMode := printer.UseSpaces - if opt.TabIndent { - printerMode |= printer.TabIndent - } - printConfig := &printer.Config{Mode: printerMode, Tabwidth: opt.TabWidth} - - var buf bytes.Buffer - err := printConfig.Fprint(&buf, fset, file) - if err != nil { - return nil, err - } - out := buf.Bytes() - if adjust != nil { - out = adjust(src, out) - } - if len(spacesBefore) > 0 { - out, err = addImportSpaces(bytes.NewReader(out), spacesBefore) - if err != nil { - return nil, err - } - } - - out, err = format.Source(out) - if err != nil { - return nil, err - } - return out, nil -} - -// parse parses src, which was read from filename, -// as a Go source file or statement list. -func parse(fset *token.FileSet, filename string, src []byte, parserMode parser.Mode, fragment bool) (*ast.File, func(orig, src []byte) []byte, error) { - if parserMode&parser.SkipObjectResolution != 0 { - panic("legacy ast.Object resolution is required") - } - - // Try as whole source file. - file, err := parser.ParseFile(fset, filename, src, parserMode) - if err == nil { - return file, nil, nil - } - // If the error is that the source file didn't begin with a - // package line and we accept fragmented input, fall through to - // try as a source fragment. Stop and return on any other error. - if !fragment || !strings.Contains(err.Error(), "expected 'package'") { - return nil, nil, err - } - - // If this is a declaration list, make it a source file - // by inserting a package clause. - // Insert using a ;, not a newline, so that parse errors are on - // the correct line. - const prefix = "package main;" - psrc := append([]byte(prefix), src...) - file, err = parser.ParseFile(fset, filename, psrc, parserMode) - if err == nil { - // Gofmt will turn the ; into a \n. - // Do that ourselves now and update the file contents, - // so that positions and line numbers are correct going forward. - psrc[len(prefix)-1] = '\n' - fset.File(file.Package).SetLinesForContent(psrc) - - // If a main function exists, we will assume this is a main - // package and leave the file. - if containsMainFunc(file) { - return file, nil, nil - } - - adjust := func(orig, src []byte) []byte { - // Remove the package clause. - src = src[len(prefix):] - return matchSpace(orig, src) - } - return file, adjust, nil - } - // If the error is that the source file didn't begin with a - // declaration, fall through to try as a statement list. - // Stop and return on any other error. - if !strings.Contains(err.Error(), "expected declaration") { - return nil, nil, err - } - - // If this is a statement list, make it a source file - // by inserting a package clause and turning the list - // into a function body. This handles expressions too. - // Insert using a ;, not a newline, so that the line numbers - // in fsrc match the ones in src. - fsrc := append(append([]byte("package p; func _() {"), src...), '}') - file, err = parser.ParseFile(fset, filename, fsrc, parserMode) - if err == nil { - adjust := func(orig, src []byte) []byte { - // Remove the wrapping. - // Gofmt has turned the ; into a \n\n. - src = src[len("package p\n\nfunc _() {"):] - src = src[:len(src)-len("}\n")] - // Gofmt has also indented the function body one level. - // Remove that indent. - src = bytes.ReplaceAll(src, []byte("\n\t"), []byte("\n")) - return matchSpace(orig, src) - } - return file, adjust, nil - } - - // Failed, and out of options. - return nil, nil, err -} - -// containsMainFunc checks if a file contains a function declaration with the -// function signature 'func main()' -func containsMainFunc(file *ast.File) bool { - for _, decl := range file.Decls { - if f, ok := decl.(*ast.FuncDecl); ok { - if f.Name.Name != "main" { - continue - } - - if len(f.Type.Params.List) != 0 { - continue - } - - if f.Type.Results != nil && len(f.Type.Results.List) != 0 { - continue - } - - return true - } - } - - return false -} - -func cutSpace(b []byte) (before, middle, after []byte) { - i := 0 - for i < len(b) && (b[i] == ' ' || b[i] == '\t' || b[i] == '\n') { - i++ - } - j := len(b) - for j > 0 && (b[j-1] == ' ' || b[j-1] == '\t' || b[j-1] == '\n') { - j-- - } - if i <= j { - return b[:i], b[i:j], b[j:] - } - return nil, nil, b[j:] -} - -// matchSpace reformats src to use the same space context as orig. -// 1. If orig begins with blank lines, matchSpace inserts them at the beginning of src. -// 2. matchSpace copies the indentation of the first non-blank line in orig -// to every non-blank line in src. -// 3. matchSpace copies the trailing space from orig and uses it in place -// of src's trailing space. -func matchSpace(orig []byte, src []byte) []byte { - before, _, after := cutSpace(orig) - i := bytes.LastIndex(before, []byte{'\n'}) - before, indent := before[:i+1], before[i+1:] - - _, src, _ = cutSpace(src) - - var b bytes.Buffer - b.Write(before) - for len(src) > 0 { - line := src - if i := bytes.IndexByte(line, '\n'); i >= 0 { - line, src = line[:i+1], line[i+1:] - } else { - src = nil - } - if len(line) > 0 && line[0] != '\n' { // not blank - b.Write(indent) - } - b.Write(line) - } - b.Write(after) - return b.Bytes() -} - -var impLine = regexp.MustCompile(`^\s+(?:[\w\.]+\s+)?"(.+?)"`) - -func addImportSpaces(r io.Reader, breaks []string) ([]byte, error) { - var out bytes.Buffer - in := bufio.NewReader(r) - inImports := false - done := false - for { - s, err := in.ReadString('\n') - if err == io.EOF { - break - } else if err != nil { - return nil, err - } - - if !inImports && !done && strings.HasPrefix(s, "import") { - inImports = true - } - if inImports && (strings.HasPrefix(s, "var") || - strings.HasPrefix(s, "func") || - strings.HasPrefix(s, "const") || - strings.HasPrefix(s, "type")) { - done = true - inImports = false - } - if inImports && len(breaks) > 0 { - if m := impLine.FindStringSubmatch(s); m != nil { - if m[1] == breaks[0] { - out.WriteByte('\n') - breaks = breaks[1:] - } - } - } - - fmt.Fprint(&out, s) - } - return out.Bytes(), nil -} diff --git a/vendor/golang.org/x/tools/internal/imports/mod.go b/vendor/golang.org/x/tools/internal/imports/mod.go deleted file mode 100644 index df94ec8186..0000000000 --- a/vendor/golang.org/x/tools/internal/imports/mod.go +++ /dev/null @@ -1,841 +0,0 @@ -// Copyright 2019 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 imports - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "os" - "path" - "path/filepath" - "regexp" - "slices" - "sort" - "strconv" - "strings" - - "golang.org/x/mod/module" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/gocommand" - "golang.org/x/tools/internal/gopathwalk" - "golang.org/x/tools/internal/stdlib" -) - -// Notes(rfindley): ModuleResolver appears to be heavily optimized for scanning -// as fast as possible, which is desirable for a call to goimports from the -// command line, but it doesn't work as well for gopls, where it suffers from -// slow startup (golang/go#44863) and intermittent hanging (golang/go#59216), -// both caused by populating the cache, albeit in slightly different ways. -// -// A high level list of TODOs: -// - Optimize the scan itself, as there is some redundancy statting and -// reading go.mod files. -// - Invert the relationship between ProcessEnv and Resolver (see the -// docstring of ProcessEnv). -// - Make it easier to use an external resolver implementation. -// -// Smaller TODOs are annotated in the code below. - -// ModuleResolver implements the Resolver interface for a workspace using -// modules. -// -// A goal of the ModuleResolver is to invoke the Go command as little as -// possible. To this end, it runs the Go command only for listing module -// information (i.e. `go list -m -e -json ...`). Package scanning, the process -// of loading package information for the modules, is implemented internally -// via the scan method. -// -// It has two types of state: the state derived from the go command, which -// is populated by init, and the state derived from scans, which is populated -// via scan. A root is considered scanned if it has been walked to discover -// directories. However, if the scan did not require additional information -// from the directory (such as package name or exports), the directory -// information itself may be partially populated. It will be lazily filled in -// as needed by scans, using the scanCallback. -type ModuleResolver struct { - env *ProcessEnv - - // Module state, populated during construction - dummyVendorMod *gocommand.ModuleJSON // if vendoring is enabled, a pseudo-module to represent the /vendor directory - moduleCacheDir string // GOMODCACHE, inferred from GOPATH if unset - roots []gopathwalk.Root // roots to scan, in approximate order of importance - mains []*gocommand.ModuleJSON // main modules - mainByDir map[string]*gocommand.ModuleJSON // module information by dir, to join with roots - modsByModPath []*gocommand.ModuleJSON // all modules, ordered by # of path components in their module path - modsByDir []*gocommand.ModuleJSON // ...or by the number of path components in their Dir. - - // Scanning state, populated by scan - - // scanSema prevents concurrent scans, and guards scannedRoots and the cache - // fields below (though the caches themselves are concurrency safe). - // Receive to acquire, send to release. - scanSema chan struct{} - scannedRoots map[gopathwalk.Root]bool // if true, root has been walked - - // Caches of directory info, populated by scans and scan callbacks - // - // moduleCacheCache stores cached information about roots in the module - // cache, which are immutable and therefore do not need to be invalidated. - // - // otherCache stores information about all other roots (even GOROOT), which - // may change. - moduleCacheCache *DirInfoCache - otherCache *DirInfoCache -} - -// newModuleResolver returns a new module-aware goimports resolver. -// -// Note: use caution when modifying this constructor: changes must also be -// reflected in ModuleResolver.ClearForNewScan. -func newModuleResolver(e *ProcessEnv, moduleCacheCache *DirInfoCache) (*ModuleResolver, error) { - r := &ModuleResolver{ - env: e, - scanSema: make(chan struct{}, 1), - } - r.scanSema <- struct{}{} // release - - goenv, err := r.env.goEnv() - if err != nil { - return nil, err - } - - // TODO(rfindley): can we refactor to share logic with r.env.invokeGo? - inv := gocommand.Invocation{ - BuildFlags: r.env.BuildFlags, - ModFlag: r.env.ModFlag, - Env: r.env.env(), - Logf: r.env.Logf, - WorkingDir: r.env.WorkingDir, - } - - vendorEnabled := false - var mainModVendor *gocommand.ModuleJSON // for module vendoring - var mainModsVendor []*gocommand.ModuleJSON // for workspace vendoring - - goWork := r.env.Env["GOWORK"] - if len(goWork) == 0 { - // TODO(rfindley): VendorEnabled runs the go command to get GOFLAGS, but - // they should be available from the ProcessEnv. Can we avoid the redundant - // invocation? - vendorEnabled, mainModVendor, err = gocommand.VendorEnabled(context.TODO(), inv, r.env.GocmdRunner) - if err != nil { - return nil, err - } - } else { - vendorEnabled, mainModsVendor, err = gocommand.WorkspaceVendorEnabled(context.Background(), inv, r.env.GocmdRunner) - if err != nil { - return nil, err - } - } - - if vendorEnabled { - if mainModVendor != nil { - // Module vendor mode is on, so all the non-Main modules are irrelevant, - // and we need to search /vendor for everything. - r.mains = []*gocommand.ModuleJSON{mainModVendor} - r.dummyVendorMod = &gocommand.ModuleJSON{ - Path: "", - Dir: filepath.Join(mainModVendor.Dir, "vendor"), - } - r.modsByModPath = []*gocommand.ModuleJSON{mainModVendor, r.dummyVendorMod} - r.modsByDir = []*gocommand.ModuleJSON{mainModVendor, r.dummyVendorMod} - } else { - // Workspace vendor mode is on, so all the non-Main modules are irrelevant, - // and we need to search /vendor for everything. - r.mains = mainModsVendor - r.dummyVendorMod = &gocommand.ModuleJSON{ - Path: "", - Dir: filepath.Join(filepath.Dir(goWork), "vendor"), - } - r.modsByModPath = append(slices.Clone(mainModsVendor), r.dummyVendorMod) - r.modsByDir = append(slices.Clone(mainModsVendor), r.dummyVendorMod) - } - } else { - // Vendor mode is off, so run go list -m ... to find everything. - err := r.initAllMods() - // We expect an error when running outside of a module with - // GO111MODULE=on. Other errors are fatal. - if err != nil { - if errMsg := err.Error(); !strings.Contains(errMsg, "working directory is not part of a module") && !strings.Contains(errMsg, "go.mod file not found") { - return nil, err - } - } - } - - r.moduleCacheDir = gomodcacheForEnv(goenv) - if r.moduleCacheDir == "" { - return nil, fmt.Errorf("cannot resolve GOMODCACHE") - } - - sort.Slice(r.modsByModPath, func(i, j int) bool { - count := func(x int) int { - return strings.Count(r.modsByModPath[x].Path, "/") - } - return count(j) < count(i) // descending order - }) - sort.Slice(r.modsByDir, func(i, j int) bool { - count := func(x int) int { - return strings.Count(r.modsByDir[x].Dir, string(filepath.Separator)) - } - return count(j) < count(i) // descending order - }) - - r.roots = []gopathwalk.Root{} - if goenv["GOROOT"] != "" { // "" happens in tests - r.roots = append(r.roots, gopathwalk.Root{Path: filepath.Join(goenv["GOROOT"], "/src"), Type: gopathwalk.RootGOROOT}) - } - r.mainByDir = make(map[string]*gocommand.ModuleJSON) - for _, main := range r.mains { - r.roots = append(r.roots, gopathwalk.Root{Path: main.Dir, Type: gopathwalk.RootCurrentModule}) - r.mainByDir[main.Dir] = main - } - if vendorEnabled { - r.roots = append(r.roots, gopathwalk.Root{Path: r.dummyVendorMod.Dir, Type: gopathwalk.RootOther}) - } else { - addDep := func(mod *gocommand.ModuleJSON) { - if mod.Replace == nil { - // This is redundant with the cache, but we'll skip it cheaply enough - // when we encounter it in the module cache scan. - // - // Including it at a lower index in r.roots than the module cache dir - // helps prioritize matches from within existing dependencies. - r.roots = append(r.roots, gopathwalk.Root{Path: mod.Dir, Type: gopathwalk.RootModuleCache}) - } else { - r.roots = append(r.roots, gopathwalk.Root{Path: mod.Dir, Type: gopathwalk.RootOther}) - } - } - // Walk dependent modules before scanning the full mod cache, direct deps first. - for _, mod := range r.modsByModPath { - if !mod.Indirect && !mod.Main { - addDep(mod) - } - } - for _, mod := range r.modsByModPath { - if mod.Indirect && !mod.Main { - addDep(mod) - } - } - // If provided, share the moduleCacheCache. - // - // TODO(rfindley): The module cache is immutable. However, the loaded - // exports do depend on GOOS and GOARCH. Fortunately, the - // ProcessEnv.buildContext does not adjust these from build.DefaultContext - // (even though it should). So for now, this is OK to share, but we need to - // add logic for handling GOOS/GOARCH. - r.moduleCacheCache = moduleCacheCache - r.roots = append(r.roots, gopathwalk.Root{Path: r.moduleCacheDir, Type: gopathwalk.RootModuleCache}) - } - - r.scannedRoots = map[gopathwalk.Root]bool{} - if r.moduleCacheCache == nil { - r.moduleCacheCache = NewDirInfoCache() - } - r.otherCache = NewDirInfoCache() - return r, nil -} - -// gomodcacheForEnv returns the GOMODCACHE value to use based on the given env -// map, which must have GOMODCACHE and GOPATH populated. -// -// TODO(rfindley): this is defensive refactoring. -// 1. Is this even relevant anymore? Can't we just read GOMODCACHE. -// 2. Use this to separate module cache scanning from other scanning. -func gomodcacheForEnv(goenv map[string]string) string { - if gmc := goenv["GOMODCACHE"]; gmc != "" { - // golang/go#67156: ensure that the module cache is clean, since it is - // assumed as a prefix to directories scanned by gopathwalk, which are - // themselves clean. - return filepath.Clean(gmc) - } - gopaths := filepath.SplitList(goenv["GOPATH"]) - if len(gopaths) == 0 { - return "" - } - return filepath.Join(gopaths[0], "/pkg/mod") -} - -func (r *ModuleResolver) initAllMods() error { - stdout, err := r.env.invokeGo(context.TODO(), "list", "-m", "-e", "-json", "...") - if err != nil { - return err - } - for dec := json.NewDecoder(stdout); dec.More(); { - mod := &gocommand.ModuleJSON{} - if err := dec.Decode(mod); err != nil { - return err - } - if mod.Dir == "" { - r.env.logf("module %v has not been downloaded and will be ignored", mod.Path) - // Can't do anything with a module that's not downloaded. - continue - } - // golang/go#36193: the go command doesn't always clean paths. - mod.Dir = filepath.Clean(mod.Dir) - r.modsByModPath = append(r.modsByModPath, mod) - r.modsByDir = append(r.modsByDir, mod) - if mod.Main { - r.mains = append(r.mains, mod) - } - } - return nil -} - -// ClearForNewScan invalidates the last scan. -// -// It preserves the set of roots, but forgets about the set of directories. -// Though it forgets the set of module cache directories, it remembers their -// contents, since they are assumed to be immutable. -func (r *ModuleResolver) ClearForNewScan() Resolver { - <-r.scanSema // acquire r, to guard scannedRoots - r2 := &ModuleResolver{ - env: r.env, - dummyVendorMod: r.dummyVendorMod, - moduleCacheDir: r.moduleCacheDir, - roots: r.roots, - mains: r.mains, - mainByDir: r.mainByDir, - modsByModPath: r.modsByModPath, - - scanSema: make(chan struct{}, 1), - scannedRoots: make(map[gopathwalk.Root]bool), - otherCache: NewDirInfoCache(), - moduleCacheCache: r.moduleCacheCache, - } - r2.scanSema <- struct{}{} // r2 must start released - // Invalidate root scans. We don't need to invalidate module cache roots, - // because they are immutable. - // (We don't support a use case where GOMODCACHE is cleaned in the middle of - // e.g. a gopls session: the user must restart gopls to get accurate - // imports.) - // - // Scanning for new directories in GOMODCACHE should be handled elsewhere, - // via a call to ScanModuleCache. - for _, root := range r.roots { - if root.Type == gopathwalk.RootModuleCache && r.scannedRoots[root] { - r2.scannedRoots[root] = true - } - } - r.scanSema <- struct{}{} // release r - return r2 -} - -// ClearModuleInfo invalidates resolver state that depends on go.mod file -// contents (essentially, the output of go list -m -json ...). -// -// Notably, it does not forget directory contents, which are reset -// asynchronously via ClearForNewScan. -// -// If the ProcessEnv is a GOPATH environment, ClearModuleInfo is a no op. -// -// TODO(rfindley): move this to a new env.go, consolidating ProcessEnv methods. -func (e *ProcessEnv) ClearModuleInfo() { - if r, ok := e.resolver.(*ModuleResolver); ok { - resolver, err := newModuleResolver(e, e.ModCache) - if err != nil { - e.resolver = nil - e.resolverErr = err - return - } - - <-r.scanSema // acquire (guards caches) - resolver.moduleCacheCache = r.moduleCacheCache - resolver.otherCache = r.otherCache - r.scanSema <- struct{}{} // release - - e.UpdateResolver(resolver) - } -} - -// UpdateResolver sets the resolver for the ProcessEnv to use in imports -// operations. Only for use with the result of [Resolver.ClearForNewScan]. -// -// TODO(rfindley): this awkward API is a result of the (arguably) inverted -// relationship between configuration and state described in the doc comment -// for [ProcessEnv]. -func (e *ProcessEnv) UpdateResolver(r Resolver) { - e.resolver = r - e.resolverErr = nil -} - -// findPackage returns the module and directory from within the main modules -// and their dependencies that contains the package at the given import path, -// or returns nil, "" if no module is in scope. -func (r *ModuleResolver) findPackage(importPath string) (*gocommand.ModuleJSON, string) { - // This can't find packages in the stdlib, but that's harmless for all - // the existing code paths. - for _, m := range r.modsByModPath { - if !strings.HasPrefix(importPath, m.Path) { - continue - } - pathInModule := importPath[len(m.Path):] - pkgDir := filepath.Join(m.Dir, pathInModule) - if r.dirIsNestedModule(pkgDir, m) { - continue - } - - if info, ok := r.cacheLoad(pkgDir); ok { - if loaded, err := info.reachedStatus(nameLoaded); loaded { - if err != nil { - continue // No package in this dir. - } - return m, pkgDir - } - if scanned, err := info.reachedStatus(directoryScanned); scanned && err != nil { - continue // Dir is unreadable, etc. - } - // This is slightly wrong: a directory doesn't have to have an - // importable package to count as a package for package-to-module - // resolution. package main or _test files should count but - // don't. - // TODO(heschi): fix this. - if _, err := r.cachePackageName(info); err == nil { - return m, pkgDir - } - } - - // Not cached. Read the filesystem. - pkgFiles, err := os.ReadDir(pkgDir) - if err != nil { - continue - } - // A module only contains a package if it has buildable go - // files in that directory. If not, it could be provided by an - // outer module. See #29736. - for _, fi := range pkgFiles { - if ok, _ := r.env.matchFile(pkgDir, fi.Name()); ok { - return m, pkgDir - } - } - } - return nil, "" -} - -func (r *ModuleResolver) cacheLoad(dir string) (directoryPackageInfo, bool) { - if info, ok := r.moduleCacheCache.Load(dir); ok { - return info, ok - } - return r.otherCache.Load(dir) -} - -func (r *ModuleResolver) cacheStore(info directoryPackageInfo) { - if info.rootType == gopathwalk.RootModuleCache { - r.moduleCacheCache.Store(info.dir, info) - } else { - r.otherCache.Store(info.dir, info) - } -} - -// cachePackageName caches the package name for a dir already in the cache. -func (r *ModuleResolver) cachePackageName(info directoryPackageInfo) (string, error) { - if info.rootType == gopathwalk.RootModuleCache { - return r.moduleCacheCache.CachePackageName(info) - } - return r.otherCache.CachePackageName(info) -} - -func (r *ModuleResolver) cacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []stdlib.Symbol, error) { - if info.rootType == gopathwalk.RootModuleCache { - return r.moduleCacheCache.CacheExports(ctx, env, info) - } - return r.otherCache.CacheExports(ctx, env, info) -} - -// findModuleByDir returns the module that contains dir, or nil if no such -// module is in scope. -func (r *ModuleResolver) findModuleByDir(dir string) *gocommand.ModuleJSON { - // This is quite tricky and may not be correct. dir could be: - // - a package in the main module. - // - a replace target underneath the main module's directory. - // - a nested module in the above. - // - a replace target somewhere totally random. - // - a nested module in the above. - // - in the mod cache. - // - in /vendor/ in -mod=vendor mode. - // - nested module? Dunno. - // Rumor has it that replace targets cannot contain other replace targets. - // - // Note that it is critical here that modsByDir is sorted to have deeper dirs - // first. This ensures that findModuleByDir finds the innermost module. - // See also golang/go#56291. - for _, m := range r.modsByDir { - if !strings.HasPrefix(dir, m.Dir) { - continue - } - - if r.dirIsNestedModule(dir, m) { - continue - } - - return m - } - return nil -} - -// dirIsNestedModule reports if dir is contained in a nested module underneath -// mod, not actually in mod. -func (r *ModuleResolver) dirIsNestedModule(dir string, mod *gocommand.ModuleJSON) bool { - if !strings.HasPrefix(dir, mod.Dir) { - return false - } - if r.dirInModuleCache(dir) { - // Nested modules in the module cache are pruned, - // so it cannot be a nested module. - return false - } - if mod != nil && mod == r.dummyVendorMod { - // The /vendor pseudomodule is flattened and doesn't actually count. - return false - } - modDir, _ := r.modInfo(dir) - if modDir == "" { - return false - } - return modDir != mod.Dir -} - -func readModName(modFile string) string { - modBytes, err := os.ReadFile(modFile) - if err != nil { - return "" - } - return modulePath(modBytes) -} - -func (r *ModuleResolver) modInfo(dir string) (modDir, modName string) { - if r.dirInModuleCache(dir) { - if matches := modCacheRegexp.FindStringSubmatch(dir); len(matches) == 3 { - index := strings.Index(dir, matches[1]+"@"+matches[2]) - modDir := filepath.Join(dir[:index], matches[1]+"@"+matches[2]) - return modDir, readModName(filepath.Join(modDir, "go.mod")) - } - } - for { - if info, ok := r.cacheLoad(dir); ok { - return info.moduleDir, info.moduleName - } - f := filepath.Join(dir, "go.mod") - info, err := os.Stat(f) - if err == nil && !info.IsDir() { - return dir, readModName(f) - } - - d := filepath.Dir(dir) - if len(d) >= len(dir) { - return "", "" // reached top of file system, no go.mod - } - dir = d - } -} - -func (r *ModuleResolver) dirInModuleCache(dir string) bool { - if r.moduleCacheDir == "" { - return false - } - return strings.HasPrefix(dir, r.moduleCacheDir) -} - -func (r *ModuleResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { - names := map[string]string{} - for _, path := range importPaths { - // TODO(rfindley): shouldn't this use the dirInfoCache? - _, packageDir := r.findPackage(path) - if packageDir == "" { - continue - } - name, err := packageDirToName(packageDir) - if err != nil { - continue - } - names[path] = name - } - return names, nil -} - -func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error { - ctx, done := event.Start(ctx, "imports.ModuleResolver.scan") - defer done() - - processDir := func(info directoryPackageInfo) { - // Skip this directory if we were not able to get the package information successfully. - if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { - return - } - pkg, err := r.canonicalize(info) - if err != nil { - return - } - if !callback.dirFound(pkg) { - return - } - - pkg.packageName, err = r.cachePackageName(info) - if err != nil { - return - } - if !callback.packageNameLoaded(pkg) { - return - } - - _, exports, err := r.loadExports(ctx, pkg, false) - if err != nil { - return - } - callback.exportsLoaded(pkg, exports) - } - - // Start processing everything in the cache, and listen for the new stuff - // we discover in the walk below. - stop1 := r.moduleCacheCache.ScanAndListen(ctx, processDir) - defer stop1() - stop2 := r.otherCache.ScanAndListen(ctx, processDir) - defer stop2() - - // We assume cached directories are fully cached, including all their - // children, and have not changed. We can skip them. - skip := func(root gopathwalk.Root, dir string) bool { - if r.env.SkipPathInScan != nil && root.Type == gopathwalk.RootCurrentModule { - if root.Path == dir { - return false - } - - if r.env.SkipPathInScan(filepath.Clean(dir)) { - return true - } - } - - info, ok := r.cacheLoad(dir) - if !ok { - return false - } - // This directory can be skipped as long as we have already scanned it. - // Packages with errors will continue to have errors, so there is no need - // to rescan them. - packageScanned, _ := info.reachedStatus(directoryScanned) - return packageScanned - } - - add := func(root gopathwalk.Root, dir string) { - r.cacheStore(r.scanDirForPackage(root, dir)) - } - - // r.roots and the callback are not necessarily safe to use in the - // goroutine below. Process them eagerly. - roots := filterRoots(r.roots, callback.rootFound) - // We can't cancel walks, because we need them to finish to have a usable - // cache. Instead, run them in a separate goroutine and detach. - scanDone := make(chan struct{}) - go func() { - select { - case <-ctx.Done(): - return - case <-r.scanSema: // acquire - } - defer func() { r.scanSema <- struct{}{} }() // release - // We have the lock on r.scannedRoots, and no other scans can run. - for _, root := range roots { - if ctx.Err() != nil { - return - } - - if r.scannedRoots[root] { - continue - } - gopathwalk.WalkSkip([]gopathwalk.Root{root}, add, skip, gopathwalk.Options{Logf: r.env.Logf, ModulesEnabled: true}) - r.scannedRoots[root] = true - } - close(scanDone) - }() - select { - case <-ctx.Done(): - case <-scanDone: - } - return nil -} - -func (r *ModuleResolver) scoreImportPath(ctx context.Context, path string) float64 { - if stdlib.HasPackage(path) { - return MaxRelevance - } - mod, _ := r.findPackage(path) - return modRelevance(mod) -} - -func modRelevance(mod *gocommand.ModuleJSON) float64 { - var relevance float64 - switch { - case mod == nil: // out of scope - return MaxRelevance - 4 - case mod.Indirect: - relevance = MaxRelevance - 3 - case !mod.Main: - relevance = MaxRelevance - 2 - default: - relevance = MaxRelevance - 1 // main module ties with stdlib - } - - _, versionString, ok := module.SplitPathVersion(mod.Path) - if ok { - index := strings.Index(versionString, "v") - if index == -1 { - return relevance - } - if versionNumber, err := strconv.ParseFloat(versionString[index+1:], 64); err == nil { - relevance += versionNumber / 1000 - } - } - - return relevance -} - -// canonicalize gets the result of canonicalizing the packages using the results -// of initializing the resolver from 'go list -m'. -func (r *ModuleResolver) canonicalize(info directoryPackageInfo) (*pkg, error) { - // Packages in GOROOT are already canonical, regardless of the std/cmd modules. - if info.rootType == gopathwalk.RootGOROOT { - return &pkg{ - importPathShort: info.nonCanonicalImportPath, - dir: info.dir, - packageName: path.Base(info.nonCanonicalImportPath), - relevance: MaxRelevance, - }, nil - } - - importPath := info.nonCanonicalImportPath - mod := r.findModuleByDir(info.dir) - // Check if the directory is underneath a module that's in scope. - if mod != nil { - // It is. If dir is the target of a replace directive, - // our guessed import path is wrong. Use the real one. - if mod.Dir == info.dir { - importPath = mod.Path - } else { - dirInMod := info.dir[len(mod.Dir)+len("/"):] - importPath = path.Join(mod.Path, filepath.ToSlash(dirInMod)) - } - } else if !strings.HasPrefix(importPath, info.moduleName) { - // The module's name doesn't match the package's import path. It - // probably needs a replace directive we don't have. - return nil, fmt.Errorf("package in %q is not valid without a replace statement", info.dir) - } - - res := &pkg{ - importPathShort: importPath, - dir: info.dir, - relevance: modRelevance(mod), - } - // We may have discovered a package that has a different version - // in scope already. Canonicalize to that one if possible. - if _, canonicalDir := r.findPackage(importPath); canonicalDir != "" { - res.dir = canonicalDir - } - return res, nil -} - -func (r *ModuleResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []stdlib.Symbol, error) { - if info, ok := r.cacheLoad(pkg.dir); ok && !includeTest { - return r.cacheExports(ctx, r.env, info) - } - return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest) -} - -func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) directoryPackageInfo { - subdir := "" - if prefix := root.Path + string(filepath.Separator); strings.HasPrefix(dir, prefix) { - subdir = dir[len(prefix):] - } - importPath := filepath.ToSlash(subdir) - if strings.HasPrefix(importPath, "vendor/") { - // Only enter vendor directories if they're explicitly requested as a root. - return directoryPackageInfo{ - status: directoryScanned, - err: fmt.Errorf("unwanted vendor directory"), - } - } - switch root.Type { - case gopathwalk.RootCurrentModule: - importPath = path.Join(r.mainByDir[root.Path].Path, filepath.ToSlash(subdir)) - case gopathwalk.RootModuleCache: - matches := modCacheRegexp.FindStringSubmatch(subdir) - if len(matches) == 0 { - return directoryPackageInfo{ - status: directoryScanned, - err: fmt.Errorf("invalid module cache path: %v", subdir), - } - } - modPath, err := module.UnescapePath(filepath.ToSlash(matches[1])) - if err != nil { - r.env.logf("decoding module cache path %q: %v", subdir, err) - return directoryPackageInfo{ - status: directoryScanned, - err: fmt.Errorf("decoding module cache path %q: %v", subdir, err), - } - } - importPath = path.Join(modPath, filepath.ToSlash(matches[3])) - } - - modDir, modName := r.modInfo(dir) - result := directoryPackageInfo{ - status: directoryScanned, - dir: dir, - rootType: root.Type, - nonCanonicalImportPath: importPath, - moduleDir: modDir, - moduleName: modName, - } - if root.Type == gopathwalk.RootGOROOT { - // stdlib packages are always in scope, despite the confusing go.mod - return result - } - return result -} - -// modCacheRegexp splits a path in a module cache into module, module version, and package. -var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) - -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. -// -// Copied from cmd/go/internal/modfile. -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/tools/internal/imports/mod_cache.go b/vendor/golang.org/x/tools/internal/imports/mod_cache.go deleted file mode 100644 index b96c9d4bf7..0000000000 --- a/vendor/golang.org/x/tools/internal/imports/mod_cache.go +++ /dev/null @@ -1,331 +0,0 @@ -// Copyright 2019 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 imports - -import ( - "context" - "fmt" - "path" - "path/filepath" - "strings" - "sync" - - "golang.org/x/mod/module" - "golang.org/x/tools/internal/gopathwalk" - "golang.org/x/tools/internal/stdlib" -) - -// To find packages to import, the resolver needs to know about all of -// the packages that could be imported. This includes packages that are -// already in modules that are in (1) the current module, (2) replace targets, -// and (3) packages in the module cache. Packages in (1) and (2) may change over -// time, as the client may edit the current module and locally replaced modules. -// The module cache (which includes all of the packages in (3)) can only -// ever be added to. -// -// The resolver can thus save state about packages in the module cache -// and guarantee that this will not change over time. To obtain information -// about new modules added to the module cache, the module cache should be -// rescanned. -// -// It is OK to serve information about modules that have been deleted, -// as they do still exist. -// TODO(suzmue): can we share information with the caller about -// what module needs to be downloaded to import this package? - -type directoryPackageStatus int - -const ( - _ directoryPackageStatus = iota - directoryScanned - nameLoaded - exportsLoaded -) - -// directoryPackageInfo holds (possibly incomplete) information about packages -// contained in a given directory. -type directoryPackageInfo struct { - // status indicates the extent to which this struct has been filled in. - status directoryPackageStatus - // err is non-nil when there was an error trying to reach status. - err error - - // Set when status >= directoryScanned. - - // dir is the absolute directory of this package. - dir string - rootType gopathwalk.RootType - // nonCanonicalImportPath is the package's expected import path. It may - // not actually be importable at that path. - nonCanonicalImportPath string - - // Module-related information. - moduleDir string // The directory that is the module root of this dir. - moduleName string // The module name that contains this dir. - - // Set when status >= nameLoaded. - - packageName string // the package name, as declared in the source. - - // Set when status >= exportsLoaded. - // TODO(rfindley): it's hard to see this, but exports depend implicitly on - // the default build context GOOS and GOARCH. - // - // We can make this explicit, and key exports by GOOS, GOARCH. - exports []stdlib.Symbol -} - -// reachedStatus returns true when info has a status at least target and any error associated with -// an attempt to reach target. -func (info *directoryPackageInfo) reachedStatus(target directoryPackageStatus) (bool, error) { - if info.err == nil { - return info.status >= target, nil - } - if info.status == target { - return true, info.err - } - return true, nil -} - -// DirInfoCache is a concurrency-safe map for storing information about -// directories that may contain packages. -// -// The information in this cache is built incrementally. Entries are initialized in scan. -// No new keys should be added in any other functions, as all directories containing -// packages are identified in scan. -// -// Other functions, including loadExports and findPackage, may update entries in this cache -// as they discover new things about the directory. -// -// The information in the cache is not expected to change for the cache's -// lifetime, so there is no protection against competing writes. Users should -// take care not to hold the cache across changes to the underlying files. -type DirInfoCache struct { - mu sync.Mutex - // dirs stores information about packages in directories, keyed by absolute path. - dirs map[string]*directoryPackageInfo - listeners map[*int]cacheListener -} - -func NewDirInfoCache() *DirInfoCache { - return &DirInfoCache{ - dirs: make(map[string]*directoryPackageInfo), - listeners: make(map[*int]cacheListener), - } -} - -type cacheListener func(directoryPackageInfo) - -// ScanAndListen calls listener on all the items in the cache, and on anything -// newly added. The returned stop function waits for all in-flight callbacks to -// finish and blocks new ones. -func (d *DirInfoCache) ScanAndListen(ctx context.Context, listener cacheListener) func() { - ctx, cancel := context.WithCancel(ctx) - - // Flushing out all the callbacks is tricky without knowing how many there - // are going to be. Setting an arbitrary limit makes it much easier. - const maxInFlight = 10 - sema := make(chan struct{}, maxInFlight) - for range maxInFlight { - sema <- struct{}{} - } - - cookie := new(int) // A unique ID we can use for the listener. - - // We can't hold mu while calling the listener. - d.mu.Lock() - var keys []string - for key := range d.dirs { - keys = append(keys, key) - } - d.listeners[cookie] = func(info directoryPackageInfo) { - select { - case <-ctx.Done(): - return - case <-sema: - } - listener(info) - sema <- struct{}{} - } - d.mu.Unlock() - - stop := func() { - cancel() - d.mu.Lock() - delete(d.listeners, cookie) - d.mu.Unlock() - for range maxInFlight { - <-sema - } - } - - // Process the pre-existing keys. - for _, k := range keys { - select { - case <-ctx.Done(): - return stop - default: - } - if v, ok := d.Load(k); ok { - listener(v) - } - } - - return stop -} - -// Store stores the package info for dir. -func (d *DirInfoCache) Store(dir string, info directoryPackageInfo) { - d.mu.Lock() - // TODO(rfindley, golang/go#59216): should we overwrite an existing entry? - // That seems incorrect as the cache should be idempotent. - _, old := d.dirs[dir] - d.dirs[dir] = &info - var listeners []cacheListener - for _, l := range d.listeners { - listeners = append(listeners, l) - } - d.mu.Unlock() - - if !old { - for _, l := range listeners { - l(info) - } - } -} - -// Load returns a copy of the directoryPackageInfo for absolute directory dir. -func (d *DirInfoCache) Load(dir string) (directoryPackageInfo, bool) { - d.mu.Lock() - defer d.mu.Unlock() - info, ok := d.dirs[dir] - if !ok { - return directoryPackageInfo{}, false - } - return *info, true -} - -// Keys returns the keys currently present in d. -func (d *DirInfoCache) Keys() (keys []string) { - d.mu.Lock() - defer d.mu.Unlock() - for key := range d.dirs { - keys = append(keys, key) - } - return keys -} - -func (d *DirInfoCache) CachePackageName(info directoryPackageInfo) (string, error) { - if loaded, err := info.reachedStatus(nameLoaded); loaded { - return info.packageName, err - } - if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { - return "", fmt.Errorf("cannot read package name, scan error: %v", err) - } - info.packageName, info.err = packageDirToName(info.dir) - info.status = nameLoaded - d.Store(info.dir, info) - return info.packageName, info.err -} - -func (d *DirInfoCache) CacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []stdlib.Symbol, error) { - if reached, _ := info.reachedStatus(exportsLoaded); reached { - return info.packageName, info.exports, info.err - } - if reached, err := info.reachedStatus(nameLoaded); reached && err != nil { - return "", nil, err - } - info.packageName, info.exports, info.err = loadExportsFromFiles(ctx, env, info.dir, false) - if info.err == context.Canceled || info.err == context.DeadlineExceeded { - return info.packageName, info.exports, info.err - } - // The cache structure wants things to proceed linearly. We can skip a - // step here, but only if we succeed. - if info.status == nameLoaded || info.err == nil { - info.status = exportsLoaded - } else { - info.status = nameLoaded - } - d.Store(info.dir, info) - return info.packageName, info.exports, info.err -} - -// ScanModuleCache walks the given directory, which must be a GOMODCACHE value, -// for directory package information, storing the results in cache. -func ScanModuleCache(dir string, cache *DirInfoCache, logf func(string, ...any)) { - // Note(rfindley): it's hard to see, but this function attempts to implement - // just the side effects on cache of calling PrimeCache with a ProcessEnv - // that has the given dir as its GOMODCACHE. - // - // Teasing out the control flow, we see that we can avoid any handling of - // vendor/ and can infer module info entirely from the path, simplifying the - // logic here. - - root := gopathwalk.Root{ - Path: filepath.Clean(dir), - Type: gopathwalk.RootModuleCache, - } - - directoryInfo := func(root gopathwalk.Root, dir string) directoryPackageInfo { - // This is a copy of ModuleResolver.scanDirForPackage, trimmed down to - // logic that applies to a module cache directory. - - subdir := "" - if dir != root.Path { - subdir = dir[len(root.Path)+len("/"):] - } - - matches := modCacheRegexp.FindStringSubmatch(subdir) - if len(matches) == 0 { - return directoryPackageInfo{ - status: directoryScanned, - err: fmt.Errorf("invalid module cache path: %v", subdir), - } - } - modPath, err := module.UnescapePath(filepath.ToSlash(matches[1])) - if err != nil { - if logf != nil { - logf("decoding module cache path %q: %v", subdir, err) - } - return directoryPackageInfo{ - status: directoryScanned, - err: fmt.Errorf("decoding module cache path %q: %v", subdir, err), - } - } - importPath := path.Join(modPath, filepath.ToSlash(matches[3])) - index := strings.Index(dir, matches[1]+"@"+matches[2]) - modDir := filepath.Join(dir[:index], matches[1]+"@"+matches[2]) - modName := readModName(filepath.Join(modDir, "go.mod")) - return directoryPackageInfo{ - status: directoryScanned, - dir: dir, - rootType: root.Type, - nonCanonicalImportPath: importPath, - moduleDir: modDir, - moduleName: modName, - } - } - - add := func(root gopathwalk.Root, dir string) { - info := directoryInfo(root, dir) - cache.Store(info.dir, info) - } - - skip := func(_ gopathwalk.Root, dir string) bool { - // Skip directories that have already been scanned. - // - // Note that gopathwalk only adds "package" directories, which must contain - // a .go file, and all such package directories in the module cache are - // immutable. So if we can load a dir, it can be skipped. - info, ok := cache.Load(dir) - if !ok { - return false - } - packageScanned, _ := info.reachedStatus(directoryScanned) - return packageScanned - } - - gopathwalk.WalkSkip([]gopathwalk.Root{root}, add, skip, gopathwalk.Options{Logf: logf, ModulesEnabled: true}) -} diff --git a/vendor/golang.org/x/tools/internal/imports/sortimports.go b/vendor/golang.org/x/tools/internal/imports/sortimports.go deleted file mode 100644 index f390be90f1..0000000000 --- a/vendor/golang.org/x/tools/internal/imports/sortimports.go +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright 2013 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. - -// Hacked up copy of go/ast/import.go -// Modified to use a single token.File in preference to a FileSet. - -package imports - -import ( - "go/ast" - "go/token" - "log" - "reflect" - "slices" - "sort" - "strconv" -) - -// sortImports sorts runs of consecutive import lines in import blocks in f. -// It also removes duplicate imports when it is possible to do so without data loss. -// -// It may mutate the token.File and the ast.File. -func sortImports(localPrefix string, tokFile *token.File, f *ast.File) { - for i, d := range f.Decls { - d, ok := d.(*ast.GenDecl) - if !ok || d.Tok != token.IMPORT { - // Not an import declaration, so we're done. - // Imports are always first. - break - } - - if len(d.Specs) == 0 { - // Empty import block, remove it. - f.Decls = slices.Delete(f.Decls, i, i+1) - } - - if !d.Lparen.IsValid() { - // Not a block: sorted by default. - continue - } - - // Identify and sort runs of specs on successive lines. - i := 0 - specs := d.Specs[:0] - for j, s := range d.Specs { - if j > i && tokFile.Line(s.Pos()) > 1+tokFile.Line(d.Specs[j-1].End()) { - // j begins a new run. End this one. - specs = append(specs, sortSpecs(localPrefix, tokFile, f, d.Specs[i:j])...) - i = j - } - } - specs = append(specs, sortSpecs(localPrefix, tokFile, f, d.Specs[i:])...) - d.Specs = specs - - // Deduping can leave a blank line before the rparen; clean that up. - // Ignore line directives. - if len(d.Specs) > 0 { - lastSpec := d.Specs[len(d.Specs)-1] - lastLine := tokFile.PositionFor(lastSpec.Pos(), false).Line - if rParenLine := tokFile.PositionFor(d.Rparen, false).Line; rParenLine > lastLine+1 { - tokFile.MergeLine(rParenLine - 1) // has side effects! - } - } - } -} - -// mergeImports merges all the import declarations into the first one. -// Taken from golang.org/x/tools/go/ast/astutil. -// This does not adjust line numbers properly -func mergeImports(f *ast.File) { - if len(f.Decls) <= 1 { - return - } - - // Merge all the import declarations into the first one. - var first *ast.GenDecl - for i := 0; i < len(f.Decls); i++ { - decl := f.Decls[i] - gen, ok := decl.(*ast.GenDecl) - if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") { - continue - } - if first == nil { - first = gen - continue // Don't touch the first one. - } - // We now know there is more than one package in this import - // declaration. Ensure that it ends up parenthesized. - first.Lparen = first.Pos() - // Move the imports of the other import declaration to the first one. - for _, spec := range gen.Specs { - updateBasicLitPos(spec.(*ast.ImportSpec).Path, first.Pos()) - first.Specs = append(first.Specs, spec) - } - f.Decls = slices.Delete(f.Decls, i, i+1) - i-- - } -} - -// declImports reports whether gen contains an import of path. -// Taken from golang.org/x/tools/go/ast/astutil. -func declImports(gen *ast.GenDecl, path string) bool { - if gen.Tok != token.IMPORT { - return false - } - for _, spec := range gen.Specs { - impspec := spec.(*ast.ImportSpec) - if importPath(impspec) == path { - return true - } - } - return false -} - -func importPath(s ast.Spec) string { - t, err := strconv.Unquote(s.(*ast.ImportSpec).Path.Value) - if err == nil { - return t - } - return "" -} - -func importName(s ast.Spec) string { - n := s.(*ast.ImportSpec).Name - if n == nil { - return "" - } - return n.Name -} - -func importComment(s ast.Spec) string { - c := s.(*ast.ImportSpec).Comment - if c == nil { - return "" - } - return c.Text() -} - -// collapse indicates whether prev may be removed, leaving only next. -func collapse(prev, next ast.Spec) bool { - if importPath(next) != importPath(prev) || importName(next) != importName(prev) { - return false - } - return prev.(*ast.ImportSpec).Comment == nil -} - -type posSpan struct { - Start token.Pos - End token.Pos -} - -// sortSpecs sorts the import specs within each import decl. -// It may mutate the token.File. -func sortSpecs(localPrefix string, tokFile *token.File, f *ast.File, specs []ast.Spec) []ast.Spec { - // Can't short-circuit here even if specs are already sorted, - // since they might yet need deduplication. - // A lone import, however, may be safely ignored. - if len(specs) <= 1 { - return specs - } - - // Record positions for specs. - pos := make([]posSpan, len(specs)) - for i, s := range specs { - pos[i] = posSpan{s.Pos(), s.End()} - } - - // Identify comments in this range. - // Any comment from pos[0].Start to the final line counts. - lastLine := tokFile.Line(pos[len(pos)-1].End) - cstart := len(f.Comments) - cend := len(f.Comments) - for i, g := range f.Comments { - if g.Pos() < pos[0].Start { - continue - } - if i < cstart { - cstart = i - } - if tokFile.Line(g.End()) > lastLine { - cend = i - break - } - } - comments := f.Comments[cstart:cend] - - // Assign each comment to the import spec preceding it. - importComment := map[*ast.ImportSpec][]*ast.CommentGroup{} - specIndex := 0 - for _, g := range comments { - for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() { - specIndex++ - } - s := specs[specIndex].(*ast.ImportSpec) - importComment[s] = append(importComment[s], g) - } - - // Sort the import specs by import path. - // Remove duplicates, when possible without data loss. - // Reassign the import paths to have the same position sequence. - // Reassign each comment to abut the end of its spec. - // Sort the comments by new position. - sort.Sort(byImportSpec{localPrefix, specs}) - - // Dedup. Thanks to our sorting, we can just consider - // adjacent pairs of imports. - deduped := specs[:0] - for i, s := range specs { - if i == len(specs)-1 || !collapse(s, specs[i+1]) { - deduped = append(deduped, s) - } else { - p := s.Pos() - tokFile.MergeLine(tokFile.Line(p)) // has side effects! - } - } - specs = deduped - - // Fix up comment positions - for i, s := range specs { - s := s.(*ast.ImportSpec) - if s.Name != nil { - s.Name.NamePos = pos[i].Start - } - updateBasicLitPos(s.Path, pos[i].Start) - s.EndPos = pos[i].End - nextSpecPos := pos[i].End - - for _, g := range importComment[s] { - for _, c := range g.List { - c.Slash = pos[i].End - nextSpecPos = c.End() - } - } - if i < len(specs)-1 { - pos[i+1].Start = nextSpecPos - pos[i+1].End = nextSpecPos - } - } - - sort.Sort(byCommentPos(comments)) - - // Fixup comments can insert blank lines, because import specs are on different lines. - // We remove those blank lines here by merging import spec to the first import spec line. - firstSpecLine := tokFile.Line(specs[0].Pos()) - for _, s := range specs[1:] { - p := s.Pos() - line := tokFile.Line(p) - for previousLine := line - 1; previousLine >= firstSpecLine; { - // MergeLine can panic. Avoid the panic at the cost of not removing the blank line - // golang/go#50329 - if previousLine > 0 && previousLine < tokFile.LineCount() { - tokFile.MergeLine(previousLine) // has side effects! - previousLine-- - } else { - // try to gather some data to diagnose how this could happen - req := "Please report what the imports section of your go file looked like." - log.Printf("panic avoided: first:%d line:%d previous:%d max:%d. %s", - firstSpecLine, line, previousLine, tokFile.LineCount(), req) - } - } - } - return specs -} - -type byImportSpec struct { - localPrefix string - specs []ast.Spec // slice of *ast.ImportSpec -} - -func (x byImportSpec) Len() int { return len(x.specs) } -func (x byImportSpec) Swap(i, j int) { x.specs[i], x.specs[j] = x.specs[j], x.specs[i] } -func (x byImportSpec) Less(i, j int) bool { - ipath := importPath(x.specs[i]) - jpath := importPath(x.specs[j]) - - igroup := importGroup(x.localPrefix, ipath) - jgroup := importGroup(x.localPrefix, jpath) - if igroup != jgroup { - return igroup < jgroup - } - - if ipath != jpath { - return ipath < jpath - } - iname := importName(x.specs[i]) - jname := importName(x.specs[j]) - - if iname != jname { - return iname < jname - } - return importComment(x.specs[i]) < importComment(x.specs[j]) -} - -type byCommentPos []*ast.CommentGroup - -func (x byCommentPos) Len() int { return len(x) } -func (x byCommentPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] } -func (x byCommentPos) Less(i, j int) bool { return x[i].Pos() < x[j].Pos() } - -// updateBasicLitPos updates lit.Pos, -// ensuring that lit.End (if set) is displaced by the same amount. -// (See https://go.dev/issue/76395.) -func updateBasicLitPos(lit *ast.BasicLit, pos token.Pos) { - len := lit.End() - lit.Pos() - lit.ValuePos = pos - // TODO(adonovan): after go1.26, simplify to: - // lit.ValueEnd = pos + len - v := reflect.ValueOf(lit).Elem().FieldByName("ValueEnd") - if v.IsValid() && v.Int() != 0 { - v.SetInt(int64(pos + len)) - } -} diff --git a/vendor/golang.org/x/tools/internal/imports/source.go b/vendor/golang.org/x/tools/internal/imports/source.go deleted file mode 100644 index cbe4f3c5ba..0000000000 --- a/vendor/golang.org/x/tools/internal/imports/source.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2024 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 imports - -import "context" - -// These types document the APIs below. -// -// TODO(rfindley): consider making these defined types rather than aliases. -type ( - ImportPath = string - PackageName = string - Symbol = string - - // References is set of References found in a Go file. The first map key is the - // left hand side of a selector expression, the second key is the right hand - // side, and the value should always be true. - References = map[PackageName]map[Symbol]bool -) - -// A Result satisfies a missing import. -// -// The Import field describes the missing import spec, and the Package field -// summarizes the package exports. -type Result struct { - Import *ImportInfo - Package *PackageInfo -} - -// An ImportInfo represents a single import statement. -type ImportInfo struct { - ImportPath string // import path, e.g. "crypto/rand". - Name string // import name, e.g. "crand", or "" if none. -} - -// A PackageInfo represents what's known about a package. -type PackageInfo struct { - Name string // package name in the package declaration, if known - Exports map[string]bool // set of names of known package level sortSymbols -} - -// A Source provides imports to satisfy unresolved references in the file being -// fixed. -type Source interface { - // LoadPackageNames queries PackageName information for the requested import - // paths, when operating from the provided srcDir. - // - // TODO(rfindley): try to refactor to remove this operation. - LoadPackageNames(ctx context.Context, srcDir string, paths []ImportPath) (map[ImportPath]PackageName, error) - - // ResolveReferences asks the Source for the best package name to satisfy - // each of the missing references, in the context of fixing the given - // filename. - // - // Returns a map from package name to a [Result] for that package name that - // provides the required symbols. Keys may be omitted in the map if no - // candidates satisfy all missing references for that package name. It is up - // to each data source to select the best result for each entry in the - // missing map. - ResolveReferences(ctx context.Context, filename string, missing References) ([]*Result, error) -} diff --git a/vendor/golang.org/x/tools/internal/imports/source_env.go b/vendor/golang.org/x/tools/internal/imports/source_env.go deleted file mode 100644 index ec996c3ccf..0000000000 --- a/vendor/golang.org/x/tools/internal/imports/source_env.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2024 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 imports - -import ( - "context" - "path/filepath" - "strings" - "sync" - - "golang.org/x/sync/errgroup" - "golang.org/x/tools/internal/gopathwalk" -) - -// ProcessEnvSource implements the [Source] interface using the legacy -// [ProcessEnv] abstraction. -type ProcessEnvSource struct { - env *ProcessEnv - srcDir string - filename string - pkgName string -} - -// NewProcessEnvSource returns a [ProcessEnvSource] wrapping the given -// env, to be used for fixing imports in the file with name filename in package -// named pkgName. -func NewProcessEnvSource(env *ProcessEnv, filename, pkgName string) (*ProcessEnvSource, error) { - abs, err := filepath.Abs(filename) - if err != nil { - return nil, err - } - srcDir := filepath.Dir(abs) - return &ProcessEnvSource{ - env: env, - srcDir: srcDir, - filename: filename, - pkgName: pkgName, - }, nil -} - -func (s *ProcessEnvSource) LoadPackageNames(ctx context.Context, srcDir string, unknown []string) (map[string]string, error) { - r, err := s.env.GetResolver() - if err != nil { - return nil, err - } - return r.loadPackageNames(unknown, srcDir) -} - -func (s *ProcessEnvSource) ResolveReferences(ctx context.Context, filename string, refs map[string]map[string]bool) ([]*Result, error) { - var mu sync.Mutex - found := make(map[string][]pkgDistance) - callback := &scanCallback{ - rootFound: func(gopathwalk.Root) bool { - return true // We want everything. - }, - dirFound: func(pkg *pkg) bool { - return pkgIsCandidate(filename, refs, pkg) - }, - packageNameLoaded: func(pkg *pkg) bool { - if _, want := refs[pkg.packageName]; !want { - return false - } - if pkg.dir == s.srcDir && s.pkgName == pkg.packageName { - // The candidate is in the same directory and has the - // same package name. Don't try to import ourselves. - return false - } - if !CanUse(filename, pkg.dir) { - return false - } - mu.Lock() - defer mu.Unlock() - found[pkg.packageName] = append(found[pkg.packageName], pkgDistance{pkg, distance(s.srcDir, pkg.dir)}) - return false // We'll do our own loading after we sort. - }, - } - resolver, err := s.env.GetResolver() - if err != nil { - return nil, err - } - if err := resolver.scan(ctx, callback); err != nil { - return nil, err - } - - g, ctx := errgroup.WithContext(ctx) - - searcher := symbolSearcher{ - logf: s.env.logf, - srcDir: s.srcDir, - xtest: strings.HasSuffix(s.pkgName, "_test"), - loadExports: resolver.loadExports, - } - - var resultMu sync.Mutex - results := make(map[string]*Result, len(refs)) - for pkgName, symbols := range refs { - g.Go(func() error { - found, err := searcher.search(ctx, found[pkgName], pkgName, symbols) - if err != nil { - return err - } - if found == nil { - return nil // No matching package. - } - - imp := &ImportInfo{ - ImportPath: found.importPathShort, - } - pkg := &PackageInfo{ - Name: pkgName, - Exports: symbols, - } - resultMu.Lock() - results[pkgName] = &Result{Import: imp, Package: pkg} - resultMu.Unlock() - return nil - }) - } - if err := g.Wait(); err != nil { - return nil, err - } - var ans []*Result - for _, x := range results { - ans = append(ans, x) - } - return ans, nil -} diff --git a/vendor/golang.org/x/tools/internal/imports/source_modindex.go b/vendor/golang.org/x/tools/internal/imports/source_modindex.go deleted file mode 100644 index ca745d4a1b..0000000000 --- a/vendor/golang.org/x/tools/internal/imports/source_modindex.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2024 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 imports - -import ( - "context" - "sync" - "time" - - "golang.org/x/tools/internal/modindex" -) - -// This code is here rather than in the modindex package -// to avoid import loops - -// TODO(adonovan): this code is only used by a test in this package. -// Can we delete it? Or is there a plan to call NewIndexSource from -// cmd/goimports? - -// implements Source using modindex, so only for module cache. -// -// this is perhaps over-engineered. A new Index is read at first use. -// And then Update is called after every 15 minutes, and a new Index -// is read if the index changed. It is not clear the Mutex is needed. -type IndexSource struct { - modcachedir string - mu sync.Mutex - index *modindex.Index // (access via getIndex) - expires time.Time -} - -// create a new Source. Called from NewView in cache/session.go. -func NewIndexSource(cachedir string) *IndexSource { - return &IndexSource{modcachedir: cachedir} -} - -func (s *IndexSource) LoadPackageNames(ctx context.Context, srcDir string, paths []ImportPath) (map[ImportPath]PackageName, error) { - /// This is used by goimports to resolve the package names of imports of the - // current package, which is irrelevant for the module cache. - return nil, nil -} - -func (s *IndexSource) ResolveReferences(ctx context.Context, filename string, missing References) ([]*Result, error) { - index, err := s.getIndex() - if err != nil { - return nil, err - } - var cs []modindex.Candidate - for pkg, nms := range missing { - for nm := range nms { - x := index.Lookup(pkg, nm, false) - cs = append(cs, x...) - } - } - found := make(map[string]*Result) - for _, c := range cs { - var x *Result - if x = found[c.ImportPath]; x == nil { - x = &Result{ - Import: &ImportInfo{ - ImportPath: c.ImportPath, - Name: "", - }, - Package: &PackageInfo{ - Name: c.PkgName, - Exports: make(map[string]bool), - }, - } - found[c.ImportPath] = x - } - x.Package.Exports[c.Name] = true - } - var ans []*Result - for _, x := range found { - ans = append(ans, x) - } - return ans, nil -} - -func (s *IndexSource) getIndex() (*modindex.Index, error) { - s.mu.Lock() - defer s.mu.Unlock() - - // (s.index = nil => s.expires is zero, - // so the first condition is strictly redundant. - // But it makes the postcondition very clear.) - if s.index == nil || time.Now().After(s.expires) { - index, err := modindex.Update(s.modcachedir) - if err != nil { - return nil, err - } - s.index = index - s.expires = index.ValidAt.Add(15 * time.Minute) // (refresh period) - } - // Inv: s.index != nil - - return s.index, nil -} diff --git a/vendor/golang.org/x/tools/internal/modindex/directories.go b/vendor/golang.org/x/tools/internal/modindex/directories.go deleted file mode 100644 index 9a963744b5..0000000000 --- a/vendor/golang.org/x/tools/internal/modindex/directories.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2024 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 modindex - -import ( - "fmt" - "log" - "os" - "path/filepath" - "regexp" - "strings" - "sync" - "time" - - "golang.org/x/mod/semver" - "golang.org/x/tools/internal/gopathwalk" -) - -type directory struct { - path string // relative to GOMODCACHE - importPath string - version string // semantic version -} - -// bestDirByImportPath returns the best directory for each import -// path, where "best" means most recent semantic version. These import -// paths are inferred from the GOMODCACHE-relative dir names in dirs. -func bestDirByImportPath(dirs []string) (map[string]directory, error) { - dirsByPath := make(map[string]directory) - for _, dir := range dirs { - importPath, version, err := dirToImportPathVersion(dir) - if err != nil { - return nil, err - } - new := directory{ - path: dir, - importPath: importPath, - version: version, - } - if old, ok := dirsByPath[importPath]; !ok || compareDirectory(new, old) < 0 { - dirsByPath[importPath] = new - } - } - return dirsByPath, nil -} - -// compareDirectory defines an ordering of path@version directories, -// by descending version, then by ascending path. -func compareDirectory(x, y directory) int { - if sign := -semver.Compare(x.version, y.version); sign != 0 { - return sign // latest first - } - return strings.Compare(string(x.path), string(y.path)) -} - -// modCacheRegexp splits a relpathpath into module, module version, and package. -var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) - -// dirToImportPathVersion computes import path and semantic version -// from a GOMODCACHE-relative directory name. -func dirToImportPathVersion(dir string) (string, string, error) { - m := modCacheRegexp.FindStringSubmatch(string(dir)) - // m[1] is the module path - // m[2] is the version major.minor.patch(-
 1 && flds[1][1] == 'D',
-			}
-			if px.Type == Func {
-				n, err := strconv.Atoi(flds[2])
-				if err != nil {
-					continue // should never happen
-				}
-				px.Results = int16(n)
-				if len(flds) >= 4 {
-					sig := strings.Split(flds[3], " ")
-					for i := range sig {
-						// $ cannot otherwise occur. removing the spaces
-						// almost works, but for chan struct{}, e.g.
-						sig[i] = strings.Replace(sig[i], "$", " ", -1)
-					}
-					px.Sig = toFields(sig)
-				}
-			}
-			ans = append(ans, px)
-		}
-	}
-	return ans
-}
-
-func toFields(sig []string) []Field {
-	ans := make([]Field, len(sig)/2)
-	for i := range ans {
-		ans[i] = Field{Arg: sig[2*i], Type: sig[2*i+1]}
-	}
-	return ans
-}
-
-// benchmarks show this is measurably better than strings.Split
-// split into first 4 fields separated by single space
-func fastSplit(x string) []string {
-	ans := make([]string, 0, 4)
-	nxt := 0
-	start := 0
-	for i := 0; i < len(x); i++ {
-		if x[i] != ' ' {
-			continue
-		}
-		ans = append(ans, x[start:i])
-		nxt++
-		start = i + 1
-		if nxt >= 3 {
-			break
-		}
-	}
-	ans = append(ans, x[start:])
-	return ans
-}
-
-func asLexType(c byte) LexType {
-	switch c {
-	case 'C':
-		return Const
-	case 'V':
-		return Var
-	case 'T':
-		return Type
-	case 'F':
-		return Func
-	}
-	return -1
-}
diff --git a/vendor/golang.org/x/tools/internal/modindex/modindex.go b/vendor/golang.org/x/tools/internal/modindex/modindex.go
deleted file mode 100644
index 5fa285d98e..0000000000
--- a/vendor/golang.org/x/tools/internal/modindex/modindex.go
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright 2024 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 modindex contains code for building and searching an
-// [Index] of the Go module cache.
-package modindex
-
-// The directory containing the index, returned by
-// [IndexDir], contains a file index-name- that contains the name
-// of the current index. We believe writing that short file is atomic.
-// [Read] reads that file to get the file name of the index.
-// WriteIndex writes an index with a unique name and then
-// writes that name into a new version of index-name-.
-// ( stands for the CurrentVersion of the index format.)
-
-import (
-	"maps"
-	"os"
-	"path/filepath"
-	"slices"
-	"strings"
-	"time"
-
-	"golang.org/x/mod/semver"
-)
-
-// Update updates the index for the specified Go
-// module cache directory, creating it as needed.
-// On success it returns the current index.
-func Update(gomodcache string) (*Index, error) {
-	prev, err := Read(gomodcache)
-	if err != nil {
-		if !os.IsNotExist(err) {
-			return nil, err
-		}
-		prev = nil
-	}
-	return update(gomodcache, prev)
-}
-
-// update builds, writes, and returns the current index.
-//
-// If old is nil, the new index is built from all of GOMODCACHE;
-// otherwise it is built from the old index plus cache updates
-// since the previous index's time.
-func update(gomodcache string, old *Index) (*Index, error) {
-	gomodcache, err := filepath.Abs(gomodcache)
-	if err != nil {
-		return nil, err
-	}
-	new, changed, err := build(gomodcache, old)
-	if err != nil {
-		return nil, err
-	}
-	if old == nil || changed {
-		if err := write(gomodcache, new); err != nil {
-			return nil, err
-		}
-	}
-	return new, nil
-}
-
-// build returns a new index for the specified Go module cache (an
-// absolute path).
-//
-// If an old index is provided, only directories more recent than it
-// that it are scanned; older directories are provided by the old
-// Index.
-//
-// The boolean result indicates whether new entries were found.
-func build(gomodcache string, old *Index) (*Index, bool, error) {
-	// Set the time window.
-	var start time.Time // = dawn of time
-	if old != nil {
-		start = old.ValidAt
-	}
-	now := time.Now()
-	end := now.Add(24 * time.Hour) // safely in the future
-
-	// Enumerate GOMODCACHE package directories.
-	// Choose the best (latest) package for each import path.
-	pkgDirs := findDirs(gomodcache, start, end)
-	dirByPath, err := bestDirByImportPath(pkgDirs)
-	if err != nil {
-		return nil, false, err
-	}
-
-	// For each import path it might occur only in
-	// dirByPath, only in old, or in both.
-	// If both, use the semantically later one.
-	var entries []Entry
-	if old != nil {
-		for _, entry := range old.Entries {
-			dir, ok := dirByPath[entry.ImportPath]
-			if !ok || semver.Compare(dir.version, entry.Version) <= 0 {
-				// New dir is missing or not more recent; use old entry.
-				entries = append(entries, entry)
-				delete(dirByPath, entry.ImportPath)
-			}
-		}
-	}
-
-	// Extract symbol information for all the new directories.
-	newEntries := extractSymbols(gomodcache, maps.Values(dirByPath))
-	entries = append(entries, newEntries...)
-	slices.SortFunc(entries, func(x, y Entry) int {
-		if n := strings.Compare(x.PkgName, y.PkgName); n != 0 {
-			return n
-		}
-		return strings.Compare(x.ImportPath, y.ImportPath)
-	})
-
-	return &Index{
-		GOMODCACHE: gomodcache,
-		ValidAt:    now, // time before the directories were scanned
-		Entries:    entries,
-	}, len(newEntries) > 0, nil
-}
diff --git a/vendor/golang.org/x/tools/internal/modindex/symbols.go b/vendor/golang.org/x/tools/internal/modindex/symbols.go
deleted file mode 100644
index 8e9702d84b..0000000000
--- a/vendor/golang.org/x/tools/internal/modindex/symbols.go
+++ /dev/null
@@ -1,244 +0,0 @@
-// Copyright 2024 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 modindex
-
-import (
-	"fmt"
-	"go/ast"
-	"go/parser"
-	"go/token"
-	"go/types"
-	"iter"
-	"os"
-	"path/filepath"
-	"runtime"
-	"slices"
-	"strings"
-	"sync"
-
-	"golang.org/x/sync/errgroup"
-)
-
-// The name of a symbol contains information about the symbol:
-//  T for types, TD if the type is deprecated
-//  C for consts, CD if the const is deprecated
-//  V for vars, VD if the var is deprecated
-// and for funcs:  F  ( )*
-// any spaces in  are replaced by $s so that the fields
-// of the name are space separated. F is replaced by FD if the func
-// is deprecated.
-type symbol struct {
-	pkg  string // name of the symbols's package
-	name string // declared name
-	kind string // T, C, V, or F, followed by D if deprecated
-	sig  string // signature information, for F
-}
-
-// extractSymbols returns a (new, unordered) array of Entries, one for
-// each provided package directory, describing its exported symbols.
-func extractSymbols(cwd string, dirs iter.Seq[directory]) []Entry {
-	var (
-		mu      sync.Mutex
-		entries []Entry
-	)
-
-	var g errgroup.Group
-	g.SetLimit(max(2, runtime.GOMAXPROCS(0)/2))
-	for dir := range dirs {
-		g.Go(func() error {
-			thedir := filepath.Join(cwd, string(dir.path))
-			mode := parser.SkipObjectResolution | parser.ParseComments
-
-			// Parse all Go files in dir and extract symbols.
-			dirents, err := os.ReadDir(thedir)
-			if err != nil {
-				return nil // log this someday?
-			}
-			var syms []symbol
-			for _, dirent := range dirents {
-				if !strings.HasSuffix(dirent.Name(), ".go") ||
-					strings.HasSuffix(dirent.Name(), "_test.go") {
-					continue
-				}
-				fname := filepath.Join(thedir, dirent.Name())
-				tr, err := parser.ParseFile(token.NewFileSet(), fname, nil, mode)
-				if err != nil {
-					continue // ignore errors, someday log them?
-				}
-				syms = append(syms, getFileExports(tr)...)
-			}
-
-			// Create an entry for the package.
-			pkg, names := processSyms(syms)
-			if pkg != "" {
-				mu.Lock()
-				defer mu.Unlock()
-				entries = append(entries, Entry{
-					PkgName:    pkg,
-					Dir:        dir.path,
-					ImportPath: dir.importPath,
-					Version:    dir.version,
-					Names:      names,
-				})
-			}
-
-			return nil
-		})
-	}
-	g.Wait() // ignore error
-
-	return entries
-}
-
-func getFileExports(f *ast.File) []symbol {
-	pkg := f.Name.Name
-	if pkg == "main" || pkg == "" {
-		return nil
-	}
-	var ans []symbol
-	// should we look for //go:build ignore?
-	for _, decl := range f.Decls {
-		switch decl := decl.(type) {
-		case *ast.FuncDecl:
-			if decl.Recv != nil {
-				// ignore methods, as we are completing package selections
-				continue
-			}
-			name := decl.Name.Name
-			dtype := decl.Type
-			// not looking at dtype.TypeParams. That is, treating
-			// generic functions just like non-generic ones.
-			sig := dtype.Params
-			kind := "F"
-			if isDeprecated(decl.Doc) {
-				kind += "D"
-			}
-			result := []string{fmt.Sprintf("%d", dtype.Results.NumFields())}
-			for _, x := range sig.List {
-				// This code creates a string representing the type.
-				// TODO(pjw): it may be fragile:
-				// 1. x.Type could be nil, perhaps in ill-formed code
-				// 2. ExprString might someday change incompatibly to
-				//    include struct tags, which can be arbitrary strings
-				if x.Type == nil {
-					// Can this happen without a parse error? (Files with parse
-					// errors are ignored in getSymbols)
-					continue // maybe report this someday
-				}
-				tp := types.ExprString(x.Type)
-				if len(tp) == 0 {
-					// Can this happen?
-					continue // maybe report this someday
-				}
-				// This is only safe if ExprString never returns anything with a $
-				// The only place a $ can occur seems to be in a struct tag, which
-				// can be an arbitrary string literal, and ExprString does not presently
-				// print struct tags. So for this to happen the type of a formal parameter
-				// has to be a explicit struct, e.g. foo(x struct{a int "$"}) and ExprString
-				// would have to show the struct tag. Even testing for this case seems
-				// a waste of effort, but let's remember the possibility
-				if strings.Contains(tp, "$") {
-					continue
-				}
-				tp = strings.Replace(tp, " ", "$", -1)
-				if len(x.Names) == 0 {
-					result = append(result, "_")
-					result = append(result, tp)
-				} else {
-					for _, y := range x.Names {
-						result = append(result, y.Name)
-						result = append(result, tp)
-					}
-				}
-			}
-			sigs := strings.Join(result, " ")
-			if s := newsym(pkg, name, kind, sigs); s != nil {
-				ans = append(ans, *s)
-			}
-		case *ast.GenDecl:
-			depr := isDeprecated(decl.Doc)
-			switch decl.Tok {
-			case token.CONST, token.VAR:
-				tp := "V"
-				if decl.Tok == token.CONST {
-					tp = "C"
-				}
-				if depr {
-					tp += "D"
-				}
-				for _, sp := range decl.Specs {
-					for _, x := range sp.(*ast.ValueSpec).Names {
-						if s := newsym(pkg, x.Name, tp, ""); s != nil {
-							ans = append(ans, *s)
-						}
-					}
-				}
-			case token.TYPE:
-				tp := "T"
-				if depr {
-					tp += "D"
-				}
-				for _, sp := range decl.Specs {
-					if s := newsym(pkg, sp.(*ast.TypeSpec).Name.Name, tp, ""); s != nil {
-						ans = append(ans, *s)
-					}
-				}
-			}
-		}
-	}
-	return ans
-}
-
-func newsym(pkg, name, kind, sig string) *symbol {
-	if len(name) == 0 || !ast.IsExported(name) {
-		return nil
-	}
-	sym := symbol{pkg: pkg, name: name, kind: kind, sig: sig}
-	return &sym
-}
-
-func isDeprecated(doc *ast.CommentGroup) bool {
-	if doc == nil {
-		return false
-	}
-	// go.dev/wiki/Deprecated Paragraph starting 'Deprecated:'
-	// This code fails for /* Deprecated: */, but it's the code from
-	// gopls/internal/analysis/deprecated
-	for line := range strings.SplitSeq(doc.Text(), "\n\n") {
-		if strings.HasPrefix(line, "Deprecated:") {
-			return true
-		}
-	}
-	return false
-}
-
-// return the package name and the value for the symbols.
-// if there are multiple packages, choose one arbitrarily
-// the returned slice is sorted lexicographically
-func processSyms(syms []symbol) (string, []string) {
-	if len(syms) == 0 {
-		return "", nil
-	}
-	slices.SortFunc(syms, func(l, r symbol) int {
-		return strings.Compare(l.name, r.name)
-	})
-	pkg := syms[0].pkg
-	var names []string
-	for _, s := range syms {
-		if s.pkg != pkg {
-			// Symbols came from two files in same dir
-			// with different package declarations.
-			continue
-		}
-		var nx string
-		if s.sig != "" {
-			nx = fmt.Sprintf("%s %s %s", s.name, s.kind, s.sig)
-		} else {
-			nx = fmt.Sprintf("%s %s", s.name, s.kind)
-		}
-		names = append(names, nx)
-	}
-	return pkg, names
-}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 3144c0647b..f4bd8614d5 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -681,7 +681,7 @@ github.com/prometheus/procfs/internal/util
 # github.com/puzpuzpuz/xsync/v3 v3.5.1
 ## explicit; go 1.18
 github.com/puzpuzpuz/xsync/v3
-# github.com/quic-go/quic-go v0.55.0
+# github.com/quic-go/quic-go v0.59.1
 ## explicit; go 1.24
 github.com/quic-go/quic-go
 github.com/quic-go/quic-go/internal/ackhandler
@@ -695,7 +695,9 @@ github.com/quic-go/quic-go/internal/utils
 github.com/quic-go/quic-go/internal/utils/linkedlist
 github.com/quic-go/quic-go/internal/utils/ringbuffer
 github.com/quic-go/quic-go/internal/wire
-github.com/quic-go/quic-go/logging
+github.com/quic-go/quic-go/qlog
+github.com/quic-go/quic-go/qlogwriter
+github.com/quic-go/quic-go/qlogwriter/jsontext
 github.com/quic-go/quic-go/quicvarint
 # github.com/secure-systems-lab/go-securesystemslib v0.9.0
 ## explicit; go 1.20
@@ -889,8 +891,6 @@ golang.org/x/crypto/pkcs12/internal/rc2
 golang.org/x/exp/constraints
 # golang.org/x/mod v0.34.0
 ## explicit; go 1.25.0
-golang.org/x/mod/internal/lazyregexp
-golang.org/x/mod/module
 golang.org/x/mod/semver
 # golang.org/x/net v0.53.0
 ## explicit; go 1.25.0
@@ -946,14 +946,12 @@ golang.org/x/text/unicode/norm
 golang.org/x/time/rate
 # golang.org/x/tools v0.43.0
 ## explicit; go 1.25.0
-golang.org/x/tools/go/ast/astutil
 golang.org/x/tools/go/ast/edge
 golang.org/x/tools/go/ast/inspector
 golang.org/x/tools/go/gcexportdata
 golang.org/x/tools/go/packages
 golang.org/x/tools/go/types/objectpath
 golang.org/x/tools/go/types/typeutil
-golang.org/x/tools/imports
 golang.org/x/tools/internal/aliases
 golang.org/x/tools/internal/event
 golang.org/x/tools/internal/event/core
@@ -961,9 +959,6 @@ golang.org/x/tools/internal/event/keys
 golang.org/x/tools/internal/event/label
 golang.org/x/tools/internal/gcimporter
 golang.org/x/tools/internal/gocommand
-golang.org/x/tools/internal/gopathwalk
-golang.org/x/tools/internal/imports
-golang.org/x/tools/internal/modindex
 golang.org/x/tools/internal/packagesinternal
 golang.org/x/tools/internal/pkgbits
 golang.org/x/tools/internal/stdlib