From a2d617c4b69b904763e392b2ee431083bdae352d Mon Sep 17 00:00:00 2001 From: Vittorio Parrella Date: Sun, 1 Dec 2024 21:45:18 +0100 Subject: [PATCH] feat: #64 added support for mp-quic-go backend --- .github/workflows/integration.yml | 3 + backend/backend_quicgo.go | 58 ++--- backend/backend_quicgo_mp.go | 370 ++++++++++++++++++++++++++++++ go.mod | 24 +- go.sum | 52 +++-- 5 files changed, 452 insertions(+), 55 deletions(-) create mode 100644 backend/backend_quicgo_mp.go diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index cfbc302..bc30c88 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -28,6 +28,9 @@ on: run-name: Code Integration [${{ github.event_name }}][${{ github.head_ref || github.ref_name }}] rev.${{ github.sha }} +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true + jobs: check-diff-files: runs-on: ubuntu-latest diff --git a/backend/backend_quicgo.go b/backend/backend_quicgo.go index f373b06..f31697a 100644 --- a/backend/backend_quicgo.go +++ b/backend/backend_quicgo.go @@ -14,11 +14,11 @@ import ( "encoding/pem" "errors" "fmt" - "github.com/Project-Faster/quic-go" - "github.com/Project-Faster/quic-go/logging" stderr "github.com/Project-Faster/qpep/shared/errors" "github.com/Project-Faster/qpep/shared/logger" "github.com/Project-Faster/qpep/workers/gateway" + "github.com/Project-Faster/quic-go" + "github.com/Project-Faster/quic-go/logging" "io/ioutil" "net" "strings" @@ -31,10 +31,10 @@ const ( QUICGO_DEFAULT_CCA = "reno" ) -var qgoBackend QuicBackend = &quicGoBackend{} +var quicGoBackendVar QuicBackend = &quicGoBackend{} func init() { - Register(QUICGO_BACKEND, qgoBackend) + Register(QUICGO_BACKEND, quicGoBackendVar) } type quicGoBackend struct { @@ -42,7 +42,7 @@ type quicGoBackend struct { } func (q *quicGoBackend) Dial(ctx context.Context, remoteAddress string, port int, clientCertPath string, ccAlgorithm string, ccSlowstartAlgo string, traceOn bool) (QuicBackendConnection, error) { - quicConfig := qgoGetConfiguration(traceOn) + quicConfig := quicGoGetConfiguration(traceOn) var err error var session quic.Connection @@ -56,7 +56,7 @@ func (q *quicGoBackend) Dial(ctx context.Context, remoteAddress string, port int return nil, stderr.ErrFailedGatewayConnect } - sessionAdapter := &qgoConnectionAdapter{ + sessionAdapter := &quicGoConnectionAdapter{ context: ctx, connection: session, } @@ -66,7 +66,7 @@ func (q *quicGoBackend) Dial(ctx context.Context, remoteAddress string, port int } func (q *quicGoBackend) Listen(ctx context.Context, address string, port int, serverCertPath string, serverKeyPath string, ccAlgorithm string, ccSlowstartAlgo string, traceOn bool) (QuicBackendConnection, error) { - quicConfig := qgoGetConfiguration(traceOn) + quicConfig := quicGoGetConfiguration(traceOn) tlsConf := loadTLSConfig(serverCertPath, serverKeyPath) @@ -76,7 +76,7 @@ func (q *quicGoBackend) Listen(ctx context.Context, address string, port int, se return nil, stderr.ErrFailedGatewayConnect } - return &qgoConnectionAdapter{ + return &quicGoConnectionAdapter{ context: ctx, listener: conn, }, err @@ -91,7 +91,7 @@ func (q *quicGoBackend) Close() error { return nil } -func qgoGetConfiguration(traceOn bool) *quic.Config { +func quicGoGetConfiguration(traceOn bool) *quic.Config { cfg := &quic.Config{ MaxIncomingStreams: 1024, DisablePathMTUDiscovery: false, @@ -111,7 +111,7 @@ func qgoGetConfiguration(traceOn bool) *quic.Config { return cfg } -type qgoConnectionAdapter struct { +type quicGoConnectionAdapter struct { context context.Context listener quic.Listener connection quic.Connection @@ -119,7 +119,7 @@ type qgoConnectionAdapter struct { streams []quic.Stream } -func (c *qgoConnectionAdapter) LocalAddr() net.Addr { +func (c *quicGoConnectionAdapter) LocalAddr() net.Addr { if c.connection != nil { return c.connection.LocalAddr() } @@ -129,7 +129,7 @@ func (c *qgoConnectionAdapter) LocalAddr() net.Addr { panic(stderr.ErrInvalidBackendOperation) } -func (c *qgoConnectionAdapter) RemoteAddr() net.Addr { +func (c *quicGoConnectionAdapter) RemoteAddr() net.Addr { if c.connection != nil { return c.connection.RemoteAddr() } @@ -139,13 +139,13 @@ func (c *qgoConnectionAdapter) RemoteAddr() net.Addr { panic(stderr.ErrInvalidBackendOperation) } -func (c *qgoConnectionAdapter) AcceptConnection(ctx context.Context) (QuicBackendConnection, error) { +func (c *quicGoConnectionAdapter) AcceptConnection(ctx context.Context) (QuicBackendConnection, error) { if c.listener != nil { conn, err := c.listener.Accept(ctx) if err != nil { return nil, err } - cNew := &qgoConnectionAdapter{ + cNew := &quicGoConnectionAdapter{ context: ctx, listener: c.listener, connection: conn, @@ -156,30 +156,30 @@ func (c *qgoConnectionAdapter) AcceptConnection(ctx context.Context) (QuicBacken panic(stderr.ErrInvalidBackendOperation) } -func (c *qgoConnectionAdapter) AcceptStream(ctx context.Context) (QuicBackendStream, error) { +func (c *quicGoConnectionAdapter) AcceptStream(ctx context.Context) (QuicBackendStream, error) { if c.connection != nil { stream, err := c.connection.AcceptStream(ctx) if stream != nil { c.streams = append(c.streams, stream) } - return &qgoStreamAdapter{ + return &quicGoStreamAdapter{ Stream: stream, }, err } panic(stderr.ErrInvalidBackendOperation) } -func (c *qgoConnectionAdapter) OpenStream(ctx context.Context) (QuicBackendStream, error) { +func (c *quicGoConnectionAdapter) OpenStream(ctx context.Context) (QuicBackendStream, error) { if c.connection != nil { stream, err := c.connection.OpenStreamSync(ctx) - return &qgoStreamAdapter{ + return &quicGoStreamAdapter{ Stream: stream, }, err } panic(stderr.ErrInvalidBackendOperation) } -func (c *qgoConnectionAdapter) Close(code int, message string) error { +func (c *quicGoConnectionAdapter) Close(code int, message string) error { defer func() { c.connection = nil c.listener = nil @@ -199,13 +199,13 @@ func (c *qgoConnectionAdapter) Close(code int, message string) error { return nil } -func (c *qgoConnectionAdapter) IsClosed() bool { +func (c *quicGoConnectionAdapter) IsClosed() bool { return c.connection == nil && c.listener == nil } -var _ QuicBackendConnection = &qgoConnectionAdapter{} +var _ QuicBackendConnection = &quicGoConnectionAdapter{} -type qgoStreamAdapter struct { +type quicGoStreamAdapter struct { quic.Stream id *uint64 @@ -214,21 +214,21 @@ type qgoStreamAdapter struct { closedWrite bool } -func (stream *qgoStreamAdapter) AbortRead(code uint64) { +func (stream *quicGoStreamAdapter) AbortRead(code uint64) { stream.CancelRead(quic.StreamErrorCode(code)) stream.closedRead = true } -func (stream *qgoStreamAdapter) AbortWrite(code uint64) { +func (stream *quicGoStreamAdapter) AbortWrite(code uint64) { stream.CancelWrite(quic.StreamErrorCode(code)) stream.closedWrite = true } -func (stream *qgoStreamAdapter) Sync() bool { +func (stream *quicGoStreamAdapter) Sync() bool { return stream.IsClosed() } -func (stream *qgoStreamAdapter) ID() uint64 { +func (stream *quicGoStreamAdapter) ID() uint64 { if stream.id != nil { return *stream.id } @@ -247,18 +247,18 @@ func (stream *qgoStreamAdapter) ID() uint64 { return 0 } -func (stream *qgoStreamAdapter) IsClosed() bool { +func (stream *quicGoStreamAdapter) IsClosed() bool { return false // stream.closedRead || stream.closedWrite } -func (stream *qgoStreamAdapter) Close() error { +func (stream *quicGoStreamAdapter) Close() error { ctx := stream.Stream.Context() <-ctx.Done() return stream.Stream.Close() } -var _ QuicBackendStream = &qgoStreamAdapter{} +var _ QuicBackendStream = &quicGoStreamAdapter{} // --- Certificate support --- // diff --git a/backend/backend_quicgo_mp.go b/backend/backend_quicgo_mp.go new file mode 100644 index 0000000..db460b8 --- /dev/null +++ b/backend/backend_quicgo_mp.go @@ -0,0 +1,370 @@ +//go:build !no_quicgo_backend + +package backend + +import ( + "bytes" + "context" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + stderr "github.com/Project-Faster/qpep/shared/errors" + "github.com/Project-Faster/qpep/shared/logger" + "github.com/Project-Faster/qpep/workers/gateway" + "github.com/project-faster/mp-quic-go" + "io/ioutil" + "net" + "strings" + "time" +) + +const ( + QUICGO_MP_BACKEND = "quic-go-mp" + QUICGO_MP_ALPN = "qpep_mp" + QUICGO_MP_DEFAULT_CCA = "reno" +) + +var quicGoMpBackendVar QuicBackend = &quicGoMpBackend{} + +func init() { + Register(QUICGO_MP_BACKEND, quicGoMpBackendVar) +} + +type quicGoMpBackend struct { + connections []QuicBackendConnection +} + +func (q *quicGoMpBackend) Dial(ctx context.Context, remoteAddress string, port int, clientCertPath string, ccAlgorithm string, ccSlowstartAlgo string, traceOn bool) (QuicBackendConnection, error) { + quicConfig := quicGoMpGetConfiguration(traceOn) + + var err error + var session quic.Session + + tlsConf := mpLoadTLSConfig(clientCertPath, "") + gatewayPath := fmt.Sprintf("%s:%d", remoteAddress, port) + + session, err = quic.DialAddr(gatewayPath, tlsConf, quicConfig) + if err != nil { + logger.Error("Unable to Dial Protocol Session: %v\n", err) + return nil, stderr.ErrFailedGatewayConnect + } + + sessionAdapter := &quicGoMpConnectionAdapter{ + context: ctx, + connection: session, + } + + q.connections = append(q.connections, sessionAdapter) + return sessionAdapter, nil +} + +func (q *quicGoMpBackend) Listen(ctx context.Context, address string, port int, serverCertPath string, serverKeyPath string, ccAlgorithm string, ccSlowstartAlgo string, traceOn bool) (QuicBackendConnection, error) { + quicConfig := quicGoMpGetConfiguration(traceOn) + + tlsConf := mpLoadTLSConfig(serverCertPath, serverKeyPath) + + conn, err := quic.ListenAddr(fmt.Sprintf("%s:%d", address, port), tlsConf, quicConfig) + if err != nil { + logger.Error("Failed to listen on Protocol session: %v\n", err) + return nil, stderr.ErrFailedGatewayConnect + } + + return &quicGoMpConnectionAdapter{ + context: ctx, + listener: conn, + }, err +} + +func (q *quicGoMpBackend) Close() error { + for _, conn := range q.connections { + _ = conn.Close(0, "") + } + q.connections = nil + logger.Info("== Protocol Session Closed ==\n") + return nil +} + +func quicGoMpGetConfiguration(traceOn bool) *quic.Config { + cfg := &quic.Config{ + MaxReceiveConnectionFlowControlWindow: 10 * 1024 * 1024, + MaxReceiveStreamFlowControlWindow: 10 * 1024 * 1024, + + IdleTimeout: 2 * time.Second, + HandshakeTimeout: gateway.GetScaledTimeout(10, time.Second), + KeepAlive: false, + + CreatePaths: true, + } + + return cfg +} + +type quicGoMpConnectionAdapter struct { + context context.Context + listener quic.Listener + connection quic.Session + + streams []quic.Stream +} + +func (c *quicGoMpConnectionAdapter) LocalAddr() net.Addr { + if c.connection != nil { + return c.connection.LocalAddr() + } + if c.listener != nil { + return c.listener.Addr() + } + panic(stderr.ErrInvalidBackendOperation) +} + +func (c *quicGoMpConnectionAdapter) RemoteAddr() net.Addr { + if c.connection != nil { + return c.connection.RemoteAddr() + } + if c.listener != nil { + return c.listener.Addr() + } + panic(stderr.ErrInvalidBackendOperation) +} + +func (c *quicGoMpConnectionAdapter) AcceptConnection(ctx context.Context) (QuicBackendConnection, error) { + if c.listener != nil { + conn, err := c.listener.Accept() + if err != nil { + return nil, err + } + cNew := &quicGoMpConnectionAdapter{ + context: ctx, + listener: c.listener, + connection: conn, + streams: make([]quic.Stream, 0, 32), + } + return cNew, nil + } + panic(stderr.ErrInvalidBackendOperation) +} + +func (c *quicGoMpConnectionAdapter) AcceptStream(ctx context.Context) (QuicBackendStream, error) { + if c.connection != nil { + stream, err := c.connection.AcceptStream() + if stream != nil { + c.streams = append(c.streams, stream) + } + return &quicGoMpStreamAdapter{ + Stream: stream, + }, err + } + panic(stderr.ErrInvalidBackendOperation) +} + +func (c *quicGoMpConnectionAdapter) OpenStream(ctx context.Context) (QuicBackendStream, error) { + if c.connection != nil { + stream, err := c.connection.OpenStreamSync() + return &quicGoMpStreamAdapter{ + Stream: stream, + }, err + } + panic(stderr.ErrInvalidBackendOperation) +} + +func (c *quicGoMpConnectionAdapter) Close(code int, message string) error { + defer func() { + c.connection = nil + c.listener = nil + c.streams = nil + }() + if c.connection != nil { + err := errors.New(fmt.Sprintf("code:%d,message:%s", code, message)) + for _, st := range c.streams { + st.Reset(err) + _ = st.Close() + } + return c.connection.Close(err) + } + if c.listener != nil { + return c.listener.Close() + } + return nil +} + +func (c *quicGoMpConnectionAdapter) IsClosed() bool { + return c.connection == nil && c.listener == nil +} + +var _ QuicBackendConnection = &quicGoMpConnectionAdapter{} + +type quicGoMpStreamAdapter struct { + quic.Stream + + id *uint64 + + closedRead bool + closedWrite bool +} + +func (stream *quicGoMpStreamAdapter) AbortRead(code uint64) { + err := errors.New(fmt.Sprintf("code:%d", code)) + stream.Reset(err) + stream.closedRead = true +} + +func (stream *quicGoMpStreamAdapter) AbortWrite(code uint64) { + err := errors.New(fmt.Sprintf("code:%d", code)) + stream.Reset(err) + stream.closedWrite = true +} + +func (stream *quicGoMpStreamAdapter) Sync() bool { + return stream.IsClosed() +} + +func (stream *quicGoMpStreamAdapter) ID() uint64 { + if stream.id != nil { + return *stream.id + } + stream.id = new(uint64) + *stream.id = uint64(stream.StreamID()) + return *stream.id +} + +func (stream *quicGoMpStreamAdapter) IsClosed() bool { + return false // stream.closedRead || stream.closedWrite +} + +func (stream *quicGoMpStreamAdapter) Close() error { + ctx := stream.Stream.Context() + <-ctx.Done() + + return stream.Stream.Close() +} + +var _ QuicBackendStream = &quicGoMpStreamAdapter{} + +// --- Certificate support --- // + +func mpLoadTLSConfig(certPEM, keyPEM string) *tls.Config { + dataCert, err1 := ioutil.ReadFile(certPEM) + dataKey, err2 := ioutil.ReadFile(keyPEM) + + if err1 != nil { + logger.Error("Could not find certificate file %s", certPEM) + return nil + } + + var cert tls.Certificate + var skippedBlockTypes []string + for { + var certDERBlock *pem.Block + certDERBlock, dataCert = pem.Decode(dataCert) + if certDERBlock == nil { + break + } + if certDERBlock.Type == "CERTIFICATE" { + cert.Certificate = append(cert.Certificate, certDERBlock.Bytes) + } else { + skippedBlockTypes = append(skippedBlockTypes, certDERBlock.Type) + } + } + + if len(cert.Certificate) == 0 { + logger.Error("Certificate file %s does not contain valid certificates", certPEM) + return nil + } + + x509Cert, err := x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + logger.Error("Certificate parsing in file %s failed: %v", certPEM, err) + return nil + } + + if err2 == nil { + // support not providing private key file + + skippedBlockTypes = skippedBlockTypes[:0] + var keyDERBlock *pem.Block + for { + keyDERBlock, dataKey = pem.Decode(dataKey) + if keyDERBlock == nil { + logger.Error("Certificate key parsing in file %s failed", dataKey) + return nil + } + if keyDERBlock.Type == "PRIVATE KEY" || strings.HasSuffix(keyDERBlock.Type, " PRIVATE KEY") { + break + } + skippedBlockTypes = append(skippedBlockTypes, keyDERBlock.Type) + } + + cert.PrivateKey, err = mpParsePrivateKey(keyDERBlock.Bytes) + if err != nil { + logger.Error("Error loading private key from file %s: %v", dataKey, err) + return nil + } + + switch pub := x509Cert.PublicKey.(type) { + case *rsa.PublicKey: + priv, ok := cert.PrivateKey.(*rsa.PrivateKey) + if !ok { + logger.Error("Error loading private key from file %s: Not a valid RSA key", dataKey) + return nil + } + if pub.N.Cmp(priv.N) != 0 { + logger.Error("Error loading private key from file %s: internal error", dataKey) + return nil + } + case *ecdsa.PublicKey: + priv, ok := cert.PrivateKey.(*ecdsa.PrivateKey) + if !ok { + logger.Error("Error loading private key from file %s: Not a valid ECDSA key", dataKey) + return nil + } + if pub.X.Cmp(priv.X) != 0 || pub.Y.Cmp(priv.Y) != 0 { + logger.Error("Error loading private key from file %s: internal error", dataKey) + return nil + } + case ed25519.PublicKey: + priv, ok := cert.PrivateKey.(ed25519.PrivateKey) + if !ok { + logger.Error("Error loading private key from file %s: Not a valida ED25519 key", dataKey) + return nil + } + if !bytes.Equal(priv.Public().(ed25519.PublicKey), pub) { + logger.Error("Error loading private key from file %s: internal error", dataKey) + return nil + } + default: + logger.Error("Error loading private key from file %s: unsupported key type %v", dataKey, pub) + return nil + } + } + + return &tls.Config{ + Certificates: []tls.Certificate{cert}, + NextProtos: []string{QUICGO_MP_ALPN}, + InsecureSkipVerify: true, + } +} + +func mpParsePrivateKey(der []byte) (crypto.PrivateKey, error) { + if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { + return key, nil + } + if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { + switch key := key.(type) { + case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey: + return key, nil + default: + return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping") + } + } + if key, err := x509.ParseECPrivateKey(der); err == nil { + return key, nil + } + + return nil, errors.New("tls: failed to parse private key") +} diff --git a/go.mod b/go.mod index 1f6580c..70728c3 100644 --- a/go.mod +++ b/go.mod @@ -13,8 +13,8 @@ require ( github.com/rs/cors v1.8.2 github.com/rs/zerolog v1.29.1 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 - golang.org/x/net v0.10.0 - golang.org/x/sys v0.8.0 + golang.org/x/net v0.31.0 + golang.org/x/sys v0.27.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -28,16 +28,18 @@ require ( github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d github.com/nyaosorg/go-windows-dbg v0.0.0-20210914123807-2acba179a4e5 github.com/project-faster/dialog v1.1.0 - github.com/project-faster/systray v1.4.0 + github.com/project-faster/mp-quic-go v0.0.0-20241201193249-afa77d6f01e4 github.com/segmentio/fasthash v1.0.3 github.com/stretchr/testify v1.8.2 gonum.org/v1/plot v0.12.0 ) require ( + bou.ke/monkey v1.0.2 // indirect git.sr.ht/~sbinet/gg v0.3.1 // indirect github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf // indirect github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect + github.com/bifurcation/mint v0.0.0-20170726191115-f1bdafed5eee // indirect github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb // indirect github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect @@ -54,7 +56,12 @@ require ( github.com/golang/mock v1.6.0 // indirect github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/gorilla/websocket v1.4.2 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/kardianos/service v1.2.2 // indirect github.com/kr/pretty v0.3.1 // indirect + github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect + github.com/lucas-clemente/fnv128a v0.0.0-20160504152609-393af48d3916 // indirect + github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/onsi/ginkgo/v2 v2.8.0 // indirect @@ -63,12 +70,13 @@ require ( github.com/quic-go/qtls-go1-18 v0.2.0 // indirect github.com/quic-go/qtls-go1-19 v0.2.0 // indirect github.com/quic-go/qtls-go1-20 v0.1.0 // indirect - golang.org/x/crypto v0.6.0 // indirect + golang.org/x/crypto v0.29.0 // indirect golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect - golang.org/x/mod v0.10.0 // indirect - golang.org/x/sync v0.2.0 // indirect - golang.org/x/text v0.9.0 // indirect - golang.org/x/tools v0.9.1 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + google.golang.org/protobuf v1.35.2 // indirect gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 // indirect ) diff --git a/go.sum b/go.sum index b5b832a..2d4acba 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/bifurcation/mint v0.0.0-20170726191115-f1bdafed5eee h1:JNtnfk/nN8hi0KmWoaGga/zkP1V6zyJ+ftk7dRhN+kQ= +github.com/bifurcation/mint v0.0.0-20170726191115-f1bdafed5eee/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -25,6 +27,7 @@ github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb/go.mod h1:w github.com/eclipse/paho.mqtt.golang v1.4.2 h1:66wOzfUHSSI1zamx7jR6yMEI5EuHnT1G6rNA5PM12m4= github.com/eclipse/paho.mqtt.golang v1.4.2/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4= github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY= github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So= @@ -59,12 +62,13 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/jackpal/gateway v1.0.7 h1:7tIFeCGmpyrMx9qvT0EgYUi7cxVW48a0mMvnIL17bPM= github.com/jackpal/gateway v1.0.7/go.mod h1:aRcO0UFKt+MgIZmRmvOmnejdDT4Y1DNiNOsSd1AcIbA= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= @@ -79,6 +83,12 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f h1:sSeNEkJrs+0F9TUau0CgWTTNEwF23HST3Eq0A+QIx+A= +github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04= +github.com/lucas-clemente/fnv128a v0.0.0-20160504152609-393af48d3916 h1:BBilz74EccNZJD5AWJKb5j1TK1F5WbloW9dyD9WQ5oY= +github.com/lucas-clemente/fnv128a v0.0.0-20160504152609-393af48d3916/go.mod h1:31qAbuTRFIJASrl34sBxFDAVzDF22dO/GV7Lxw+Kmi8= +github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug= +github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58= github.com/martinlindhe/notify v0.0.0-20181008203735-20632c9a275a h1:nQcAxLK581HrmqF0TVy2GC3iFjB8X+aWGtxQ/t2uyGE= github.com/martinlindhe/notify v0.0.0-20181008203735-20632c9a275a/go.mod h1:zL1p4SieQ27ZZ4V4KdVYdEcSkVl1OwNoi8xI1r5hJkc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= @@ -87,11 +97,13 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nyaosorg/go-windows-dbg v0.0.0-20210914123807-2acba179a4e5 h1:znLCa6TrPGsRkXPuKVsvc4+2CPP6q/86XONwgAGacMY= github.com/nyaosorg/go-windows-dbg v0.0.0-20210914123807-2acba179a4e5/go.mod h1:CddPouRgNL7B8Gt+XJ0Ex8F2ABQURTknkq2SUC7tHG8= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI= github.com/onsi/ginkgo/v2 v2.8.0/go.mod h1:6JsQiECmxCa3V5st74AL/AmsV482EDdVrGaVW6z3oYU= -github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y= +github.com/onsi/gomega v1.36.0 h1:Pb12RlruUtj4XUuPUqeEWc6j5DkVVVA49Uf6YLfC95Y= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/parvit/kardianos-service v1.0.1 h1:mvao5XrG7ZSV/cCxlcBtwtks9jXmp8CvfSjLzj01m7Q= @@ -106,6 +118,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/project-faster/dialog v1.1.0 h1:X8VuaoQYyODaCu9WCGtKMHH7JxS9OvGoVuVsOcaceQw= github.com/project-faster/dialog v1.1.0/go.mod h1:w6I9kRdTYV8b5XO/UYm5hobK2m8CiD+t6ewqcOpciw0= +github.com/project-faster/mp-quic-go v0.0.0-20241201193249-afa77d6f01e4 h1:EFGK+LIpBCCKK0090ucrw+5H+8Ae5k7lQVzhUktT1cU= +github.com/project-faster/mp-quic-go v0.0.0-20241201193249-afa77d6f01e4/go.mod h1:rDBHnX992a+hxRU8FtSvcMb61EkXZxc2GKwnwP0td3Q= github.com/project-faster/systray v1.4.0 h1:0pY5iJt4MfjKPDx5YB6wFN44RS1t5IndQWn9Y4a8w2o= github.com/project-faster/systray v1.4.0/go.mod h1:Bv4Bn5c79K7vmV21sogt+am4rDUDN8NWDZoiJ0CgfvI= github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U= @@ -142,8 +156,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -157,20 +171,20 @@ golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mi golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -183,32 +197,34 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o= gonum.org/v1/plot v0.12.0 h1:y1ZNmfz/xHuHvtgFe8USZVyykQo5ERXPnspQNVK15Og= gonum.org/v1/plot v0.12.0/go.mod h1:PgiMf9+3A3PnZdJIciIXmyN1FwdAA6rXELSN761oQkw= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 h1:MZF6J7CV6s/h0HBkfqebrYfKCVEo5iN+wzE4QhV3Evo= gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2/go.mod h1:s1Sn2yZos05Qfs7NKt867Xe18emOmtsO3eAKbDaon0o= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=