From da7de52813f6a0ad4335435e284375895c7faf3a Mon Sep 17 00:00:00 2001 From: "Lawrence, Rendall" Date: Sat, 18 Mar 2023 00:58:35 +0300 Subject: [PATCH 1/6] change httprouter with fasthttp and simple handler * add http benchmark * move HTTP query parameters parsing to http subpackage * update dependencies --- bittorrent/params.go | 167 ----------------- bittorrent/peer.go | 10 +- dist/example_config.yaml | 10 +- dist/example_config_pg.yaml | 1 - dist/example_config_redis.yaml | 1 - frontend/http/frontend.go | 182 ++++++++++--------- frontend/http/frontend_test.go | 131 +++++++++++++ frontend/http/params.go | 66 +++++++ {bittorrent => frontend/http}/params_test.go | 57 ++---- frontend/http/parser.go | 70 ++++--- frontend/http/writer.go | 53 +++--- frontend/http/writer_test.go | 4 +- frontend/options.go | 42 +---- frontend/udp/frontend.go | 10 +- go.mod | 25 +-- go.sum | 54 +++--- middleware/jwt/jwt_test.go | 8 - pkg/log/log.go | 10 +- 18 files changed, 442 insertions(+), 459 deletions(-) create mode 100644 frontend/http/frontend_test.go create mode 100644 frontend/http/params.go rename {bittorrent => frontend/http}/params_test.go (68%) diff --git a/bittorrent/params.go b/bittorrent/params.go index 7b2b272..00a8773 100644 --- a/bittorrent/params.go +++ b/bittorrent/params.go @@ -2,11 +2,6 @@ package bittorrent import ( "context" - "errors" - "net/url" - "strconv" - "strings" - "github.com/rs/zerolog" ) @@ -21,39 +16,9 @@ type Params interface { // returned as a string because they are encoded in the URL as strings. String(key string) (string, bool) - // RawPath returns the raw path from the request URL. - // The path returned can contain URL encoded data. - // For a request of the form "/announce?port=1234" this would return - // "/announce". - RawPath() string - - // RawQuery returns the raw query from the request URL, excluding the - // delimiter '?'. - // For a request of the form "/announce?port=1234" this would return - // "port=1234" - RawQuery() string - zerolog.LogObjectMarshaler } -var ( - // ErrKeyNotFound is returned when a provided key has no value associated with - // it. - ErrKeyNotFound = errors.New("query: value for the provided key does not exist") - // ErrInvalidQueryEscape is returned when a query string contains invalid - // escapes. - ErrInvalidQueryEscape = ClientError("invalid query escape") -) - -// QueryParams parses a URL Query and implements the Params interface with some -// additional helpers. -type QueryParams struct { - path string - query string - params map[string]string - infoHashes []InfoHash -} - type routeParamsKey struct{} // RouteParamsKey is a key for the context of a request that @@ -104,135 +69,3 @@ func RemapRouteParamsToBgContext(inCtx context.Context) context.Context { } return context.WithValue(context.Background(), RouteParamsKey, rp) } - -// ParseURLData parses a request URL or UDP URLData as defined in BEP41. -// It expects a concatenated string of the request's path and query parts as -// defined in RFC 3986. As both the udp: and http: scheme used by BitTorrent -// include an authority part the path part must always begin with a slash. -// An example of the expected URLData would be "/announce?port=1234&uploaded=0" -// or "/?auth=0x1337". -// HTTP servers should pass (*http.Request).RequestURI, UDP servers should -// pass the concatenated, unchanged URLData as defined in BEP41. -// -// Note that, in the case of a key occurring multiple times in the query, only -// the last value for that key is kept. -// The only exception to this rule is the key "info_hash" which will attempt to -// parse each value as an InfoHash and return an error if parsing fails. All -// InfoHashes are collected and can later be retrieved by calling the InfoHashes -// method. -// -// Also note that any error that is encountered during parsing is returned as a -// ClientError, as this method is expected to be used to parse client-provided -// data. -func ParseURLData(urlData string) (*QueryParams, error) { - var path, query string - - queryDelim := strings.IndexAny(urlData, "?") - if queryDelim == -1 { - path = urlData - } else { - path = urlData[:queryDelim] - query = urlData[queryDelim+1:] - } - - q, err := parseQuery(query) - if err != nil { - return nil, ClientError(err.Error()) - } - q.path = path - return q, nil -} - -// parseQuery parses a URL query into QueryParams. -// The query is expected to exclude the delimiting '?'. -func parseQuery(query string) (q *QueryParams, err error) { - // This is basically url.parseQuery, but with a map[string]string - // instead of map[string][]string for the values. - q = &QueryParams{ - query: query, - infoHashes: nil, - params: make(map[string]string), - } - - for query != "" { - key := query - if i := strings.IndexAny(key, "&;"); i >= 0 { - key, query = key[:i], key[i+1:] - } else { - query = "" - } - if key == "" { - continue - } - value := "" - if i := strings.Index(key, "="); i >= 0 { - key, value = key[:i], key[i+1:] - } - key, err = url.QueryUnescape(key) - if err != nil { - // QueryUnescape returns an error like "invalid escape: '%x'". - // But frontends record these errors to prometheus, which generates - // a lot of time series. - // We log it here for debugging instead. - return nil, ErrInvalidQueryEscape - } - value, err = url.QueryUnescape(value) - if err != nil { - // QueryUnescape returns an error like "invalid escape: '%x'". - // But frontends record these errors to prometheus, which generates - // a lot of time series. - // We log it here for debugging instead. - return nil, ErrInvalidQueryEscape - } - - if key == "info_hash" { - if ih, err := NewInfoHash(value); err == nil { - q.infoHashes = append(q.infoHashes, ih) - } else { - return nil, err - } - } else { - q.params[strings.ToLower(key)] = value - } - } - - return q, nil -} - -// String returns a string parsed from a query. Every key can be returned as a -// string because they are encoded in the URL as strings. -func (qp QueryParams) String(key string) (string, bool) { - value, ok := qp.params[strings.ToLower(key)] - return value, ok -} - -// Uint returns an uint parsed from a query. After being called, it is safe to -// cast the uint64 to your desired length. -func (qp QueryParams) Uint(key string, bitSize int) (uint64, error) { - str, exists := qp.params[strings.ToLower(key)] - if !exists { - return 0, ErrKeyNotFound - } - - return strconv.ParseUint(str, 10, bitSize) -} - -// InfoHashes returns a list of requested infohashes. -func (qp QueryParams) InfoHashes() []InfoHash { - return qp.infoHashes -} - -// RawPath returns the raw path from the parsed URL. -func (qp QueryParams) RawPath() string { - return qp.path -} - -// RawQuery returns the raw query from the parsed URL. -func (qp QueryParams) RawQuery() string { - return qp.query -} - -// MarshalZerologObject writes fields into zerolog event -func (qp QueryParams) MarshalZerologObject(e *zerolog.Event) { - e.Str("path", qp.path).Str("query", qp.query) -} diff --git a/bittorrent/peer.go b/bittorrent/peer.go index 701413b..3227e42 100644 --- a/bittorrent/peer.go +++ b/bittorrent/peer.go @@ -88,7 +88,15 @@ func NewInfoHash(data any) (InfoHash, error) { case [InfoHashV2Len]byte: ba = t[:] case []byte: - ba = t + l := len(t) + if l == InfoHashV1Len*2 || l == InfoHashV2Len*2 { + ba = make([]byte, l/2) + if _, err := hex.Decode(ba, t); err != nil { + return NoneInfoHash, err + } + } else { + ba = t + } case string: l := len(t) if l == InfoHashV1Len*2 || l == InfoHashV2Len*2 { diff --git a/dist/example_config.yaml b/dist/example_config.yaml index 558373c..e5d7afb 100644 --- a/dist/example_config.yaml +++ b/dist/example_config.yaml @@ -36,11 +36,12 @@ frontends: # Enable SO_REUSEPORT to allow starting multiple mochi instances with the same HTTP(S) port. # You can also use this parameter to define two or more listeners or separate processes # for the same address and port, and (possibly) increase throughput (faster queue processing - # because of multiple 'workers'). + # because of multiple processes). reuse_port: true - # Number of connection listeners to start. See reuse_port, default is 1. - workers: 1 + # For http frontend it's number of concurrent connections. + # Default is 262144. + workers: 0 # The timeout durations for HTTP requests. read_timeout: 5s @@ -119,7 +120,8 @@ frontends: # because of multiple 'workers'). reuse_port: true - # Number of connection listeners to start. See reuse_port, default is 1. + # For udp frontend it's number of listen goroutines to be used with reuse_port option. + # Default is 1. workers: 1 # The leeway for a timestamp on a connection ID. diff --git a/dist/example_config_pg.yaml b/dist/example_config_pg.yaml index e830291..4310d63 100644 --- a/dist/example_config_pg.yaml +++ b/dist/example_config_pg.yaml @@ -14,7 +14,6 @@ frontends: tls_cert_path: "" tls_key_path: "" reuse_port: true - workers: 1 read_timeout: 5s write_timeout: 5s enable_keepalive: false diff --git a/dist/example_config_redis.yaml b/dist/example_config_redis.yaml index adbbde9..1b5b497 100644 --- a/dist/example_config_redis.yaml +++ b/dist/example_config_redis.yaml @@ -14,7 +14,6 @@ frontends: tls_cert_path: "" tls_key_path: "" reuse_port: true - workers: 1 read_timeout: 5s write_timeout: 5s enable_keepalive: false diff --git a/frontend/http/frontend.go b/frontend/http/frontend.go index 3eea926..f5e29aa 100644 --- a/frontend/http/frontend.go +++ b/frontend/http/frontend.go @@ -3,16 +3,15 @@ package http import ( - "context" "crypto/tls" "errors" - "io" "net/http" "net/netip" + "path" "sync" "time" - "github.com/julienschmidt/httprouter" + "github.com/valyala/fasthttp" "github.com/sot-tech/mochi/bittorrent" "github.com/sot-tech/mochi/frontend" @@ -37,6 +36,8 @@ func init() { // Config represents all configurable options for an HTTP BitTorrent Frontend type Config struct { frontend.ListenOptions + ReadTimeout time.Duration `cfg:"read_timeout"` + WriteTimeout time.Duration `cfg:"write_timeout"` IdleTimeout time.Duration `cfg:"idle_timeout"` EnableKeepAlive bool `cfg:"enable_keepalive"` UseTLS bool `cfg:"tls"` @@ -49,6 +50,8 @@ type Config struct { } const ( + defaultReadTimeout = 2 * time.Second + defaultWriteTimeout = 2 * time.Second defaultIdleTimeout = 30 * time.Second defaultAnnounceRoute = "/announce" defaultScrapeRoute = "/scrape" @@ -58,11 +61,30 @@ const ( // default values replacing anything that is invalid. func (cfg Config) Validate() (validCfg Config, err error) { validCfg = cfg - validCfg.ListenOptions = cfg.ListenOptions.Validate(false, logger) + validCfg.ListenOptions = cfg.ListenOptions.Validate(logger) if cfg.UseTLS && (len(cfg.TLSCertPath) == 0 || len(cfg.TLSKeyPath) == 0) { err = errTLSNotProvided return } + + if cfg.ReadTimeout <= 0 { + validCfg.ReadTimeout = defaultReadTimeout + logger.Warn(). + Str("name", "ReadTimeout"). + Dur("provided", cfg.ReadTimeout). + Dur("default", validCfg.ReadTimeout). + Msg("falling back to default configuration") + } + + if cfg.WriteTimeout <= 0 { + validCfg.WriteTimeout = defaultWriteTimeout + logger.Warn(). + Str("name", "WriteTimeout"). + Dur("provided", cfg.WriteTimeout). + Dur("default", validCfg.WriteTimeout). + Msg("falling back to default configuration") + } + if cfg.IdleTimeout <= 0 { validCfg.IdleTimeout = defaultIdleTimeout if cfg.EnableKeepAlive { @@ -94,20 +116,8 @@ func (cfg Config) Validate() (validCfg Config, err error) { return } -// httpServer replaces http.Close method with http.Shutdown -type httpServer struct { - *http.Server -} - -func (c httpServer) Close() (err error) { - if c.Server != nil { - err = c.Shutdown(context.Background()) - } - return -} - type httpFE struct { - servers []httpServer + *fasthttp.Server logic *middleware.Logic collectTimings bool onceCloser sync.Once @@ -128,20 +138,17 @@ func NewFrontend(c conf.MapConfig, logic *middleware.Logic) (frontend.Frontend, f := &httpFE{ logic: logic, - servers: make([]httpServer, cfg.Workers), collectTimings: cfg.EnableRequestTiming, ParseOptions: cfg.ParseOptions, - } - - for i := range f.servers { - f.servers[i] = httpServer{ - &http.Server{ - ReadTimeout: cfg.ReadTimeout, - ReadHeaderTimeout: cfg.ReadTimeout, - WriteTimeout: cfg.WriteTimeout, - IdleTimeout: cfg.IdleTimeout, - }, - } + Server: &fasthttp.Server{ + ReadTimeout: cfg.ReadTimeout, + WriteTimeout: cfg.WriteTimeout, + IdleTimeout: cfg.IdleTimeout, + Concurrency: int(cfg.Workers), + DisableKeepalive: !cfg.EnableKeepAlive, + GetOnly: true, + Logger: logger, + }, } // If TLS is enabled, create a key pair. @@ -151,36 +158,50 @@ func NewFrontend(c conf.MapConfig, logic *middleware.Logic) (frontend.Frontend, return nil, err } certs := []tls.Certificate{cert} - for i := range f.servers { - f.servers[i].TLSConfig = &tls.Config{ - Certificates: certs, - MinVersion: tls.VersionTLS12, - } + f.Server.TLSConfig = &tls.Config{ + Certificates: certs, + MinVersion: tls.VersionTLS12, } } - router := httprouter.New() + pathRouting := make(map[string]func(*fasthttp.RequestCtx), + len(cfg.AnnounceRoutes)+len(cfg.ScrapeRoutes)+len(cfg.PingRoutes)) + for _, route := range cfg.AnnounceRoutes { - router.GET(route, f.announceRoute) + route = path.Clean(route) + if !path.IsAbs(route) { + route = "/" + route + } + pathRouting[route] = f.announceRoute } for _, route := range cfg.ScrapeRoutes { - router.GET(route, f.scrapeRoute) + route = path.Clean(route) + if !path.IsAbs(route) { + route = "/" + route + } + pathRouting[path.Clean(route)] = f.scrapeRoute } for _, route := range cfg.PingRoutes { - router.GET(route, f.ping) - router.HEAD(route, f.ping) + route = path.Clean(route) + if !path.IsAbs(route) { + route = "/" + route + } + pathRouting[path.Clean(route)] = f.ping } - for _, srv := range f.servers { - srv.Handler = router - srv.SetKeepAlivesEnabled(cfg.EnableKeepAlive) - go runServer(srv, &cfg) + f.Server.Handler = func(ctx *fasthttp.RequestCtx) { + if route, exists := pathRouting[string(ctx.Path())]; exists { + route(ctx) + } else { + ctx.NotFound() + } } + go runServer(f.Server, &cfg) return f, nil } -func runServer(s httpServer, cfg *Config) { +func runServer(s *fasthttp.Server, cfg *Config) { ln, err := cfg.ListenTCP() if err == nil { if s.TLSConfig == nil { @@ -197,30 +218,20 @@ func runServer(s httpServer, cfg *Config) { // Close provides a thread-safe way to gracefully shut down a currently running Frontend. func (f *httpFE) Close() (err error) { f.onceCloser.Do(func() { - cls := make([]io.Closer, len(f.servers)) - for i, s := range f.servers { - cls[i] = s + if f.Server != nil { + err = f.Server.Shutdown() } - err = frontend.CloseGroup(cls) }) return } -func httpParamsToRouteParams(in httprouter.Params) (out bittorrent.RouteParams) { - out = make([]bittorrent.RouteParam, 0, len(in)) - for _, p := range in { - out = append(out, bittorrent.RouteParam{Key: p.Key, Value: p.Value}) - } - return -} - // announceRoute parses and responds to an Announce. -func (f *httpFE) announceRoute(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { +func (f *httpFE) announceRoute(reqCtx *fasthttp.RequestCtx) { var err error var start time.Time var addr netip.Addr - var req *bittorrent.AnnounceRequest + var aReq *bittorrent.AnnounceRequest if f.collectTimings && metrics.Enabled() { start = time.Now() defer func() { @@ -228,34 +239,32 @@ func (f *httpFE) announceRoute(w http.ResponseWriter, r *http.Request, ps httpro }() } - req, err = ParseAnnounce(r, f.ParseOptions) + aReq, err = ParseAnnounce(reqCtx, f.ParseOptions) if err != nil { - WriteError(w, err) + writeErrorResponse(reqCtx, err) return } - addr = req.GetFirst() + addr = aReq.GetFirst() - ctx := bittorrent.InjectRouteParamsToContext(r.Context(), httpParamsToRouteParams(ps)) - ctx, resp, err := f.logic.HandleAnnounce(ctx, req) + ctx := bittorrent.InjectRouteParamsToContext(reqCtx, nil) + ctx, aResp, err := f.logic.HandleAnnounce(ctx, aReq) if err != nil { - WriteError(w, err) + writeErrorResponse(reqCtx, err) return } - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - err = WriteAnnounceResponse(w, resp) - if err != nil { - WriteError(w, err) - return - } + reqCtx.Response.Header.Set("Content-Type", "text/plain; charset=utf-8") + writeAnnounceResponse(reqCtx, aResp) // next actions are background and should not be canceled after http writer closed ctx = bittorrent.RemapRouteParamsToBgContext(ctx) - go f.logic.AfterAnnounce(ctx, req, resp) + // params mapped from fasthttp.QueryArgs will in the next request + aReq.Params = nil + go f.logic.AfterAnnounce(ctx, aReq, aResp) } // scrapeRoute parses and responds to a Scrape. -func (f *httpFE) scrapeRoute(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { +func (f *httpFE) scrapeRoute(reqCtx *fasthttp.RequestCtx) { var err error var start time.Time var addr netip.Addr @@ -266,47 +275,42 @@ func (f *httpFE) scrapeRoute(w http.ResponseWriter, r *http.Request, ps httprout }() } - req, err := ParseScrape(r, f.ParseOptions) + req, err := ParseScrape(reqCtx, f.ParseOptions) if err != nil { - WriteError(w, err) + writeErrorResponse(reqCtx, err) return } addr = req.GetFirst() - ctx := bittorrent.InjectRouteParamsToContext(r.Context(), httpParamsToRouteParams(ps)) + ctx := bittorrent.InjectRouteParamsToContext(reqCtx, nil) ctx, resp, err := f.logic.HandleScrape(ctx, req) if err != nil { - WriteError(w, err) + writeErrorResponse(reqCtx, err) return } - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - err = WriteScrapeResponse(w, resp) - if err != nil { - WriteError(w, err) - return - } + reqCtx.Response.Header.Set("Content-Type", "text/plain; charset=utf-8") + writeScrapeResponse(reqCtx, resp) // next actions are background and should not be canceled after http writer closed ctx = bittorrent.RemapRouteParamsToBgContext(ctx) + // params mapped from fasthttp.QueryArgs will in the next request + req.Params = nil go f.logic.AfterScrape(ctx, req, resp) } -func (f *httpFE) ping(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { +func (f *httpFE) ping(ctx *fasthttp.RequestCtx) { var err error status := http.StatusOK - ctx := r.Context() - if r.Method == http.MethodGet { - err = f.logic.Ping(ctx) - } + err = f.logic.Ping(ctx) if err != nil { logger.Error().Err(err).Msg("ping completed with error") status = http.StatusServiceUnavailable } if ctxErr := ctx.Err(); ctxErr == nil { - w.WriteHeader(status) + ctx.SetStatusCode(status) } else { - logger.Info().Err(ctxErr).Str("ip", r.RemoteAddr).Msg("ping request cancelled") + logger.Info().Err(ctxErr).Stringer("addr", ctx.RemoteAddr()).Msg("ping request cancelled") } } diff --git a/frontend/http/frontend_test.go b/frontend/http/frontend_test.go new file mode 100644 index 0000000..5652786 --- /dev/null +++ b/frontend/http/frontend_test.go @@ -0,0 +1,131 @@ +package http + +import ( + cr "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "github.com/sot-tech/mochi/bittorrent" + "github.com/sot-tech/mochi/middleware" + "github.com/sot-tech/mochi/pkg/log" + "io" + "math/rand" + "net/http" + "net/url" + "strings" + "testing" +) + +const iterations = 10000 + +var ( + addr = fmt.Sprintf("127.0.0.1:%d", rand.Int63n(10000)+16384) + hashes = make([]string, 10) + peers = make([]string, 10) +) + +func init() { + _ = log.ConfigureLogger("", "error", false, false) + for i := range hashes { + var bb []byte + if rand.Int()%2 == 0 { + bb = make([]byte, bittorrent.InfoHashV1Len) + } else { + bb = make([]byte, bittorrent.InfoHashV2Len) + } + if _, err := cr.Read(bb); err != nil { + panic(err) + } + hashes[i] = hex.EncodeToString(bb) + } + + for i := range peers { + bb := make([]byte, bittorrent.PeerIDLen) + if _, err := cr.Read(bb); err != nil { + panic(err) + } + peers[i] = string(bb) + } + _, err := NewFrontend(map[string]any{ + "addr": addr, + "enable_keepalive": true, + "ping_routes": []string{"ping"}, + }, &middleware.Logic{}) + if err != nil { + panic(err) + } +} + +func runGet(u string, checkResponse bool) (err error) { + var r *http.Response + if r, err = http.Get(u); err == nil { + if r.StatusCode < 400 { + if checkResponse { + var out []byte + if out, err = io.ReadAll(r.Body); err == nil { + sout := string(out) + if strings.Contains(sout, "failure reason") { + return errors.New(sout) + } + } + } else { + _ = r.Body.Close() + } + } else { + return errors.New(r.Status) + } + } + return +} + +func BenchmarkPing(b *testing.B) { + u := url.URL{ + Scheme: "http", + Host: addr, + Path: "ping", + } + us := u.String() + for i := 0; i < iterations; i++ { + if err := runGet(us, false); err != nil { + b.Error(err) + } + } +} + +func BenchmarkAnnounce(b *testing.B) { + for i := 0; i < iterations; i++ { + u := url.URL{ + Scheme: "http", + Host: addr, + Path: defaultAnnounceRoute, + RawQuery: url.Values{ + "event": []string{bittorrent.StartedStr}, + "compact": []string{"1"}, + "left": []string{"100"}, + "downloaded": []string{"0"}, + "uploaded": []string{"0"}, + "numwant": []string{"1"}, + "port": []string{"12345"}, + "info_hash": []string{hashes[rand.Intn(len(hashes))]}, + "peer_id": []string{peers[rand.Intn(len(peers))]}, + }.Encode(), + } + if err := runGet(u.String(), true); err != nil { + b.Error(err) + } + } +} + +func BenchmarkScrape(b *testing.B) { + for i := 0; i < iterations; i++ { + u := url.URL{ + Scheme: "http", + Host: addr, + Path: defaultScrapeRoute, + RawQuery: url.Values{"info_hash": hashes[:len(hashes)/2]}.Encode(), + } + if err := runGet(u.String(), true); err != nil { + b.Error(err) + } + } +} diff --git a/frontend/http/params.go b/frontend/http/params.go new file mode 100644 index 0000000..fde8aa7 --- /dev/null +++ b/frontend/http/params.go @@ -0,0 +1,66 @@ +package http + +import ( + "bytes" + "github.com/rs/zerolog" + "github.com/sot-tech/mochi/bittorrent" + "github.com/valyala/fasthttp" +) + +// queryParams parses a URL Query and implements the Params interface with some +// additional helpers. +type queryParams struct { + *fasthttp.Args +} + +// parseURLData parses a request URL or UDP URLData as defined in BEP41. +// It expects a concatenated string of the request's path and query parts as +// defined in RFC 3986. As both the udp: and http: scheme used by BitTorrent +// include an authority part the path part must always begin with a slash. +// An example of the expected URLData would be "/announce?port=1234&uploaded=0" +// or "/?auth=0x1337". +// HTTP servers should pass (*http.Request).RequestURI, UDP servers should +// pass the concatenated, unchanged URLData as defined in BEP41. +// +// Note that, in the case of a key occurring multiple times in the query, only +// the last value for that key is kept. +// The only exception to this rule is the key "info_hash" which will attempt to +// parse each value as an InfoHash and return an error if parsing fails. All +// InfoHashes are collected and can later be retrieved by calling the InfoHashes +// method. +// +// Also note that any error that is encountered during parsing is returned as a +// ClientError, as this method is expected to be used to parse client-provided +// data. +func parseURLData(urlData []byte) (*queryParams, error) { + i := bytes.IndexByte(urlData, '?') + if i >= 0 { + urlData = urlData[i+1:] + } + q := &queryParams{new(fasthttp.Args)} + q.ParseBytes(urlData) + return q, nil +} + +// String returns a string parsed from a query. Every key can be returned as a +// string because they are encoded in the URL as strings. +func (qp queryParams) String(key string) (string, bool) { + v := qp.Peek(key) + return string(v), v != nil +} + +// InfoHashes returns a list of requested infohashes. +func (qp queryParams) InfoHashes() bittorrent.InfoHashes { + var ihs bittorrent.InfoHashes + for _, bb := range qp.PeekMulti("info_hash") { + if ih, err := bittorrent.NewInfoHash(bb); err == nil { + ihs = append(ihs, ih) + } + } + return ihs +} + +// MarshalZerologObject writes fields into zerolog event +func (qp queryParams) MarshalZerologObject(e *zerolog.Event) { + e.Stringer("query", qp.Args) +} diff --git a/bittorrent/params_test.go b/frontend/http/params_test.go similarity index 68% rename from bittorrent/params_test.go rename to frontend/http/params_test.go index dced055..2b83c01 100644 --- a/bittorrent/params_test.go +++ b/frontend/http/params_test.go @@ -1,4 +1,4 @@ -package bittorrent +package http import ( "net/url" @@ -24,34 +24,34 @@ var ( {"peer_id": {""}, "compact": {""}}, } - InvalidQueries = []string{ - "/announce?" + "info_hash=%0%a", + InvalidQueries = [][]byte{ + []byte("/announce?info_hash=%0%a"), } // See https://github.com/chihaya/chihaya/issues/334. - shouldNotPanicQueries = []string{ - "/annnounce?" + "info_hash=" + testPeerID + "&a", - "/annnounce?" + "info_hash=" + testPeerID + "&=b?", + shouldNotPanicQueries = [][]byte{ + []byte("/annnounce?info_hash=" + testPeerID + "&a"), + []byte("/annnounce?info_hash=" + testPeerID + "&=b?"), } ) -func mapArrayEqual(boxed map[string][]string, unboxed map[string]string) bool { - if len(boxed) != len(unboxed) { - return false +func mapArrayEqual(boxed map[string][]string, unboxed *queryParams) (bool, string) { + if len(boxed) != unboxed.Len() { + return false, "" } for mapKey, mapVal := range boxed { // Always expect box to hold only one element - if len(mapVal) != 1 || mapVal[0] != unboxed[mapKey] { - return false + if len(mapVal) != 1 || mapVal[0] != string(unboxed.Peek(mapKey)) { + return false, mapVal[0] } } - return true + return true, "" } func TestParseEmptyURLData(t *testing.T) { - parsedQuery, err := ParseURLData("") + parsedQuery, err := parseURLData(nil) if err != nil { t.Fatal(err) } @@ -62,49 +62,32 @@ func TestParseEmptyURLData(t *testing.T) { func TestParseValidURLData(t *testing.T) { for parseIndex, parseVal := range ValidAnnounceArguments { - parsedQueryObj, err := ParseURLData("/announce?" + parseVal.Encode()) + parsedQueryObj, err := parseURLData([]byte("/announce?" + parseVal.Encode())) if err != nil { t.Fatal(err) } - if !mapArrayEqual(parseVal, parsedQueryObj.params) { - t.Fatalf("Incorrect parse at item %d.\n Expected=%v\n Received=%v\n", parseIndex, parseVal, parsedQueryObj.params) - } - - if parsedQueryObj.path != "/announce" { - t.Fatalf("Incorrect path, expected %q, got %q", "/announce", parsedQueryObj.path) - } - } -} - -func TestParseInvalidURLData(t *testing.T) { - for parseIndex, parseStr := range InvalidQueries { - parsedQueryObj, err := ParseURLData(parseStr) - if err == nil { - t.Fatal("Should have produced error", parseIndex) - } - - if parsedQueryObj != nil { - t.Fatal("Should be nil after error", parsedQueryObj, parseIndex) + if eq, exp := mapArrayEqual(parseVal, parsedQueryObj); !eq { + t.Fatalf("Incorrect parse at item %d.\n Expected=%v\n Received=%v\n", parseIndex, parseVal, exp) } } } func TestParseShouldNotPanicURLData(t *testing.T) { for _, parseStr := range shouldNotPanicQueries { - _, _ = ParseURLData(parseStr) + _, _ = parseURLData(parseStr) } } func BenchmarkParseQuery(b *testing.B) { - announceStrings := make([]string, 0) + announceStrings := make([][]byte, 0) for i := range ValidAnnounceArguments { - announceStrings = append(announceStrings, ValidAnnounceArguments[i].Encode()) + announceStrings = append(announceStrings, []byte(ValidAnnounceArguments[i].Encode())) } b.ResetTimer() for bCount := 0; bCount < b.N; bCount++ { i := bCount % len(announceStrings) - parsedQueryObj, err := parseQuery(announceStrings[i]) + parsedQueryObj, err := parseURLData(announceStrings[i]) if err != nil { b.Error(err, i) b.Log(parsedQueryObj) diff --git a/frontend/http/parser.go b/frontend/http/parser.go index 4247259..c3e5b35 100644 --- a/frontend/http/parser.go +++ b/frontend/http/parser.go @@ -1,13 +1,12 @@ package http import ( + "bytes" "errors" - "net/http" - "net/netip" - "strings" - "github.com/sot-tech/mochi/bittorrent" "github.com/sot-tech/mochi/frontend" + "github.com/valyala/fasthttp" + "net/netip" ) // ParseOptions is the configuration used to parse an Announce Request. @@ -31,16 +30,14 @@ var ( ) // ParseAnnounce parses an bittorrent.AnnounceRequest from an http.Request. -func ParseAnnounce(r *http.Request, opts ParseOptions) (*bittorrent.AnnounceRequest, error) { - qp, err := bittorrent.ParseURLData(r.RequestURI) - if err != nil { - return nil, err - } +func ParseAnnounce(r *fasthttp.RequestCtx, opts ParseOptions) (*bittorrent.AnnounceRequest, error) { + qp := &queryParams{r.QueryArgs()} request := &bittorrent.AnnounceRequest{Params: qp} // Attempt to parse the event from the request. var eventStr string + var err error eventStr, request.EventProvided = qp.String("event") if request.EventProvided { if request.Event, err = bittorrent.NewEvent(eventStr); err != nil { @@ -50,10 +47,6 @@ func ParseAnnounce(r *http.Request, opts ParseOptions) (*bittorrent.AnnounceRequ request.Event = bittorrent.None } - // Determine if the client expects a compact response. - compactStr, _ := qp.String("compact") - request.Compact = compactStr != "" && compactStr != "0" - // Parse the info hash from the request. infoHashes := qp.InfoHashes() if len(infoHashes) < 1 { @@ -62,50 +55,53 @@ func ParseAnnounce(r *http.Request, opts ParseOptions) (*bittorrent.AnnounceRequ if len(infoHashes) > 1 { return nil, errMultipleInfoHashes } + //FIXME: make sure that we have a copy of InfoHash request.InfoHash = infoHashes[0] + // Determine if the client expects a compact response. + request.Compact = qp.GetBool("compact") + // Parse the PeerID from the request. - peerID, ok := qp.String("peer_id") - if !ok { - return nil, errInvalidPeerID - } - request.ID, err = bittorrent.NewPeerID([]byte(peerID)) + request.ID, err = bittorrent.NewPeerID(qp.Peek("peer_id")) if err != nil { return nil, errInvalidPeerID } // Determine the number of remaining bytes for the client. - request.Left, err = qp.Uint("left", 64) + n, err := qp.GetUint("left") if err != nil { return nil, errInvalidParameterLeft } + request.Left = uint64(n) // Determine the number of bytes downloaded by the client. - request.Downloaded, err = qp.Uint("downloaded", 64) + n, err = qp.GetUint("downloaded") if err != nil { return nil, errInvalidParameterDownloaded } + request.Downloaded = uint64(n) // Determine the number of bytes shared by the client. - request.Uploaded, err = qp.Uint("uploaded", 64) + n, err = qp.GetUint("uploaded") if err != nil { return nil, errInvalidParameterUploaded } + request.Uploaded = uint64(n) // Determine the number of peers the client wants in the response. - numWant, err := qp.Uint("numwant", 32) - if err != nil && !errors.Is(err, bittorrent.ErrKeyNotFound) { + n, err = qp.GetUint("numwant") + if err != nil && !errors.Is(err, fasthttp.ErrNoArgValue) { return nil, errInvalidParameterNumWant } // If there were no errors, the user actually provided the numWant. request.NumWantProvided = err == nil - request.NumWant = uint32(numWant) + request.NumWant = uint32(n) // Parse the port where the client is listening. - port, err := qp.Uint("port", 16) + n, err = qp.GetUint("port") if err != nil { return nil, bittorrent.ErrInvalidPort } - request.Port = uint16(port) + request.Port = uint16(n) // Parse the IP address where the client is listening. request.RequestAddresses = requestedIPs(r, qp, opts) @@ -118,11 +114,8 @@ func ParseAnnounce(r *http.Request, opts ParseOptions) (*bittorrent.AnnounceRequ } // ParseScrape parses an bittorrent.ScrapeRequest from an http.Request. -func ParseScrape(r *http.Request, opts ParseOptions) (*bittorrent.ScrapeRequest, error) { - qp, err := bittorrent.ParseURLData(r.RequestURI) - if err != nil { - return nil, err - } +func ParseScrape(r *fasthttp.RequestCtx, opts ParseOptions) (*bittorrent.ScrapeRequest, error) { + qp := &queryParams{r.QueryArgs()} infoHashes := qp.InfoHashes() if len(infoHashes) < 1 { @@ -130,18 +123,19 @@ func ParseScrape(r *http.Request, opts ParseOptions) (*bittorrent.ScrapeRequest, } request := &bittorrent.ScrapeRequest{ + //FIXME: make sure that we have a copy of InfoHashes InfoHashes: infoHashes, Params: qp, RequestAddresses: requestedIPs(r, qp, opts), } - err = bittorrent.SanitizeScrape(request, opts.MaxScrapeInfoHashes, opts.FilterPrivateIPs) + err := bittorrent.SanitizeScrape(request, opts.MaxScrapeInfoHashes, opts.FilterPrivateIPs) return request, err } // requestedIPs determines the IP address for a BitTorrent client request. -func requestedIPs(r *http.Request, p bittorrent.Params, opts ParseOptions) (addresses bittorrent.RequestAddresses) { +func requestedIPs(r *fasthttp.RequestCtx, p *queryParams, opts ParseOptions) (addresses bittorrent.RequestAddresses) { if opts.AllowIPSpoofing { for _, f := range []string{"ip", "ipv4", "ipv6"} { if ipStr, ok := p.String(f); ok { @@ -150,16 +144,16 @@ func requestedIPs(r *http.Request, p bittorrent.Params, opts ParseOptions) (addr } } - if ipValues := r.Header.Values(opts.RealIPHeader); len(ipValues) > 0 && opts.RealIPHeader != "" { + if ipValues := r.Request.Header.PeekAll(opts.RealIPHeader); len(ipValues) > 0 && opts.RealIPHeader != "" { for _, ipStr := range ipValues { - for _, ipStr := range strings.Split(ipStr, ",") { - if ipStr = strings.TrimSpace(ipStr); len(ipStr) > 0 { - addresses.Add(parseRequestAddress(ipStr, false)) + for _, ipStr := range bytes.Split(ipStr, []byte{','}) { + if ipStr = bytes.TrimSpace(ipStr); len(ipStr) > 0 { + addresses.Add(parseRequestAddress(string(ipStr), false)) } } } } else { - addrPort, _ := netip.ParseAddrPort(r.RemoteAddr) + addrPort, _ := netip.ParseAddrPort(r.RemoteAddr().String()) addresses.Add(bittorrent.RequestAddress{ Addr: addrPort.Addr(), Provided: false, diff --git a/frontend/http/writer.go b/frontend/http/writer.go index 3b6641c..ef6cde5 100644 --- a/frontend/http/writer.go +++ b/frontend/http/writer.go @@ -3,8 +3,9 @@ package http import ( "bytes" "errors" + "github.com/valyala/fasthttp" + "io" "net" - "net/http" "strconv" "time" @@ -14,21 +15,18 @@ import ( var respBufferPool = bytepool.NewBufferPool() -// WriteError communicates an error to a BitTorrent client over HTTP. -func WriteError(w http.ResponseWriter, err error) { +func writeErrorResponse(w io.StringWriter, err error) { message := "internal server error" var clientErr bittorrent.ClientError if errors.As(err, &clientErr) { message = clientErr.Error() } else { - logger.Error().Err(err).Msg("http: internal error") + logger.Error().Err(err).Msg("internal error") } - _, _ = w.Write([]byte("d14:failure reason" + strconv.Itoa(len(message)) + ":" + message + "e")) + _, _ = w.WriteString("d14:failure reason" + strconv.Itoa(len(message)) + ":" + message + "e") } -// WriteAnnounceResponse communicates the results of an Announce to a -// BitTorrent client over HTTP. -func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceResponse) error { +func writeAnnounceResponse(w io.Writer, resp *bittorrent.AnnounceResponse) { bb := respBufferPool.Get() defer respBufferPool.Put(bb) @@ -40,13 +38,13 @@ func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceRespo } bb.WriteString("d8:completei") - bb.WriteString(strconv.FormatUint(uint64(resp.Complete), 10)) + bb.Write(fasthttp.AppendUint(nil, int(resp.Complete))) bb.WriteString("e10:incompletei") - bb.WriteString(strconv.FormatUint(uint64(resp.Incomplete), 10)) + bb.Write(fasthttp.AppendUint(nil, int(resp.Incomplete))) bb.WriteString("e8:intervali") - bb.WriteString(strconv.FormatUint(uint64(resp.Interval), 10)) + bb.Write(fasthttp.AppendUint(nil, int(resp.Interval))) bb.WriteString("e12:min intervali") - bb.WriteString(strconv.FormatUint(uint64(resp.MinInterval), 10)) + bb.Write(fasthttp.AppendUint(nil, int(resp.MinInterval))) bb.WriteByte('e') // Add the peers to the dictionary in the compact format. @@ -68,8 +66,7 @@ func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceRespo } bb.WriteByte('e') - _, err := bb.WriteTo(w) - return err + _, _ = bb.WriteTo(w) } func compactAddresses(bb *bytes.Buffer, peers bittorrent.Peers, v6 bool) { @@ -80,13 +77,12 @@ func compactAddresses(bb *bytes.Buffer, peers bittorrent.Peers, v6 bool) { key, al = "6:peers6", net.IPv6len } bb.WriteString(key) - bb.WriteString(strconv.Itoa((al + 2) * l)) + bb.Write(fasthttp.AppendUint(nil, (al+2)*l)) bb.WriteByte(':') for _, peer := range peers { bb.Write(peer.Addr().AsSlice()) port := peer.Port() - bb.WriteByte(byte(port >> 8)) - bb.WriteByte(byte(port)) + bb.Write([]byte{byte(port >> 8), byte(port)}) } } } @@ -94,35 +90,32 @@ func compactAddresses(bb *bytes.Buffer, peers bittorrent.Peers, v6 bool) { func dictAddress(bb *bytes.Buffer, peer bittorrent.Peer) { bb.WriteString("d2:ip") addr := peer.Addr().String() - bb.WriteString(strconv.Itoa(len(addr))) + bb.Write(fasthttp.AppendUint(nil, len(addr))) bb.WriteByte(':') bb.WriteString(addr) bb.WriteString("7:peer id20:") bb.Write(peer.ID[:]) bb.WriteString("4:porti") - bb.WriteString(strconv.FormatUint(uint64(peer.Port()), 10)) - bb.WriteString("ee") + bb.Write(fasthttp.AppendUint(nil, int(peer.Port()))) + bb.Write([]byte{'e', 'e'}) } -// WriteScrapeResponse communicates the results of a Scrape to a BitTorrent -// client over HTTP. -func WriteScrapeResponse(w http.ResponseWriter, resp *bittorrent.ScrapeResponse) error { +func writeScrapeResponse(w io.Writer, resp *bittorrent.ScrapeResponse) { bb := respBufferPool.Get() defer respBufferPool.Put(bb) bb.WriteString("d5:filesd") for _, scrape := range resp.Files { - bb.WriteString(strconv.Itoa(len(scrape.InfoHash))) + bb.Write(fasthttp.AppendUint(nil, len(scrape.InfoHash))) bb.WriteByte(':') bb.Write([]byte(scrape.InfoHash)) bb.WriteString("d8:completei") - bb.WriteString(strconv.FormatUint(uint64(scrape.Complete), 10)) + bb.Write(fasthttp.AppendUint(nil, int(scrape.Complete))) bb.WriteString("e10:downloadedi") - bb.WriteString(strconv.FormatUint(uint64(scrape.Snatches), 10)) + bb.Write(fasthttp.AppendUint(nil, int(scrape.Snatches))) bb.WriteString("e10:incompletei") - bb.WriteString(strconv.FormatUint(uint64(scrape.Incomplete), 10)) + bb.Write(fasthttp.AppendUint(nil, int(scrape.Incomplete))) bb.WriteString("ee") } - bb.WriteString("ee") - _, err := bb.WriteTo(w) - return err + bb.Write([]byte{'e', 'e'}) + _, _ = bb.WriteTo(w) } diff --git a/frontend/http/writer_test.go b/frontend/http/writer_test.go index bb5cb6a..69228db 100644 --- a/frontend/http/writer_test.go +++ b/frontend/http/writer_test.go @@ -26,7 +26,7 @@ func TestWriteError(t *testing.T) { for _, tt := range table { t.Run(fmt.Sprintf("%s expecting %s", tt.reason, tt.expected), func(t *testing.T) { r := httptest.NewRecorder() - WriteError(r, bittorrent.ClientError(tt.reason)) + writeErrorResponse(r, bittorrent.ClientError(tt.reason)) require.Equal(t, r.Body.String(), tt.expected) }) } @@ -42,7 +42,7 @@ func TestWriteStatus(t *testing.T) { for _, tt := range table { t.Run(fmt.Sprintf("%s expecting %s", tt.reason, tt.expected), func(t *testing.T) { r := httptest.NewRecorder() - WriteError(r, bittorrent.ClientError(tt.reason)) + writeErrorResponse(r, bittorrent.ClientError(tt.reason)) require.Equal(t, r.Body.String(), tt.expected) }) } diff --git a/frontend/options.go b/frontend/options.go index c6ca37a..3af4b7b 100644 --- a/frontend/options.go +++ b/frontend/options.go @@ -2,18 +2,12 @@ package frontend import ( "errors" - "net" - "time" - "github.com/libp2p/go-reuseport" "github.com/sot-tech/mochi/pkg/log" + "net" ) -const ( - defaultReadTimeout = 2 * time.Second - defaultWriteTimeout = 2 * time.Second - defaultListenAddress = ":6969" -) +const defaultListenAddress = ":6969" var errUnexpectedListenerType = errors.New("unexpected listener type") @@ -22,14 +16,12 @@ type ListenOptions struct { Addr string ReusePort bool `cfg:"reuse_port"` Workers uint - ReadTimeout time.Duration `cfg:"read_timeout"` - WriteTimeout time.Duration `cfg:"write_timeout"` - EnableRequestTiming bool `cfg:"enable_request_timing"` + EnableRequestTiming bool `cfg:"enable_request_timing"` } // Validate checks if listen address provided and sets default // timeout options if needed -func (lo ListenOptions) Validate(ignoreTimeouts bool, logger *log.Logger) (validOptions ListenOptions) { +func (lo ListenOptions) Validate(logger *log.Logger) (validOptions ListenOptions) { validOptions = lo if len(lo.Addr) == 0 { validOptions.Addr = defaultListenAddress @@ -39,32 +31,6 @@ func (lo ListenOptions) Validate(ignoreTimeouts bool, logger *log.Logger) (valid Str("default", validOptions.Addr). Msg("falling back to default configuration") } - if lo.Workers == 0 { - validOptions.Workers = 1 - } - if lo.Workers > 1 && !lo.ReusePort { - validOptions.ReusePort = true - logger.Warn().Msg("forcibly enabling ReusePort because Workers > 1") - } - if !ignoreTimeouts { - if lo.ReadTimeout <= 0 { - validOptions.ReadTimeout = defaultReadTimeout - logger.Warn(). - Str("name", "ReadTimeout"). - Dur("provided", lo.ReadTimeout). - Dur("default", validOptions.ReadTimeout). - Msg("falling back to default configuration") - } - - if lo.WriteTimeout <= 0 { - validOptions.WriteTimeout = defaultWriteTimeout - logger.Warn(). - Str("name", "WriteTimeout"). - Dur("provided", lo.WriteTimeout). - Dur("default", validOptions.WriteTimeout). - Msg("falling back to default configuration") - } - } return } diff --git a/frontend/udp/frontend.go b/frontend/udp/frontend.go index 6ee2360..cce7ed3 100644 --- a/frontend/udp/frontend.go +++ b/frontend/udp/frontend.go @@ -54,7 +54,15 @@ type Config struct { // default values replacing anything that is invalid. func (cfg Config) Validate() (validCfg Config) { validCfg = cfg - validCfg.ListenOptions = cfg.ListenOptions.Validate(true, logger) + validCfg.ListenOptions = cfg.ListenOptions.Validate(logger) + + if cfg.Workers == 0 { + cfg.Workers = 1 + } + if cfg.Workers > 1 && !cfg.ReusePort { + cfg.ReusePort = true + logger.Warn().Msg("forcibly enabling ReusePort because Workers > 1") + } // Generate a private key if one isn't provided by the user. if cfg.PrivateKey == "" { diff --git a/go.mod b/go.mod index fd33b1f..a3ba01b 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,12 @@ module github.com/sot-tech/mochi go 1.19 require ( - code.cloudfoundry.org/go-diodes v0.0.0-20230222233121-2712486f12be + code.cloudfoundry.org/go-diodes v0.0.0-20230317203753-49f1af6d2f1a github.com/MicahParks/keyfunc v1.9.0 github.com/anacrolix/torrent v1.48.0 github.com/cespare/xxhash/v2 v2.2.0 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/jackc/pgx/v5 v5.3.1 - github.com/julienschmidt/httprouter v1.3.0 github.com/libp2p/go-reuseport v0.2.0 github.com/minio/sha256-simd v1.0.0 github.com/mitchellh/mapstructure v1.5.0 @@ -17,6 +16,7 @@ require ( github.com/redis/go-redis/v9 v9.0.2 github.com/rs/zerolog v1.29.0 github.com/stretchr/testify v1.8.2 + github.com/valyala/fasthttp v1.45.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -25,31 +25,34 @@ require ( github.com/anacrolix/log v0.13.2-0.20221123232138-02e2764801c3 // indirect github.com/anacrolix/missinggo v1.3.0 // indirect github.com/anacrolix/missinggo/v2 v2.7.1 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/pprof v0.0.0-20230309165930-d61513b1440d // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.0 // indirect + github.com/klauspost/compress v1.16.3 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/onsi/ginkgo/v2 v2.8.4 // indirect + github.com/onsi/ginkgo/v2 v2.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.41.0 // indirect + github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect - golang.org/x/crypto v0.6.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + golang.org/x/crypto v0.7.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect - golang.org/x/tools v0.6.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + golang.org/x/tools v0.7.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect ) diff --git a/go.sum b/go.sum index 552b8fd..c47f4d5 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -code.cloudfoundry.org/go-diodes v0.0.0-20230222233121-2712486f12be h1:giafIIx83xPMKSFXt/RsSiZz3dGmENtrs+1DPTrwOqM= -code.cloudfoundry.org/go-diodes v0.0.0-20230222233121-2712486f12be/go.mod h1:7XxQ+IiO+UoOqgMzMXCQ5t0kvlEEq7QXaA7evlE20cU= +code.cloudfoundry.org/go-diodes v0.0.0-20230317203753-49f1af6d2f1a h1:ci46gsKkiAiqv9LjVgbVWRZer1Z5zxbQt1IGMkCjksE= +code.cloudfoundry.org/go-diodes v0.0.0-20230317203753-49f1af6d2f1a/go.mod h1:XfKqABWo2YXnsg9cp39TNwzSj0xPx/k2SbgNPE5Fgpc= crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -42,6 +42,8 @@ github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pm github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8= github.com/anacrolix/torrent v1.48.0 h1:OQe1aQb8WnhDzpcI7r3yWoHzHWKyPbfhXGfO9Q/pvbY= github.com/anacrolix/torrent v1.48.0/go.mod h1:3UtkJ8BnxXDRwvk+eT+uwiZalfFJ8YzAhvxe4QRPSJI= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -110,8 +112,8 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -124,8 +126,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso= -github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= +github.com/google/pprof v0.0.0-20230309165930-d61513b1440d h1:um9/pc7tKMINFfP1eE7Wv6PRGXlcCSJkVajF7KJw3uQ= +github.com/google/pprof v0.0.0-20230309165930-d61513b1440d/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -152,9 +154,9 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= +github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= @@ -192,10 +194,10 @@ github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOl github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo/v2 v2.8.4 h1:gf5mIQ8cLFieruNLAdgijHF1PYfLphKm2dxxcUtcqK0= -github.com/onsi/ginkgo/v2 v2.8.4/go.mod h1:427dEDQZkDKsBvCjc2A/ZPefhKxsTTrsQegMlayL730= +github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= +github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754= +github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -220,8 +222,8 @@ github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3d github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.41.0 h1:npo01n6vUlRViIj5fgwiK8vlNIh8bnoxqh3gypKsyAw= -github.com/prometheus/common v0.41.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -261,6 +263,10 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.45.0 h1:zPkkzpIn8tdHZUrVa6PzYd0i5verqiPSkgTd3bSUcpA= +github.com/valyala/fasthttp v1.45.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= @@ -268,8 +274,8 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -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.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -285,7 +291,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -311,20 +317,20 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -342,8 +348,8 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/middleware/jwt/jwt_test.go b/middleware/jwt/jwt_test.go index f2b7997..f54a94d 100644 --- a/middleware/jwt/jwt_test.go +++ b/middleware/jwt/jwt_test.go @@ -61,14 +61,6 @@ func (p params) String(key string) (out string, found bool) { return } -func (params) RawPath() (s string) { - return -} - -func (params) RawQuery() (s string) { - return -} - func (params) MarshalZerologObject(*zerolog.Event) {} func init() { diff --git a/pkg/log/log.go b/pkg/log/log.go index 798e07e..5df6a62 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -21,15 +21,11 @@ import ( var ( root = zl.Logger - rootWg = sync.WaitGroup{} + rootMu = sync.Mutex{} customOut io.WriteCloser customOutMu = sync.Mutex{} ) -func init() { - rootWg.Add(1) -} - // ConfigureLogger initializes root and all child loggers. // NOTE: this function MUST be called before any child log call // @@ -70,9 +66,10 @@ func ConfigureLogger(output, level string, formatted, colored bool) (err error) return err } } + rootMu.Lock() + defer rootMu.Unlock() root = zerolog.New(w).With().Timestamp().Logger() zerolog.SetGlobalLevel(lvl) - rootWg.Done() return nil } @@ -87,7 +84,6 @@ type Logger struct { func (l *Logger) init() { l.zlOnce.Do(func() { - rootWg.Wait() l.Logger = root.With().Str("component", l.comp).Logger() }) } From 24c15395833e5f882fd81590ed528e29e9ce1ed7 Mon Sep 17 00:00:00 2001 From: "Lawrence, Rendall" Date: Sat, 18 Mar 2023 19:38:14 +0300 Subject: [PATCH 2/6] restore udp queryParams implementation * fix lint warnings --- bittorrent/params.go | 1 + frontend/http/frontend_test.go | 8 ++- frontend/http/params.go | 30 -------- frontend/http/params_test.go | 36 ++++++++-- frontend/http/parser.go | 8 ++- frontend/http/writer.go | 3 +- frontend/options.go | 6 +- frontend/udp/params.go | 98 ++++++++++++++++++++++++++ frontend/udp/params_test.go | 125 +++++++++++++++++++++++++++++++++ frontend/udp/parser.go | 8 ++- 10 files changed, 277 insertions(+), 46 deletions(-) create mode 100644 frontend/udp/params.go create mode 100644 frontend/udp/params_test.go diff --git a/bittorrent/params.go b/bittorrent/params.go index 00a8773..f97ac7a 100644 --- a/bittorrent/params.go +++ b/bittorrent/params.go @@ -2,6 +2,7 @@ package bittorrent import ( "context" + "github.com/rs/zerolog" ) diff --git a/frontend/http/frontend_test.go b/frontend/http/frontend_test.go index 5652786..0bf4779 100644 --- a/frontend/http/frontend_test.go +++ b/frontend/http/frontend_test.go @@ -5,15 +5,16 @@ import ( "encoding/hex" "errors" "fmt" - "github.com/sot-tech/mochi/bittorrent" - "github.com/sot-tech/mochi/middleware" - "github.com/sot-tech/mochi/pkg/log" "io" "math/rand" "net/http" "net/url" "strings" "testing" + + "github.com/sot-tech/mochi/bittorrent" + "github.com/sot-tech/mochi/middleware" + "github.com/sot-tech/mochi/pkg/log" ) const iterations = 10000 @@ -58,6 +59,7 @@ func init() { func runGet(u string, checkResponse bool) (err error) { var r *http.Response + // nolint:gosec if r, err = http.Get(u); err == nil { if r.StatusCode < 400 { if checkResponse { diff --git a/frontend/http/params.go b/frontend/http/params.go index fde8aa7..3e9f759 100644 --- a/frontend/http/params.go +++ b/frontend/http/params.go @@ -1,7 +1,6 @@ package http import ( - "bytes" "github.com/rs/zerolog" "github.com/sot-tech/mochi/bittorrent" "github.com/valyala/fasthttp" @@ -13,35 +12,6 @@ type queryParams struct { *fasthttp.Args } -// parseURLData parses a request URL or UDP URLData as defined in BEP41. -// It expects a concatenated string of the request's path and query parts as -// defined in RFC 3986. As both the udp: and http: scheme used by BitTorrent -// include an authority part the path part must always begin with a slash. -// An example of the expected URLData would be "/announce?port=1234&uploaded=0" -// or "/?auth=0x1337". -// HTTP servers should pass (*http.Request).RequestURI, UDP servers should -// pass the concatenated, unchanged URLData as defined in BEP41. -// -// Note that, in the case of a key occurring multiple times in the query, only -// the last value for that key is kept. -// The only exception to this rule is the key "info_hash" which will attempt to -// parse each value as an InfoHash and return an error if parsing fails. All -// InfoHashes are collected and can later be retrieved by calling the InfoHashes -// method. -// -// Also note that any error that is encountered during parsing is returned as a -// ClientError, as this method is expected to be used to parse client-provided -// data. -func parseURLData(urlData []byte) (*queryParams, error) { - i := bytes.IndexByte(urlData, '?') - if i >= 0 { - urlData = urlData[i+1:] - } - q := &queryParams{new(fasthttp.Args)} - q.ParseBytes(urlData) - return q, nil -} - // String returns a string parsed from a query. Every key can be returned as a // string because they are encoded in the URL as strings. func (qp queryParams) String(key string) (string, bool) { diff --git a/frontend/http/params_test.go b/frontend/http/params_test.go index 2b83c01..9830fb7 100644 --- a/frontend/http/params_test.go +++ b/frontend/http/params_test.go @@ -1,8 +1,11 @@ package http import ( + "bytes" "net/url" "testing" + + "github.com/valyala/fasthttp" ) var ( @@ -24,10 +27,6 @@ var ( {"peer_id": {""}, "compact": {""}}, } - InvalidQueries = [][]byte{ - []byte("/announce?info_hash=%0%a"), - } - // See https://github.com/chihaya/chihaya/issues/334. shouldNotPanicQueries = [][]byte{ []byte("/annnounce?info_hash=" + testPeerID + "&a"), @@ -50,6 +49,35 @@ func mapArrayEqual(boxed map[string][]string, unboxed *queryParams) (bool, strin return true, "" } +// parseURLData parses a request URL or UDP URLData as defined in BEP41. +// It expects a concatenated string of the request's path and query parts as +// defined in RFC 3986. As both the udp: and http: scheme used by BitTorrent +// include an authority part the path part must always begin with a slash. +// An example of the expected URLData would be "/announce?port=1234&uploaded=0" +// or "/?auth=0x1337". +// HTTP servers should pass (*http.Request).RequestURI, UDP servers should +// pass the concatenated, unchanged URLData as defined in BEP41. +// +// Note that, in the case of a key occurring multiple times in the query, only +// the last value for that key is kept. +// The only exception to this rule is the key "info_hash" which will attempt to +// parse each value as an InfoHash and return an error if parsing fails. All +// InfoHashes are collected and can later be retrieved by calling the InfoHashes +// method. +// +// Also note that any error that is encountered during parsing is returned as a +// ClientError, as this method is expected to be used to parse client-provided +// data. +func parseURLData(urlData []byte) (*queryParams, error) { + i := bytes.IndexByte(urlData, '?') + if i >= 0 { + urlData = urlData[i+1:] + } + q := &queryParams{new(fasthttp.Args)} + q.ParseBytes(urlData) + return q, nil +} + func TestParseEmptyURLData(t *testing.T) { parsedQuery, err := parseURLData(nil) if err != nil { diff --git a/frontend/http/parser.go b/frontend/http/parser.go index c3e5b35..32faa57 100644 --- a/frontend/http/parser.go +++ b/frontend/http/parser.go @@ -3,10 +3,12 @@ package http import ( "bytes" "errors" + "net/netip" + "github.com/sot-tech/mochi/bittorrent" "github.com/sot-tech/mochi/frontend" + "github.com/valyala/fasthttp" - "net/netip" ) // ParseOptions is the configuration used to parse an Announce Request. @@ -55,7 +57,7 @@ func ParseAnnounce(r *fasthttp.RequestCtx, opts ParseOptions) (*bittorrent.Annou if len(infoHashes) > 1 { return nil, errMultipleInfoHashes } - //FIXME: make sure that we have a copy of InfoHash + // FIXME: make sure that we have a copy of InfoHash request.InfoHash = infoHashes[0] // Determine if the client expects a compact response. @@ -123,7 +125,7 @@ func ParseScrape(r *fasthttp.RequestCtx, opts ParseOptions) (*bittorrent.ScrapeR } request := &bittorrent.ScrapeRequest{ - //FIXME: make sure that we have a copy of InfoHashes + // FIXME: make sure that we have a copy of InfoHashes InfoHashes: infoHashes, Params: qp, RequestAddresses: requestedIPs(r, qp, opts), diff --git a/frontend/http/writer.go b/frontend/http/writer.go index ef6cde5..f61e223 100644 --- a/frontend/http/writer.go +++ b/frontend/http/writer.go @@ -3,12 +3,13 @@ package http import ( "bytes" "errors" - "github.com/valyala/fasthttp" "io" "net" "strconv" "time" + "github.com/valyala/fasthttp" + "github.com/sot-tech/mochi/bittorrent" "github.com/sot-tech/mochi/pkg/bytepool" ) diff --git a/frontend/options.go b/frontend/options.go index 3af4b7b..61fc631 100644 --- a/frontend/options.go +++ b/frontend/options.go @@ -2,9 +2,11 @@ package frontend import ( "errors" - "github.com/libp2p/go-reuseport" - "github.com/sot-tech/mochi/pkg/log" "net" + + "github.com/sot-tech/mochi/pkg/log" + + "github.com/libp2p/go-reuseport" ) const defaultListenAddress = ":6969" diff --git a/frontend/udp/params.go b/frontend/udp/params.go new file mode 100644 index 0000000..be73a72 --- /dev/null +++ b/frontend/udp/params.go @@ -0,0 +1,98 @@ +package udp + +import ( + "bytes" + "net/url" + "strings" + + "github.com/rs/zerolog" + + "github.com/sot-tech/mochi/bittorrent" +) + +// ErrInvalidQueryEscape is returned when a query string contains invalid +// escapes. +var ErrInvalidQueryEscape = bittorrent.ClientError("invalid query escape") + +// queryParams parses a URL Query and implements the Params interface +type queryParams struct { + params map[string]string +} + +// parseQuery parses a request URL or UDP URLData as defined in BEP41. +// It expects a concatenated string of the request's path and query parts as +// defined in RFC 3986. As both the udp: and http: scheme used by BitTorrent +// include an authority part the path part must always begin with a slash. +// An example of the expected URLData would be "/announce?port=1234&uploaded=0" +// or "/?auth=0x1337". +// HTTP servers should pass (*http.Request).RequestURI, UDP servers should +// pass the concatenated, unchanged URLData as defined in BEP41. +// +// Note that, in the case of a key occurring multiple times in the query, only +// the last value for that key is kept. +// +// Also note that any error that is encountered during parsing is returned as a +// ClientError, as this method is expected to be used to parse client-provided +// data. +func parseQuery(query []byte) (q *queryParams, err error) { + queryDelim := bytes.IndexRune(query, '?') + if queryDelim != -1 { + query = query[queryDelim+1:] + } + // This is basically url.parseQuery, but with a map[string]string + // instead of map[string][]string for the values. + q = &queryParams{ + params: make(map[string]string), + } + + for len(query) > 0 { + key := query + if i := bytes.IndexAny(key, "&;"); i >= 0 { + key, query = key[:i], key[i+1:] + } else { + query = nil + } + if len(key) == 0 { + continue + } + var value []byte + if i := bytes.IndexRune(key, '='); i >= 0 { + key, value = key[:i], key[i+1:] + } + var k, v string + k, err = url.QueryUnescape(string(key)) + if err != nil { + // QueryUnescape returns an error like "invalid escape: '%x'". + // But frontends record these errors to prometheus, which generates + // a lot of time series. + // We log it here for debugging instead. + return nil, ErrInvalidQueryEscape + } + v, err = url.QueryUnescape(string(value)) + if err != nil { + // QueryUnescape returns an error like "invalid escape: '%x'". + // But frontends record these errors to prometheus, which generates + // a lot of time series. + // We log it here for debugging instead. + return nil, ErrInvalidQueryEscape + } + + q.params[strings.ToLower(k)] = v + } + + return q, nil +} + +// String returns a string parsed from a query. Every key can be returned as a +// string because they are encoded in the URL as strings. +func (qp queryParams) String(key string) (string, bool) { + value, ok := qp.params[strings.ToLower(key)] + return value, ok +} + +// MarshalZerologObject writes fields into zerolog event +func (qp queryParams) MarshalZerologObject(e *zerolog.Event) { + for k, v := range qp.params { + e.Str(k, v) + } +} diff --git a/frontend/udp/params_test.go b/frontend/udp/params_test.go new file mode 100644 index 0000000..266d6f5 --- /dev/null +++ b/frontend/udp/params_test.go @@ -0,0 +1,125 @@ +package udp + +import ( + "net/url" + "testing" +) + +var ( + testPeerID = "-TEST01-6wfG2wk6wWLc" + + ValidAnnounceArguments = []url.Values{ + {}, + {"peer_id": {testPeerID}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}}, + {"peer_id": {testPeerID}, "ip": {"192.168.0.1"}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}}, + {"peer_id": {testPeerID}, "ip": {"192.168.0.1"}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "numwant": {"28"}}, + {"peer_id": {testPeerID}, "ip": {"192.168.0.1"}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "event": {"stopped"}}, + {"peer_id": {testPeerID}, "ip": {"192.168.0.1"}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "event": {"started"}, "numwant": {"13"}}, + {"peer_id": {testPeerID}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "no_peer_id": {"1"}}, + {"peer_id": {testPeerID}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "compact": {"0"}, "no_peer_id": {"1"}}, + {"peer_id": {testPeerID}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "compact": {"0"}, "no_peer_id": {"1"}, "key": {"peerKey"}}, + {"peer_id": {testPeerID}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "compact": {"0"}, "no_peer_id": {"1"}, "key": {"peerKey"}, "trackerid": {"trackerId"}}, + {"peer_id": {"%3Ckey%3A+0x90%3E"}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "compact": {"0"}, "no_peer_id": {"1"}, "key": {"peerKey"}, "trackerid": {"trackerId"}}, + {"peer_id": {"%3Ckey%3A+0x90%3E"}, "compact": {"1"}}, + {"peer_id": {""}, "compact": {""}}, + } + + InvalidQueries = [][]byte{ + []byte("/announce?info_hash=%0%a"), + } + + // See https://github.com/chihaya/chihaya/issues/334. + shouldNotPanicQueries = [][]byte{ + []byte("/annnounce?info_hash=" + testPeerID + "&a"), + []byte("/annnounce?info_hash=" + testPeerID + "&=b?"), + } +) + +func mapArrayEqual(boxed map[string][]string, unboxed map[string]string) bool { + if len(boxed) != len(unboxed) { + return false + } + + for mapKey, mapVal := range boxed { + // Always expect box to hold only one element + if len(mapVal) != 1 || mapVal[0] != unboxed[mapKey] { + return false + } + } + + return true +} + +func TestParseEmptyURLData(t *testing.T) { + parsedQuery, err := parseQuery(nil) + if err != nil { + t.Fatal(err) + } + if parsedQuery == nil { + t.Fatal("Parsed query must not be nil") + } +} + +func TestParseValidURLData(t *testing.T) { + for parseIndex, parseVal := range ValidAnnounceArguments { + parsedQueryObj, err := parseQuery([]byte("/announce?" + parseVal.Encode())) + if err != nil { + t.Fatal(err) + } + + if !mapArrayEqual(parseVal, parsedQueryObj.params) { + t.Fatalf("Incorrect parse at item %d.\n Expected=%v\n Received=%v\n", parseIndex, parseVal, parsedQueryObj.params) + } + } +} + +func TestParseInvalidURLData(t *testing.T) { + for parseIndex, parseStr := range InvalidQueries { + parsedQueryObj, err := parseQuery(parseStr) + if err == nil { + t.Fatal("Should have produced error", parseIndex) + } + + if parsedQueryObj != nil { + t.Fatal("Should be nil after error", parsedQueryObj, parseIndex) + } + } +} + +func TestParseShouldNotPanicURLData(t *testing.T) { + for _, parseStr := range shouldNotPanicQueries { + _, _ = parseQuery(parseStr) + } +} + +func BenchmarkParseQuery(b *testing.B) { + announceStrings := make([][]byte, 0) + for i := range ValidAnnounceArguments { + announceStrings = append(announceStrings, []byte(ValidAnnounceArguments[i].Encode())) + } + b.ResetTimer() + for bCount := 0; bCount < b.N; bCount++ { + i := bCount % len(announceStrings) + parsedQueryObj, err := parseQuery(announceStrings[i]) + if err != nil { + b.Error(err, i) + b.Log(parsedQueryObj) + } + } +} + +func BenchmarkURLParseQuery(b *testing.B) { + announceStrings := make([]string, 0) + for i := range ValidAnnounceArguments { + announceStrings = append(announceStrings, ValidAnnounceArguments[i].Encode()) + } + b.ResetTimer() + for bCount := 0; bCount < b.N; bCount++ { + i := bCount % len(announceStrings) + parsedQueryObj, err := url.ParseQuery(announceStrings[i]) + if err != nil { + b.Error(err, i) + b.Log(parsedQueryObj) + } + } +} diff --git a/frontend/udp/parser.go b/frontend/udp/parser.go index 91e8571..8f36782 100644 --- a/frontend/udp/parser.go +++ b/frontend/udp/parser.go @@ -71,6 +71,7 @@ func ParseAnnounce(r Request, v6Action bool, opts frontend.ParseOptions) (*bitto // XXX: pure V2 hashes will cause invalid parsing, // but BEP-52 says, that V2 hashes SHOULD be truncated + // FIXME: make sure that we have a copy of InfoHash request.InfoHash, err = bittorrent.NewInfoHash(r.Packet[16:36]) if err != nil { return nil, errInvalidInfoHash @@ -116,7 +117,7 @@ func ParseAnnounce(r Request, v6Action bool, opts frontend.ParseOptions) (*bitto // 41 and updates an announce with the values parsed. func handleOptionalParameters(packet []byte) (bittorrent.Params, error) { if len(packet) == 0 { - return bittorrent.ParseURLData("") + return parseQuery(nil) } buf := reqRespBufferPool.Get() @@ -126,7 +127,7 @@ func handleOptionalParameters(packet []byte) (bittorrent.Params, error) { option := packet[i] switch option { case optionEndOfOptions: - return bittorrent.ParseURLData(buf.String()) + return parseQuery(buf.Bytes()) case optionNOP: i++ case optionURLData: @@ -153,7 +154,7 @@ func handleOptionalParameters(packet []byte) (bittorrent.Params, error) { } } - return bittorrent.ParseURLData(buf.String()) + return parseQuery(buf.Bytes()) } // ParseScrape parses a ScrapeRequest from a UDP request. @@ -177,6 +178,7 @@ func ParseScrape(r Request, opts frontend.ParseOptions) (*bittorrent.ScrapeReque var request *bittorrent.ScrapeRequest for len(r.Packet) >= bittorrent.InfoHashV1Len { var ih bittorrent.InfoHash + // FIXME: make sure that we have a copy of InfoHash if ih, err = bittorrent.NewInfoHash(r.Packet[:bittorrent.InfoHashV1Len]); err == nil { infoHashes = append(infoHashes, ih) r.Packet = r.Packet[bittorrent.InfoHashV1Len:] From 34c2921be887308a9b016f8e31a51dbdc6c15697 Mon Sep 17 00:00:00 2001 From: "Lawrence, Rendall" Date: Sun, 19 Mar 2023 18:37:57 +0300 Subject: [PATCH 3/6] add support for `no_peer_id` HTTP announce parameter * remove `compact` from req/resp structures, because it used only in HTTP and only while response write --- bittorrent/request.go | 6 +----- frontend/http/frontend.go | 13 +++++++++---- frontend/http/parser.go | 11 ++++------- frontend/http/writer.go | 16 +++++++++------- frontend/udp/frontend.go | 22 +++++++++++----------- frontend/udp/params.go | 2 +- frontend/udp/parser.go | 8 ++++---- frontend/udp/writer.go | 16 ++++++++-------- middleware/logic.go | 1 - middleware/logic_test.go | 1 - 10 files changed, 47 insertions(+), 49 deletions(-) diff --git a/bittorrent/request.go b/bittorrent/request.go index b5cd4a0..150f87d 100644 --- a/bittorrent/request.go +++ b/bittorrent/request.go @@ -147,7 +147,6 @@ func (rp RequestPeer) MarshalZerologObject(e *zerolog.Event) { type AnnounceRequest struct { Event Event InfoHash InfoHash - Compact bool EventProvided bool NumWantProvided bool NumWant uint32 @@ -163,7 +162,6 @@ type AnnounceRequest struct { func (r AnnounceRequest) MarshalZerologObject(e *zerolog.Event) { e.Stringer("event", r.Event). Stringer("infoHash", r.InfoHash). - Bool("compact", r.Compact). Bool("eventProvided", r.EventProvided). Bool("numWantProvided", r.NumWantProvided). Uint32("numWant", r.NumWant). @@ -177,7 +175,6 @@ func (r AnnounceRequest) MarshalZerologObject(e *zerolog.Event) { // AnnounceResponse represents the parameters used to create an announce // response. type AnnounceResponse struct { - Compact bool Complete uint32 Incomplete uint32 Interval time.Duration @@ -188,8 +185,7 @@ type AnnounceResponse struct { // MarshalZerologObject writes fields into zerolog event func (r AnnounceResponse) MarshalZerologObject(e *zerolog.Event) { - e.Bool("compact", r.Compact). - Uint32("complete", r.Complete). + e.Uint32("complete", r.Complete). Uint32("incomplete", r.Incomplete). Dur("interval", r.Interval). Dur("minInterval", r.MinInterval). diff --git a/frontend/http/frontend.go b/frontend/http/frontend.go index f5e29aa..76279e8 100644 --- a/frontend/http/frontend.go +++ b/frontend/http/frontend.go @@ -239,7 +239,7 @@ func (f *httpFE) announceRoute(reqCtx *fasthttp.RequestCtx) { }() } - aReq, err = ParseAnnounce(reqCtx, f.ParseOptions) + aReq, err = parseAnnounce(reqCtx, f.ParseOptions) if err != nil { writeErrorResponse(reqCtx, err) return @@ -254,11 +254,16 @@ func (f *httpFE) announceRoute(reqCtx *fasthttp.RequestCtx) { } reqCtx.Response.Header.Set("Content-Type", "text/plain; charset=utf-8") - writeAnnounceResponse(reqCtx, aResp) + qArgs := reqCtx.QueryArgs() + // `compact` means that tracker should return addresses in + // binary (single concatenated string) mode instead of dictionary. + // `no_peer_id` means, that tracker may omit PeerID field in response dictionary. + // see https://wiki.theory.org/BitTorrentSpecification#Tracker_Request_Parameters + writeAnnounceResponse(reqCtx, aResp, qArgs.GetBool("compact"), !qArgs.GetBool("no_peer_id")) // next actions are background and should not be canceled after http writer closed ctx = bittorrent.RemapRouteParamsToBgContext(ctx) - // params mapped from fasthttp.QueryArgs will in the next request + // params mapped from fasthttp.QueryArgs will be reused in the next request aReq.Params = nil go f.logic.AfterAnnounce(ctx, aReq, aResp) } @@ -275,7 +280,7 @@ func (f *httpFE) scrapeRoute(reqCtx *fasthttp.RequestCtx) { }() } - req, err := ParseScrape(reqCtx, f.ParseOptions) + req, err := parseScrape(reqCtx, f.ParseOptions) if err != nil { writeErrorResponse(reqCtx, err) return diff --git a/frontend/http/parser.go b/frontend/http/parser.go index 32faa57..6542496 100644 --- a/frontend/http/parser.go +++ b/frontend/http/parser.go @@ -31,8 +31,8 @@ var ( errInvalidParameterNumWant = bittorrent.ClientError("parameter 'num want' invalid or not provided") ) -// ParseAnnounce parses an bittorrent.AnnounceRequest from an http.Request. -func ParseAnnounce(r *fasthttp.RequestCtx, opts ParseOptions) (*bittorrent.AnnounceRequest, error) { +// parseAnnounce parses an bittorrent.AnnounceRequest from an http.Request. +func parseAnnounce(r *fasthttp.RequestCtx, opts ParseOptions) (*bittorrent.AnnounceRequest, error) { qp := &queryParams{r.QueryArgs()} request := &bittorrent.AnnounceRequest{Params: qp} @@ -60,9 +60,6 @@ func ParseAnnounce(r *fasthttp.RequestCtx, opts ParseOptions) (*bittorrent.Annou // FIXME: make sure that we have a copy of InfoHash request.InfoHash = infoHashes[0] - // Determine if the client expects a compact response. - request.Compact = qp.GetBool("compact") - // Parse the PeerID from the request. request.ID, err = bittorrent.NewPeerID(qp.Peek("peer_id")) if err != nil { @@ -115,8 +112,8 @@ func ParseAnnounce(r *fasthttp.RequestCtx, opts ParseOptions) (*bittorrent.Annou return request, err } -// ParseScrape parses an bittorrent.ScrapeRequest from an http.Request. -func ParseScrape(r *fasthttp.RequestCtx, opts ParseOptions) (*bittorrent.ScrapeRequest, error) { +// parseScrape parses an bittorrent.ScrapeRequest from an http.Request. +func parseScrape(r *fasthttp.RequestCtx, opts ParseOptions) (*bittorrent.ScrapeRequest, error) { qp := &queryParams{r.QueryArgs()} infoHashes := qp.InfoHashes() diff --git a/frontend/http/writer.go b/frontend/http/writer.go index f61e223..b384703 100644 --- a/frontend/http/writer.go +++ b/frontend/http/writer.go @@ -27,7 +27,7 @@ func writeErrorResponse(w io.StringWriter, err error) { _, _ = w.WriteString("d14:failure reason" + strconv.Itoa(len(message)) + ":" + message + "e") } -func writeAnnounceResponse(w io.Writer, resp *bittorrent.AnnounceResponse) { +func writeAnnounceResponse(w io.Writer, resp *bittorrent.AnnounceResponse, compact, includePeerID bool) { bb := respBufferPool.Get() defer respBufferPool.Put(bb) @@ -49,7 +49,7 @@ func writeAnnounceResponse(w io.Writer, resp *bittorrent.AnnounceResponse) { bb.WriteByte('e') // Add the peers to the dictionary in the compact format. - if resp.Compact { + if compact { // Add the IPv4 peers to the dictionary. compactAddresses(bb, resp.IPv4Peers, false) // Add the IPv6 peers to the dictionary. @@ -58,10 +58,10 @@ func writeAnnounceResponse(w io.Writer, resp *bittorrent.AnnounceResponse) { // Add the peers to the dictionary. bb.WriteString("5:peersl") for _, peer := range resp.IPv4Peers { - dictAddress(bb, peer) + dictAddress(bb, peer, includePeerID) } for _, peer := range resp.IPv6Peers { - dictAddress(bb, peer) + dictAddress(bb, peer, includePeerID) } bb.WriteByte('e') } @@ -88,14 +88,16 @@ func compactAddresses(bb *bytes.Buffer, peers bittorrent.Peers, v6 bool) { } } -func dictAddress(bb *bytes.Buffer, peer bittorrent.Peer) { +func dictAddress(bb *bytes.Buffer, peer bittorrent.Peer, includePeerID bool) { bb.WriteString("d2:ip") addr := peer.Addr().String() bb.Write(fasthttp.AppendUint(nil, len(addr))) bb.WriteByte(':') bb.WriteString(addr) - bb.WriteString("7:peer id20:") - bb.Write(peer.ID[:]) + if includePeerID { + bb.WriteString("7:peer id20:") + bb.Write(peer.ID[:]) + } bb.WriteString("4:porti") bb.Write(fasthttp.AppendUint(nil, int(peer.Port()))) bb.Write([]byte{'e', 'e'}) diff --git a/frontend/udp/frontend.go b/frontend/udp/frontend.go index cce7ed3..5e61dc1 100644 --- a/frontend/udp/frontend.go +++ b/frontend/udp/frontend.go @@ -268,7 +268,7 @@ func (f *udpFE) handleRequest(ctx context.Context, r Request, w ResponseWriter) // invalid, then fail. if actionID != connectActionID && !gen.Validate(connID, r.IP, timecache.Now()) { err = errBadConnectionID - WriteError(w, txID, err) + writeErrorResponse(w, txID, err) return } @@ -282,15 +282,15 @@ func (f *udpFE) handleRequest(ctx context.Context, r Request, w ResponseWriter) return } - WriteConnectionID(w, txID, gen.Generate(r.IP, timecache.Now())) + writeConnectionID(w, txID, gen.Generate(r.IP, timecache.Now())) case announceActionID, announceV6ActionID: actionName = "announce" var req *bittorrent.AnnounceRequest - req, err = ParseAnnounce(r, actionID == announceV6ActionID, f.ParseOptions) + req, err = parseAnnounce(r, actionID == announceV6ActionID, f.ParseOptions) if err != nil { - WriteError(w, txID, err) + writeErrorResponse(w, txID, err) return } @@ -298,11 +298,11 @@ func (f *udpFE) handleRequest(ctx context.Context, r Request, w ResponseWriter) ctx := bittorrent.InjectRouteParamsToContext(ctx, bittorrent.RouteParams{}) ctx, resp, err = f.logic.HandleAnnounce(ctx, req) if err != nil { - WriteError(w, txID, err) + writeErrorResponse(w, txID, err) return } - WriteAnnounce(w, txID, resp, actionID == announceV6ActionID, r.IP.Is6()) + writeAnnounceResponse(w, txID, resp, actionID == announceV6ActionID, r.IP.Is6()) ctx = bittorrent.RemapRouteParamsToBgContext(ctx) go f.logic.AfterAnnounce(ctx, req, resp) @@ -311,9 +311,9 @@ func (f *udpFE) handleRequest(ctx context.Context, r Request, w ResponseWriter) actionName = "scrape" var req *bittorrent.ScrapeRequest - req, err = ParseScrape(r, f.ParseOptions) + req, err = parseScrape(r, f.ParseOptions) if err != nil { - WriteError(w, txID, err) + writeErrorResponse(w, txID, err) return } @@ -321,18 +321,18 @@ func (f *udpFE) handleRequest(ctx context.Context, r Request, w ResponseWriter) ctx := bittorrent.InjectRouteParamsToContext(ctx, bittorrent.RouteParams{}) ctx, resp, err = f.logic.HandleScrape(ctx, req) if err != nil { - WriteError(w, txID, err) + writeErrorResponse(w, txID, err) return } - WriteScrape(w, txID, resp) + writeScrapeResponse(w, txID, resp) ctx = bittorrent.RemapRouteParamsToBgContext(ctx) go f.logic.AfterScrape(ctx, req, resp) default: err = errUnknownAction - WriteError(w, txID, err) + writeErrorResponse(w, txID, err) } return diff --git a/frontend/udp/params.go b/frontend/udp/params.go index be73a72..59a10e9 100644 --- a/frontend/udp/params.go +++ b/frontend/udp/params.go @@ -39,7 +39,7 @@ func parseQuery(query []byte) (q *queryParams, err error) { if queryDelim != -1 { query = query[queryDelim+1:] } - // This is basically url.parseQuery, but with a map[string]string + // This is basically url.ParseQuery, but with a map[string]string // instead of map[string][]string for the values. q = &queryParams{ params: make(map[string]string), diff --git a/frontend/udp/parser.go b/frontend/udp/parser.go index 8f36782..2117392 100644 --- a/frontend/udp/parser.go +++ b/frontend/udp/parser.go @@ -51,12 +51,12 @@ var ( reqRespBufferPool = bytepool.NewBufferPool() ) -// ParseAnnounce parses an AnnounceRequest from a UDP request. +// parseAnnounce parses an AnnounceRequest from a UDP request. // // If v6Action is true, the announce is parsed the // "old opentracker way": // https://web.archive.org/web/20170503181830/http://opentracker.blog.h3q.com/2007/12/28/the-ipv6-situation/ -func ParseAnnounce(r Request, v6Action bool, opts frontend.ParseOptions) (*bittorrent.AnnounceRequest, error) { +func parseAnnounce(r Request, v6Action bool, opts frontend.ParseOptions) (*bittorrent.AnnounceRequest, error) { var err error ipEnd := 84 + net.IPv4len if v6Action { @@ -157,8 +157,8 @@ func handleOptionalParameters(packet []byte) (bittorrent.Params, error) { return parseQuery(buf.Bytes()) } -// ParseScrape parses a ScrapeRequest from a UDP request. -func ParseScrape(r Request, opts frontend.ParseOptions) (*bittorrent.ScrapeRequest, error) { +// parseScrape parses a ScrapeRequest from a UDP request. +func parseScrape(r Request, opts frontend.ParseOptions) (*bittorrent.ScrapeRequest, error) { // If a scrape isn't at least 36 bytes long, it's malformed. if len(r.Packet) < 36 { return nil, errMalformedPacket diff --git a/frontend/udp/writer.go b/frontend/udp/writer.go index 5ed0efe..61c195b 100644 --- a/frontend/udp/writer.go +++ b/frontend/udp/writer.go @@ -9,8 +9,8 @@ import ( "github.com/sot-tech/mochi/bittorrent" ) -// WriteError writes the failure reason as a null-terminated string. -func WriteError(w io.Writer, txID []byte, err error) { +// writeErrorResponse writes the failure reason as a null-terminated string. +func writeErrorResponse(w io.Writer, txID []byte, err error) { buf := reqRespBufferPool.Get() defer reqRespBufferPool.Put(buf) writeHeader(buf, txID, errorActionID) @@ -23,12 +23,12 @@ func WriteError(w io.Writer, txID []byte, err error) { _, _ = w.Write(buf.Bytes()) } -// WriteAnnounce encodes an announce response according to BEP 15. +// writeAnnounceResponse encodes an announce response according to BEP 15. // The peers returned will be resp.IPv6Peers or resp.IPv4Peers, depending on // whether v6Peers is set. // If v6Action is set, the action will be 4, according to // https://web.archive.org/web/20170503181830/http://opentracker.blog.h3q.com/2007/12/28/the-ipv6-situation/ -func WriteAnnounce(w io.Writer, txID []byte, resp *bittorrent.AnnounceResponse, v6Action, v6Peers bool) { +func writeAnnounceResponse(w io.Writer, txID []byte, resp *bittorrent.AnnounceResponse, v6Action, v6Peers bool) { buf := reqRespBufferPool.Get() defer reqRespBufferPool.Put(buf) @@ -54,8 +54,8 @@ func WriteAnnounce(w io.Writer, txID []byte, resp *bittorrent.AnnounceResponse, _, _ = w.Write(buf.Bytes()) } -// WriteScrape encodes a scrape response according to BEP 15. -func WriteScrape(w io.Writer, txID []byte, resp *bittorrent.ScrapeResponse) { +// writeScrapeResponse encodes a scrape response according to BEP 15. +func writeScrapeResponse(w io.Writer, txID []byte, resp *bittorrent.ScrapeResponse) { buf := reqRespBufferPool.Get() defer reqRespBufferPool.Put(buf) @@ -70,8 +70,8 @@ func WriteScrape(w io.Writer, txID []byte, resp *bittorrent.ScrapeResponse) { _, _ = w.Write(buf.Bytes()) } -// WriteConnectionID encodes a new connection response according to BEP 15. -func WriteConnectionID(w io.Writer, txID, connID []byte) { +// writeConnectionID encodes a new connection response according to BEP 15. +func writeConnectionID(w io.Writer, txID, connID []byte) { buf := reqRespBufferPool.Get() defer reqRespBufferPool.Put(buf) diff --git a/middleware/logic.go b/middleware/logic.go index 26495e1..2476ff3 100644 --- a/middleware/logic.go +++ b/middleware/logic.go @@ -47,7 +47,6 @@ func (l *Logic) HandleAnnounce(ctx context.Context, req *bittorrent.AnnounceRequ resp = &bittorrent.AnnounceResponse{ Interval: l.announceInterval, MinInterval: l.minAnnounceInterval, - Compact: req.Compact, } for _, h := range l.preHooks { if ctx, err = h.HandleAnnounce(ctx, req, resp); err != nil { diff --git a/middleware/logic_test.go b/middleware/logic_test.go index 7b0592e..8bdbd98 100644 --- a/middleware/logic_test.go +++ b/middleware/logic_test.go @@ -34,7 +34,6 @@ func (hooks hookList) handleAnnounce(ctx context.Context, req *bittorrent.Announ resp = &bittorrent.AnnounceResponse{ Interval: 60, MinInterval: 60, - Compact: true, } for _, h := range []Hook(hooks) { From 63e0b93db47d43059f4e4bcf4361b94d1c215c5c Mon Sep 17 00:00:00 2001 From: "Lawrence, Rendall" Date: Sun, 19 Mar 2023 20:13:44 +0300 Subject: [PATCH 4/6] remove `randseed` package --- cmd/mochi/config.go | 3 --- frontend/udp/connection_id.go | 17 ++++++++++---- frontend/udp/connection_id_test.go | 1 - frontend/udp/frontend_test.go | 1 - middleware/jwt/jwt_test.go | 1 - middleware/varinterval/varinterval_test.go | 2 -- pkg/randseed/rand_seed.go | 27 ---------------------- storage/test/storage_bench.go | 2 -- 8 files changed, 12 insertions(+), 42 deletions(-) delete mode 100644 pkg/randseed/rand_seed.go diff --git a/cmd/mochi/config.go b/cmd/mochi/config.go index d51ebaf..aac39bd 100644 --- a/cmd/mochi/config.go +++ b/cmd/mochi/config.go @@ -11,9 +11,6 @@ import ( fu "github.com/sot-tech/mochi/frontend/udp" "github.com/sot-tech/mochi/pkg/conf" - // Seed math random - _ "github.com/sot-tech/mochi/pkg/randseed" - // Imports to register middleware hooks. _ "github.com/sot-tech/mochi/middleware/clientapproval" _ "github.com/sot-tech/mochi/middleware/jwt" diff --git a/frontend/udp/connection_id.go b/frontend/udp/connection_id.go index 6892477..672fc1d 100644 --- a/frontend/udp/connection_id.go +++ b/frontend/udp/connection_id.go @@ -2,9 +2,9 @@ package udp import ( "crypto/hmac" + cr "crypto/rand" "encoding/binary" "hash" - "math/rand" "net/netip" "time" @@ -70,18 +70,25 @@ func NewConnectionIDGenerator(key []byte, maxClockSkew time.Duration) *Connectio buff: make([]byte, buffLen), scratch: make([]byte, scratchLen), maxClockSkew: int64(maxClockSkew), - s: rand.Uint64(), } } // reset resets the generator. // This is called by other methods of the generator, it's not necessary to call // it after getting a generator from a pool. -func (g *ConnectionIDGenerator) reset() { +func (g *ConnectionIDGenerator) reset(init bool) { g.mac.Reset() g.connID = g.connID[:connIDLen] g.buff = g.buff[:buffLen] g.scratch = g.scratch[:0] + if init { + r := make([]byte, 8) + if _, err := cr.Read(r); err == nil { + g.s = binary.BigEndian.Uint64(r) + } else { + g.s = uint64(time.Now().UnixNano()) + } + } } // Generate generates an 8-byte connection ID as described in BEP 15 for the @@ -102,7 +109,7 @@ func (g *ConnectionIDGenerator) reset() { // will be reused, so it must not be referenced after returning the generator // to a pool and will be overwritten be subsequent calls to Generate! func (g *ConnectionIDGenerator) Generate(ip netip.Addr, now time.Time) (out []byte) { - g.reset() + g.reset(true) var r uint64 r, g.s = xorshift.XorShift64S(g.s) g.buff[0] = byte(r) @@ -123,7 +130,7 @@ func (g *ConnectionIDGenerator) Generate(ip netip.Addr, now time.Time) (out []by // Validate validates the given connection ID for an IP and the current time. func (g *ConnectionIDGenerator) Validate(connectionID []byte, ip netip.Addr, now time.Time) bool { - g.reset() + g.reset(false) nowTS := now.Unix() g.buff[0] = connectionID[0] // connectionID contains only 2 bytes of timestamp, so we clean little 16 bits to place it and rehash. diff --git a/frontend/udp/connection_id_test.go b/frontend/udp/connection_id_test.go index 8b38b05..dae1ef9 100644 --- a/frontend/udp/connection_id_test.go +++ b/frontend/udp/connection_id_test.go @@ -13,7 +13,6 @@ import ( "github.com/cespare/xxhash/v2" "github.com/sot-tech/mochi/pkg/log" - _ "github.com/sot-tech/mochi/pkg/randseed" "github.com/stretchr/testify/require" ) diff --git a/frontend/udp/frontend_test.go b/frontend/udp/frontend_test.go index 0313e9b..dda2955 100644 --- a/frontend/udp/frontend_test.go +++ b/frontend/udp/frontend_test.go @@ -7,7 +7,6 @@ import ( "github.com/sot-tech/mochi/middleware" "github.com/sot-tech/mochi/pkg/conf" "github.com/sot-tech/mochi/pkg/log" - _ "github.com/sot-tech/mochi/pkg/randseed" "github.com/sot-tech/mochi/storage" _ "github.com/sot-tech/mochi/storage/memory" ) diff --git a/middleware/jwt/jwt_test.go b/middleware/jwt/jwt_test.go index f54a94d..35ce4ee 100644 --- a/middleware/jwt/jwt_test.go +++ b/middleware/jwt/jwt_test.go @@ -22,7 +22,6 @@ import ( "github.com/sot-tech/mochi/bittorrent" "github.com/sot-tech/mochi/pkg/conf" "github.com/sot-tech/mochi/pkg/log" - _ "github.com/sot-tech/mochi/pkg/randseed" ) const ( diff --git a/middleware/varinterval/varinterval_test.go b/middleware/varinterval/varinterval_test.go index c792485..f8c298e 100644 --- a/middleware/varinterval/varinterval_test.go +++ b/middleware/varinterval/varinterval_test.go @@ -9,8 +9,6 @@ import ( "github.com/sot-tech/mochi/bittorrent" "github.com/sot-tech/mochi/pkg/conf" - - _ "github.com/sot-tech/mochi/pkg/randseed" ) var configTests = []struct { diff --git a/pkg/randseed/rand_seed.go b/pkg/randseed/rand_seed.go deleted file mode 100644 index 8807832..0000000 --- a/pkg/randseed/rand_seed.go +++ /dev/null @@ -1,27 +0,0 @@ -// Package randseed just seeds (math) rand.Rand -package randseed - -import ( - cr "crypto/rand" - "math/rand" - "time" -) - -func init() { - // Seeding global math random - //nolint:staticcheck - rand.Seed(GenSeed()) -} - -// GenSeed returns 64bit seed from crypto/rand source or -// from current time, if crypto random read error occurred -func GenSeed() (seed int64) { - r := make([]byte, 8) - if _, err := cr.Read(r); err == nil { - seed = int64(r[0])<<56 | int64(r[1])<<48 | int64(r[2])<<40 | int64(r[3])<<32 | - int64(r[4])<<24 | int64(r[5])<<16 | int64(r[6])<<8 | int64(r[7]) - } else { - seed = time.Now().UnixNano() - } - return -} diff --git a/storage/test/storage_bench.go b/storage/test/storage_bench.go index b86c6f7..edded0e 100644 --- a/storage/test/storage_bench.go +++ b/storage/test/storage_bench.go @@ -13,8 +13,6 @@ import ( "testing" "github.com/sot-tech/mochi/bittorrent" - // used for seeding global math.Rand - _ "github.com/sot-tech/mochi/pkg/randseed" "github.com/sot-tech/mochi/storage" ) From 21c600e92179063ef60b4eaac1e54c6e7e15a640 Mon Sep 17 00:00:00 2001 From: "Lawrence, Rendall" Date: Sun, 19 Mar 2023 20:19:55 +0300 Subject: [PATCH 5/6] add verbose to linter --- .github/workflows/lint.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 1f077d2..3e8893b 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -31,6 +31,7 @@ jobs: - uses: "golangci/golangci-lint-action@v3" with: version: "latest" + args: -v codeql: name: "Analyze with CodeQL" From 869a594aa386a95d81acafc1b20632f8c6550368 Mon Sep 17 00:00:00 2001 From: "Lawrence, Rendall" Date: Sun, 19 Mar 2023 20:33:07 +0300 Subject: [PATCH 6/6] fix lint warnings --- .github/workflows/lint.yaml | 1 - frontend/http/params_test.go | 24 +++++++----------------- frontend/udp/params_test.go | 4 +++- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 3e8893b..1f077d2 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -31,7 +31,6 @@ jobs: - uses: "golangci/golangci-lint-action@v3" with: version: "latest" - args: -v codeql: name: "Analyze with CodeQL" diff --git a/frontend/http/params_test.go b/frontend/http/params_test.go index 9830fb7..d99df78 100644 --- a/frontend/http/params_test.go +++ b/frontend/http/params_test.go @@ -68,21 +68,18 @@ func mapArrayEqual(boxed map[string][]string, unboxed *queryParams) (bool, strin // Also note that any error that is encountered during parsing is returned as a // ClientError, as this method is expected to be used to parse client-provided // data. -func parseURLData(urlData []byte) (*queryParams, error) { +func parseURLData(urlData []byte) *queryParams { i := bytes.IndexByte(urlData, '?') if i >= 0 { urlData = urlData[i+1:] } q := &queryParams{new(fasthttp.Args)} q.ParseBytes(urlData) - return q, nil + return q } func TestParseEmptyURLData(t *testing.T) { - parsedQuery, err := parseURLData(nil) - if err != nil { - t.Fatal(err) - } + parsedQuery := parseURLData(nil) if parsedQuery == nil { t.Fatal("Parsed query must not be nil") } @@ -90,10 +87,7 @@ func TestParseEmptyURLData(t *testing.T) { func TestParseValidURLData(t *testing.T) { for parseIndex, parseVal := range ValidAnnounceArguments { - parsedQueryObj, err := parseURLData([]byte("/announce?" + parseVal.Encode())) - if err != nil { - t.Fatal(err) - } + parsedQueryObj := parseURLData([]byte("/announce?" + parseVal.Encode())) if eq, exp := mapArrayEqual(parseVal, parsedQueryObj); !eq { t.Fatalf("Incorrect parse at item %d.\n Expected=%v\n Received=%v\n", parseIndex, parseVal, exp) @@ -101,9 +95,9 @@ func TestParseValidURLData(t *testing.T) { } } -func TestParseShouldNotPanicURLData(t *testing.T) { +func TestParseShouldNotPanicURLData(_ *testing.T) { for _, parseStr := range shouldNotPanicQueries { - _, _ = parseURLData(parseStr) + _ = parseURLData(parseStr) } } @@ -115,11 +109,7 @@ func BenchmarkParseQuery(b *testing.B) { b.ResetTimer() for bCount := 0; bCount < b.N; bCount++ { i := bCount % len(announceStrings) - parsedQueryObj, err := parseURLData(announceStrings[i]) - if err != nil { - b.Error(err, i) - b.Log(parsedQueryObj) - } + _ = parseURLData(announceStrings[i]) } } diff --git a/frontend/udp/params_test.go b/frontend/udp/params_test.go index 266d6f5..89e9a93 100644 --- a/frontend/udp/params_test.go +++ b/frontend/udp/params_test.go @@ -88,7 +88,9 @@ func TestParseInvalidURLData(t *testing.T) { func TestParseShouldNotPanicURLData(t *testing.T) { for _, parseStr := range shouldNotPanicQueries { - _, _ = parseQuery(parseStr) + if _, err := parseQuery(parseStr); err != nil { + t.Error(err) + } } }