github.com/google/go-cmp v0.5.9
github.com/gorilla/websocket v1.5.0
github.com/jessevdk/go-flags v1.5.0
+ github.com/multiformats/go-multihash v0.2.3
github.com/pion/datachannel v1.5.2
github.com/pion/logging v0.2.2
github.com/pion/webrtc/v3 v3.1.42
github.com/google/uuid v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.3 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
+ github.com/minio/sha256-simd v1.0.0 // indirect
+ github.com/mr-tron/base58 v1.2.0 // indirect
github.com/mschoch/smat v0.2.0 // indirect
+ github.com/multiformats/go-varint v0.0.6 // indirect
github.com/pion/dtls/v2 v2.2.4 // indirect
github.com/pion/ice/v2 v2.2.6 // indirect
github.com/pion/interceptor v0.1.11 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
+ github.com/spaolacci/murmur3 v1.1.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.8.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.8.0 // indirect
go.opentelemetry.io/proto/otlp v0.18.0 // indirect
google.golang.org/grpc v1.56.3 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
+ lukechampine.com/blake3 v1.1.6 // indirect
modernc.org/libc v1.22.3 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
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/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
+github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
+github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
+github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
+github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
+github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
+github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY=
+github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
+github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9COBTTl8mzkwADnc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c=
+lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
--- /dev/null
+package metainfo
+
+import (
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/multiformats/go-multihash"
+
+ g "github.com/anacrolix/generics"
+ infohash_v2 "github.com/anacrolix/torrent/types/infohash-v2"
+)
+
+// Magnet link components.
+type MagnetV2 struct {
+ InfoHash g.Option[Hash] // Expected in this implementation
+ V2InfoHash g.Option[infohash_v2.T]
+ Trackers []string // "tr" values
+ DisplayName string // "dn" value, if not empty
+ Params url.Values // All other values, such as "x.pe", "as", "xs" etc.
+}
+
+const (
+ btmhPrefix = "urn:btmh:"
+)
+
+func (m MagnetV2) String() string {
+ // Deep-copy m.Params
+ vs := make(url.Values, len(m.Params)+len(m.Trackers)+2)
+ for k, v := range m.Params {
+ vs[k] = append([]string(nil), v...)
+ }
+
+ for _, tr := range m.Trackers {
+ vs.Add("tr", tr)
+ }
+ if m.DisplayName != "" {
+ vs.Add("dn", m.DisplayName)
+ }
+
+ // Transmission and Deluge both expect "urn:btih:" to be unescaped. Deluge wants it to be at the
+ // start of the magnet link. The InfoHash field is expected to be BitTorrent in this
+ // implementation.
+ u := url.URL{
+ Scheme: "magnet",
+ }
+ var queryParts []string
+ if m.InfoHash.Ok {
+ queryParts = append(queryParts, "xt="+btihPrefix+m.InfoHash.Value.HexString())
+ }
+ if m.V2InfoHash.Ok {
+ queryParts = append(
+ queryParts,
+ "xt="+btmhPrefix+infohash_v2.ToMultihash(m.V2InfoHash.Value).HexString(),
+ )
+ }
+ if rem := vs.Encode(); rem != "" {
+ queryParts = append(queryParts, rem)
+ }
+ u.RawQuery = strings.Join(queryParts, "&")
+ return u.String()
+}
+
+// ParseMagnetUri parses Magnet-formatted URIs into a Magnet instance
+func ParseMagnetV2Uri(uri string) (m MagnetV2, err error) {
+ u, err := url.Parse(uri)
+ if err != nil {
+ err = fmt.Errorf("error parsing uri: %w", err)
+ return
+ }
+ if u.Scheme != "magnet" {
+ err = fmt.Errorf("unexpected scheme %q", u.Scheme)
+ return
+ }
+ q := u.Query()
+ for _, xt := range q["xt"] {
+ if hashStr, found := strings.CutPrefix(xt, btihPrefix); found {
+ if m.InfoHash.Ok {
+ err = errors.New("more than one infohash found in magnet link")
+ return
+ }
+ m.InfoHash.Value, err = parseEncodedV1Infohash(hashStr)
+ if err != nil {
+ err = fmt.Errorf("error parsing infohash %q: %w", hashStr, err)
+ return
+ }
+ m.InfoHash.Ok = true
+ } else if hashStr, found := strings.CutPrefix(xt, btmhPrefix); found {
+ if m.V2InfoHash.Ok {
+ err = errors.New("more than one infohash found in magnet link")
+ return
+ }
+ m.V2InfoHash.Value, err = parseV2Infohash(hashStr)
+ if err != nil {
+ err = fmt.Errorf("error parsing infohash %q: %w", hashStr, err)
+ return
+ }
+ m.V2InfoHash.Ok = true
+ } else {
+ lazyAddParam(&m.Params, "xt", xt)
+ }
+ }
+ q.Del("xt")
+ m.DisplayName = popFirstValue(q, "dn").UnwrapOrZeroValue()
+ m.Trackers = q["tr"]
+ q.Del("tr")
+ // Add everything we haven't consumed.
+ copyParams(&m.Params, q)
+ return
+}
+
+func lazyAddParam(vs *url.Values, k, v string) {
+ if *vs == nil {
+ g.MakeMap(vs)
+ }
+ vs.Add(k, v)
+}
+
+func copyParams(dest *url.Values, src url.Values) {
+ for k, vs := range src {
+ for _, v := range vs {
+ lazyAddParam(dest, k, v)
+ }
+ }
+}
+
+func parseV2Infohash(encoded string) (ih infohash_v2.T, err error) {
+ b, err := hex.DecodeString(encoded)
+ if err != nil {
+ return
+ }
+ mh, err := multihash.Decode(b)
+ if err != nil {
+ return
+ }
+ if mh.Code != multihash.SHA2_256 || mh.Length != infohash_v2.Size || len(mh.Digest) != infohash_v2.Size {
+ err = errors.New("bad multihash")
+ return
+ }
+ n := copy(ih[:], mh.Digest)
+ if n != infohash_v2.Size {
+ panic(n)
+ }
+ return
+}
--- /dev/null
+package metainfo
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestParseMagnetV2(t *testing.T) {
+ c := qt.New(t)
+
+ const v2Only = "magnet:?xt=urn:btmh:1220caf1e1c30e81cb361b9ee167c4aa64228a7fa4fa9f6105232b28ad099f3a302e&dn=bittorrent-v2-test"
+
+ m2, err := ParseMagnetV2Uri(v2Only)
+ c.Assert(err, qt.IsNil)
+ c.Check(m2.InfoHash.Ok, qt.IsFalse)
+ c.Check(m2.V2InfoHash.Ok, qt.IsTrue)
+ c.Check(m2.V2InfoHash.Value.HexString(), qt.Equals, "caf1e1c30e81cb361b9ee167c4aa64228a7fa4fa9f6105232b28ad099f3a302e")
+ c.Check(m2.Params, qt.HasLen, 0)
+
+ _, err = ParseMagnetUri(v2Only)
+ c.Check(err, qt.IsNotNil)
+
+ const hybrid = "magnet:?xt=urn:btih:631a31dd0a46257d5078c0dee4e66e26f73e42ac&xt=urn:btmh:1220d8dd32ac93357c368556af3ac1d95c9d76bd0dff6fa9833ecdac3d53134efabb&dn=bittorrent-v1-v2-hybrid-test"
+
+ m2, err = ParseMagnetV2Uri(hybrid)
+ c.Assert(err, qt.IsNil)
+ c.Check(m2.InfoHash.Ok, qt.IsTrue)
+ c.Check(m2.InfoHash.Value.HexString(), qt.Equals, "631a31dd0a46257d5078c0dee4e66e26f73e42ac")
+ c.Check(m2.V2InfoHash.Ok, qt.IsTrue)
+ c.Check(m2.V2InfoHash.Value.HexString(), qt.Equals, "d8dd32ac93357c368556af3ac1d95c9d76bd0dff6fa9833ecdac3d53134efabb")
+ c.Check(m2.Params, qt.HasLen, 0)
+
+ m, err := ParseMagnetUri(hybrid)
+ c.Assert(err, qt.IsNil)
+ c.Check(m.InfoHash.HexString(), qt.Equals, "631a31dd0a46257d5078c0dee4e66e26f73e42ac")
+ c.Check(m.Params["xt"], qt.HasLen, 1)
+}
"fmt"
"net/url"
"strings"
+
+ g "github.com/anacrolix/generics"
+
+ "github.com/anacrolix/torrent/types/infohash"
)
// Magnet link components.
Params url.Values // All other values, such as "x.pe", "as", "xs" etc.
}
-const xtPrefix = "urn:btih:"
+const btihPrefix = "urn:btih:"
func (m Magnet) String() string {
// Deep-copy m.Params
// implementation.
u := url.URL{
Scheme: "magnet",
- RawQuery: "xt=" + xtPrefix + m.InfoHash.HexString(),
+ RawQuery: "xt=" + btihPrefix + m.InfoHash.HexString(),
}
if len(vs) != 0 {
u.RawQuery += "&" + vs.Encode()
return
}
q := u.Query()
- xt := q.Get("xt")
- m.InfoHash, err = parseInfohash(q.Get("xt"))
- if err != nil {
- err = fmt.Errorf("error parsing infohash %q: %w", xt, err)
+ gotInfohash := false
+ for _, xt := range q["xt"] {
+ if gotInfohash {
+ lazyAddParam(&m.Params, "xt", xt)
+ continue
+ }
+ encoded, found := strings.CutPrefix(xt, btihPrefix)
+ if !found {
+ lazyAddParam(&m.Params, "xt", xt)
+ continue
+ }
+ m.InfoHash, err = parseEncodedV1Infohash(encoded)
+ if err != nil {
+ err = fmt.Errorf("error parsing v1 infohash %q: %w", xt, err)
+ return
+ }
+ gotInfohash = true
+ }
+ if !gotInfohash {
+ err = errors.New("missing v1 infohash")
return
}
- dropFirst(q, "xt")
- m.DisplayName = q.Get("dn")
- dropFirst(q, "dn")
+ q.Del("xt")
+ m.DisplayName = popFirstValue(q, "dn").UnwrapOrZeroValue()
m.Trackers = q["tr"]
- delete(q, "tr")
- if len(q) == 0 {
- q = nil
- }
- m.Params = q
+ q.Del("tr")
+ copyParams(&m.Params, q)
return
}
-func parseInfohash(xt string) (ih Hash, err error) {
- if !strings.HasPrefix(xt, xtPrefix) {
- err = errors.New("bad xt parameter prefix")
- return
- }
- encoded := xt[len(xtPrefix):]
+func parseEncodedV1Infohash(encoded string) (ih infohash.T, err error) {
decode := func() func(dst, src []byte) (int, error) {
switch len(encoded) {
case 40:
return
}
-func dropFirst(vs url.Values, key string) {
+func popFirstValue(vs url.Values, key string) g.Option[string] {
sl := vs[key]
switch len(sl) {
- case 0, 1:
+ case 0:
+ return g.None[string]()
+ case 1:
vs.Del(key)
+ return g.Some(sl[0])
default:
vs[key] = sl[1:]
+ return g.Some(sl[0])
}
}
import (
"encoding/hex"
+ "github.com/davecgh/go-spew/spew"
+ qt "github.com/frankban/quicktest"
"testing"
"github.com/stretchr/testify/assert"
}
return false
}
+
+// Check that we can parse the magnet link generated from a real-world torrent. This was added due
+// to a regression in copyParams.
+func TestParseSintelMagnet(t *testing.T) {
+ c := qt.New(t)
+ mi, err := LoadFromFile("../testdata/sintel.torrent")
+ c.Assert(err, qt.IsNil)
+ m := mi.Magnet(nil, nil)
+ ms := m.String()
+ t.Logf("magnet link: %q", ms)
+ m, err = ParseMagnetUri(ms)
+ c.Check(err, qt.IsNil)
+ spewCfg := spew.NewDefaultConfig()
+ spewCfg.DisableMethods = true
+ spewCfg.Dump(m)
+ m2, err := ParseMagnetV2Uri(ms)
+ spewCfg.Dump(m2)
+ c.Check(err, qt.IsNil)
+}
--- /dev/null
+package infohash_v2
+
+import (
+ "crypto/sha256"
+ "encoding"
+ "encoding/hex"
+ "fmt"
+
+ "github.com/multiformats/go-multihash"
+
+ "github.com/anacrolix/torrent/types/infohash"
+)
+
+const Size = sha256.Size
+
+// 32-byte SHA2-256 hash. See BEP 52.
+type T [Size]byte
+
+var _ fmt.Formatter = (*T)(nil)
+
+func (t *T) Format(f fmt.State, c rune) {
+ // TODO: I can't figure out a nice way to just override the 'x' rune, since it's meaningless
+ // with the "default" 'v', or .String() already returning the hex.
+ f.Write([]byte(t.HexString()))
+}
+
+func (t *T) Bytes() []byte {
+ return t[:]
+}
+
+func (t *T) AsString() string {
+ return string(t[:])
+}
+
+func (t *T) String() string {
+ return t.HexString()
+}
+
+func (t *T) HexString() string {
+ return fmt.Sprintf("%x", t[:])
+}
+
+func (t *T) FromHexString(s string) (err error) {
+ if len(s) != 2*Size {
+ err = fmt.Errorf("hash hex string has bad length: %d", len(s))
+ return
+ }
+ n, err := hex.Decode(t[:], []byte(s))
+ if err != nil {
+ return
+ }
+ if n != Size {
+ panic(n)
+ }
+ return
+}
+
+// Truncates the hash to 20 bytes for use in auxiliary interfaces, like DHT and trackers.
+func (t *T) ToShort() (short infohash.T) {
+ copy(short[:], t[:])
+ return
+}
+
+var (
+ _ encoding.TextUnmarshaler = (*T)(nil)
+ _ encoding.TextMarshaler = T{}
+)
+
+func (t *T) UnmarshalText(b []byte) error {
+ return t.FromHexString(string(b))
+}
+
+func (t T) MarshalText() (text []byte, err error) {
+ return []byte(t.HexString()), nil
+}
+
+func FromHexString(s string) (h T) {
+ err := h.FromHexString(s)
+ if err != nil {
+ panic(err)
+ }
+ return
+}
+
+func HashBytes(b []byte) (ret T) {
+ hasher := sha256.New()
+ hasher.Write(b)
+ copy(ret[:], hasher.Sum(nil))
+ return
+}
+
+func ToMultihash(t T) multihash.Multihash {
+ b, _ := multihash.Encode(t[:], multihash.SHA2_256)
+ return b
+}
PiecePriorityNormal // Wanted.
PiecePriorityHigh // Wanted a lot.
PiecePriorityReadahead // May be required soon.
- // Succeeds a piece where a read occurred. Currently the same as Now,
- // apparently due to issues with caching.
+ // Succeeds a piece where a read occurred. Currently, the same as Now, apparently due to issues
+ // with caching.
PiecePriorityNext
PiecePriorityNow // A Reader is reading in this piece. Highest urgency.
)