"github.com/anacrolix/torrent/metainfo"
"github.com/anacrolix/torrent/mse"
pp "github.com/anacrolix/torrent/peer_protocol"
- infohash_v2 "github.com/anacrolix/torrent/types/infohash-v2"
request_strategy "github.com/anacrolix/torrent/request-strategy"
"github.com/anacrolix/torrent/storage"
"github.com/anacrolix/torrent/tracker"
"github.com/anacrolix/torrent/types/infohash"
+ infohash_v2 "github.com/anacrolix/torrent/types/infohash-v2"
"github.com/anacrolix/torrent/webtorrent"
)
fmt.Fprintf(w, "# Torrents: %d\n", len(torrentsSlice))
fmt.Fprintln(w)
sort.Slice(torrentsSlice, func(l, r int) bool {
- return torrentsSlice[l].infoHash.AsString() < torrentsSlice[r].infoHash.AsString()
+ return torrentsSlice[l].canonicalShortInfohash().AsString() < torrentsSlice[r].canonicalShortInfohash().AsString()
})
for _, t := range torrentsSlice {
if t.name() == "" {
cl.websocketTrackers = websocketTrackers{
PeerId: cl.peerID,
Logger: cl.logger,
- GetAnnounceRequest: func(event tracker.AnnounceEvent, infoHash [20]byte) (tracker.AnnounceRequest, error) {
+ GetAnnounceRequest: func(
+ event tracker.AnnounceEvent, infoHash [20]byte,
+ ) (
+ tracker.AnnounceRequest, error,
+ ) {
cl.lock()
defer cl.unlock()
t, ok := cl.torrents[infoHash]
if !ok {
return tracker.AnnounceRequest{}, errors.New("torrent not tracked by client")
}
- return t.announceRequest(event), nil
+ return t.announceRequest(event, infoHash), nil
},
Proxy: cl.config.HTTPProxy,
WebsocketTrackerHttpHeader: cl.config.WebsocketTrackerHttpHeader,
return cl.LocalPort()
}
-func (cl *Client) initiateHandshakes(c *PeerConn, t *Torrent) error {
+func (cl *Client) initiateHandshakes(c *PeerConn, t *Torrent) (err error) {
if c.headerEncrypted {
var rw io.ReadWriter
- var err error
rw, c.cryptoMethod, err = mse.InitiateHandshake(
struct {
io.Reader
io.Writer
}{c.r, c.w},
- t.infoHash[:],
+ t.canonicalShortInfohash().Bytes(),
nil,
cl.config.CryptoProvides,
)
return fmt.Errorf("header obfuscation handshake: %w", err)
}
}
- ih, err := cl.connBtHandshake(c, &t.infoHash)
+ ih, err := cl.connBtHandshake(c, t.canonicalShortInfohash())
if err != nil {
return fmt.Errorf("bittorrent protocol handshake: %w", err)
}
- if ih != t.infoHash {
- return errors.New("bittorrent protocol handshake: peer infohash didn't match")
+ if g.Some(ih) == t.infoHash {
+ return nil
}
- return nil
+ if t.infoHashV2.Ok && *t.infoHashV2.Value.ToShort() == ih {
+ c.v2 = true
+ return nil
+ }
+ err = errors.New("bittorrent protocol handshake: peer infohash didn't match")
+ return
}
// Calls f with any secret keys. Note that it takes the Client lock, and so must be used from code
// Return a Torrent ready for insertion into a Client.
func (cl *Client) newTorrentOpt(opts AddTorrentOpts) (t *Torrent) {
+ var v1InfoHash g.Option[infohash.T]
+ if !opts.InfoHash.IsZero() {
+ v1InfoHash.Set(opts.InfoHash)
+ }
+ if !v1InfoHash.Ok && !opts.InfoHashV2.Ok {
+ panic("v1 infohash must be nonzero or v2 infohash must be set")
+ }
// use provided storage, if provided
storageClient := cl.defaultStorage
if opts.Storage != nil {
t = &Torrent{
cl: cl,
- infoHash: opts.InfoHash,
+ infoHash: v1InfoHash,
infoHashV2: opts.InfoHashV2,
peers: prioritizedPeers{
om: gbtree.New(32),
return cl.AddTorrentInfoHashWithStorage(infoHash, nil)
}
-// Adds a torrent by InfoHash with a custom Storage implementation.
+// Deprecated. Adds a torrent by InfoHash with a custom Storage implementation.
// If the torrent already exists then this Storage is ignored and the
// existing torrent returned with `new` set to `false`
-func (cl *Client) AddTorrentInfoHashWithStorage(infoHash metainfo.Hash, specStorage storage.ClientImpl) (t *Torrent, new bool) {
+func (cl *Client) AddTorrentInfoHashWithStorage(
+ infoHash metainfo.Hash,
+ specStorage storage.ClientImpl,
+) (t *Torrent, new bool) {
cl.lock()
defer cl.unlock()
t, ok := cl.torrents[infoHash]
"encoding/binary"
"fmt"
"io"
+ "math/rand"
"net"
"net/netip"
"os"
cl, err := NewClient(TestingConfig(t))
require.NoError(t, err)
defer cl.Close()
- for i := 0; i < 1000; i += 1 {
+ for i := range 1000 {
var spec TorrentSpec
- binary.PutVarint(spec.InfoHash[:], int64(i))
+ binary.PutVarint(spec.InfoHash[:], int64(i+1))
tt, new, err := cl.AddTorrentSpec(&spec)
assert.NoError(t, err)
assert.True(t, new)
require.NoError(t, err)
defer cl.Close()
spec := TorrentSpec{}
+ rand.Read(spec.InfoHash[:])
T, new, _ := cl.AddTorrentSpec(&spec)
if !new {
t.FailNow()
}
func TestPieceCompletedInStorageButNotClient(t *testing.T) {
+ c := qt.New(t)
greetingTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
defer os.RemoveAll(greetingTempDir)
cfg := TestingConfig(t)
seeder, err := NewClient(TestingConfig(t))
require.NoError(t, err)
defer seeder.Close()
- seeder.AddTorrentSpec(&TorrentSpec{
+ _, new, err := seeder.AddTorrentSpec(&TorrentSpec{
InfoBytes: greetingMetainfo.InfoBytes,
+ InfoHash: greetingMetainfo.HashInfoBytes(),
})
+ c.Check(err, qt.IsNil)
+ c.Check(new, qt.IsTrue)
}
// Check that when the listen port is 0, all the protocols listened on have
package main
import (
- "github.com/anacrolix/torrent/metainfo"
"os"
+
+ "github.com/anacrolix/torrent/metainfo"
)
type argError struct {
import (
"crypto/sha256"
+
"github.com/RoaringBitmap/roaring"
g "github.com/anacrolix/generics"
"github.com/anacrolix/missinggo/v2/bitmap"
logger: log.Default,
chunkSize: defaultChunkSize,
}
+ tt.infoHash.Ok = true
+ tt.infoHash.Value[0] = 1
mi := testutil.GreetingMetaInfo()
info, err := mi.UnmarshalInfo()
require.NoError(t, err)
package torrent_test
import (
+ "math/rand"
"strconv"
"testing"
c.Assert(err, qt.IsNil)
defer cl2.Close()
addOpts := AddTorrentOpts{}
+ rand.Read(addOpts.InfoHash[:])
t1, _ := cl1.AddTorrentOpt(addOpts)
t2, _ := cl2.AddTorrentOpt(addOpts)
defer testutil.ExportStatusWriter(cl1, "cl1", t)()
import (
"crypto/sha256"
"fmt"
- g "github.com/anacrolix/generics"
"math/bits"
+
+ g "github.com/anacrolix/generics"
)
// The leaf block size for BitTorrent v2 Merkle trees.
import (
"fmt"
+
"github.com/anacrolix/torrent/merkle"
)
package metainfo
import (
+ "sort"
+
g "github.com/anacrolix/generics"
- "github.com/anacrolix/torrent/bencode"
"golang.org/x/exp/maps"
- "sort"
+
+ "github.com/anacrolix/torrent/bencode"
)
const FileTreePropertiesKey = ""
package metainfo
import (
- g "github.com/anacrolix/generics"
"strings"
+
+ g "github.com/anacrolix/generics"
)
// Information specific to a single file inside the MetaInfo structure.
"net/url"
"strings"
+ g "github.com/anacrolix/generics"
"github.com/multiformats/go-multihash"
- g "github.com/anacrolix/generics"
infohash_v2 "github.com/anacrolix/torrent/types/infohash-v2"
)
import (
"encoding/hex"
- "github.com/davecgh/go-spew/spew"
- qt "github.com/frankban/quicktest"
"testing"
+ "github.com/davecgh/go-spew/spew"
+ qt "github.com/frankban/quicktest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
package metainfo
import (
- "github.com/davecgh/go-spew/spew"
"io"
"os"
"path"
"testing"
"github.com/anacrolix/missinggo/v2"
+ "github.com/davecgh/go-spew/spew"
qt "github.com/frankban/quicktest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
type PeerConn struct {
Peer
+ // BEP 52
+ v2 bool
+
// A string that should identify the PeerConn's net.Conn endpoints. The net.Conn could
// be wrapping WebRTC, uTP, or TCP etc. Used in writing the conn status for peers.
connString string
cl.initLogger()
qtc := qt.New(t)
c := cl.newConnection(nil, newConnectionOpts{network: "io.Pipe"})
- c.setTorrent(cl.newTorrent(metainfo.Hash{}, nil))
+ c.setTorrent(cl.newTorrentForTesting())
err := c.t.setInfo(&metainfo.Info{Pieces: make([]byte, metainfo.HashSize*3)})
qtc.Assert(err, qt.IsNil)
r, w := io.Pipe()
})
cl.initLogger()
ts := &torrentStorage{}
- t := cl.newTorrent(metainfo.Hash{}, nil)
+ t := cl.newTorrentForTesting()
t.initialPieceCheckDisabled = true
require.NoError(b, t.setInfo(&metainfo.Info{
Pieces: make([]byte, 20),
"github.com/anacrolix/dht/v2/krpc"
"github.com/stretchr/testify/require"
- "github.com/anacrolix/torrent/metainfo"
pp "github.com/anacrolix/torrent/peer_protocol"
)
var cl Client
cl.init(TestingConfig(t))
cl.initLogger()
- torrent := cl.newTorrent(metainfo.Hash{}, nil)
+ torrent := cl.newTorrentForTesting()
addr := &net.TCPAddr{IP: net.IPv6loopback, Port: 4747}
c := cl.newConnection(nil, newConnectionOpts{
remoteAddr: addr,
import (
"fmt"
- g "github.com/anacrolix/generics"
- infohash_v2 "github.com/anacrolix/torrent/types/infohash-v2"
"sync"
"github.com/anacrolix/chansync"
+ g "github.com/anacrolix/generics"
"github.com/anacrolix/missinggo/v2/bitmap"
"github.com/anacrolix/torrent/metainfo"
pp "github.com/anacrolix/torrent/peer_protocol"
"github.com/anacrolix/torrent/storage"
+ infohash_v2 "github.com/anacrolix/torrent/types/infohash-v2"
)
type Piece struct {
}
func (p *Piece) String() string {
- return fmt.Sprintf("%s/%d", p.t.infoHash.HexString(), p.index)
+ return fmt.Sprintf("%s/%d", p.t.canonicalShortInfohash().HexString(), p.index)
}
func (p *Piece) Info() metainfo.Piece {
}
// TODO: Just reset pieces in the readahead window. This might help
// prevent thrashing with small caches and file and piece priorities.
- r.log(log.Fstr("error reading torrent %s piece %d offset %d, %d bytes: %v",
- r.t.infoHash.HexString(), firstPieceIndex, firstPieceOffset, len(b1), err))
+ r.log(log.Fstr("error reading piece %d offset %d, %d bytes: %v",
+ firstPieceIndex, firstPieceOffset, len(b1), err))
if !r.t.updatePieceCompletion(firstPieceIndex) {
r.log(log.Fstr("piece %d completion unchanged", firstPieceIndex))
}
input,
t.getPieceRequestOrder(),
func(ih InfoHash, pieceIndex int, pieceExtra requestStrategy.PieceRequestOrderState) {
- if ih != t.infoHash {
+ if ih != *t.canonicalShortInfohash() {
return
}
if !p.peerHasPiece(pieceIndex) {
import (
"fmt"
+
g "github.com/anacrolix/generics"
- infohash_v2 "github.com/anacrolix/torrent/types/infohash-v2"
"github.com/anacrolix/torrent/metainfo"
pp "github.com/anacrolix/torrent/peer_protocol"
"github.com/anacrolix/torrent/storage"
+ infohash_v2 "github.com/anacrolix/torrent/types/infohash-v2"
)
// Specifies a new torrent for adding to a client, or additions to an existing Torrent. There are
}
func TorrentSpecFromMagnetUri(uri string) (spec *TorrentSpec, err error) {
- m, err := metainfo.ParseMagnetUri(uri)
+ m, err := metainfo.ParseMagnetV2Uri(uri)
if err != nil {
return
}
spec = &TorrentSpec{
Trackers: [][]string{m.Trackers},
DisplayName: m.DisplayName,
- InfoHash: m.InfoHash,
+ InfoHash: m.InfoHash.UnwrapOrZeroValue(),
+ InfoHashV2: m.V2InfoHash,
Webseeds: m.Params["ws"],
Sources: append(m.Params["xs"], m.Params["as"]...),
PeerAddrs: m.Params["x.pe"], // BEP 9
if info.HasV2() {
v2Infohash.Set(infohash_v2.HashBytes(mi.InfoBytes))
if !info.HasV1() {
- v1Ih = v2Infohash.Value.ToShort()
+ v1Ih = *v2Infohash.Value.ToShort()
}
}
// The Torrent's infohash. This is fixed and cannot change. It uniquely identifies a torrent.
func (t *Torrent) InfoHash() metainfo.Hash {
- return t.infoHash
+ return *t.canonicalShortInfohash()
}
// Returns a channel that is closed when the info (.Info()) for the torrent has become available.
defer wg.Wait()
t.cl.lock()
defer t.cl.unlock()
- err := t.cl.dropTorrent(t.infoHash, &wg)
+ err := t.cl.dropTorrent(*t.canonicalShortInfohash(), &wg)
if err != nil {
panic(err)
}
func (t *Torrent) String() string {
s := t.name()
if s == "" {
- return t.infoHash.HexString()
+ return t.canonicalShortInfohash().HexString()
} else {
return strconv.Quote(s)
}
}
func (cl *Client) newTorrentForTesting() *Torrent {
- return cl.newTorrent(metainfo.Hash{}, nil)
+ return cl.newTorrent(metainfo.Hash{1}, nil)
}
"crypto/sha1"
"errors"
"fmt"
- "github.com/anacrolix/torrent/merkle"
- "github.com/anacrolix/torrent/types/infohash"
- infohash_v2 "github.com/anacrolix/torrent/types/infohash-v2"
"hash"
"io"
"math/rand"
"github.com/anacrolix/sync"
"github.com/pion/datachannel"
"golang.org/x/exp/maps"
+ "golang.org/x/sync/errgroup"
"github.com/anacrolix/torrent/bencode"
"github.com/anacrolix/torrent/internal/check"
"github.com/anacrolix/torrent/internal/nestedmaps"
+ "github.com/anacrolix/torrent/merkle"
"github.com/anacrolix/torrent/metainfo"
pp "github.com/anacrolix/torrent/peer_protocol"
utHolepunch "github.com/anacrolix/torrent/peer_protocol/ut-holepunch"
"github.com/anacrolix/torrent/storage"
"github.com/anacrolix/torrent/tracker"
typedRoaring "github.com/anacrolix/torrent/typed-roaring"
+ "github.com/anacrolix/torrent/types/infohash"
+ infohash_v2 "github.com/anacrolix/torrent/types/infohash-v2"
"github.com/anacrolix/torrent/webseed"
"github.com/anacrolix/torrent/webtorrent"
)
closed chansync.SetOnce
onClose []func()
- infoHash metainfo.Hash
+ infoHash g.Option[metainfo.Hash]
infoHashV2 g.Option[infohash_v2.T]
pieces []Piece
// Whether we want to know more peers.
wantPeersEvent missinggo.Event
// An announcer for each tracker URL.
- trackerAnnouncers map[string]torrentTrackerAnnouncer
+ trackerAnnouncers map[torrentTrackerAnnouncerKey]torrentTrackerAnnouncer
// How many times we've initiated a DHT announce. TODO: Move into stats.
numDHTAnnounces int
disableTriggers bool
}
+type torrentTrackerAnnouncerKey struct {
+ shortInfohash [20]byte
+ url string
+}
+
type outgoingConnAttemptKey = *PeerInfo
func (t *Torrent) length() int64 {
}
if t.storageOpener != nil {
var err error
- t.storage, err = t.storageOpener.OpenTorrent(info, t.infoHash)
+ t.storage, err = t.storageOpener.OpenTorrent(info, *t.canonicalShortInfohash())
if err != nil {
return fmt.Errorf("error opening torrent storage: %s", err)
}
func (t *Torrent) pieceRequestOrderKey(i int) request_strategy.PieceRequestOrderKey {
return request_strategy.PieceRequestOrderKey{
- InfoHash: t.infoHash,
+ InfoHash: *t.canonicalShortInfohash(),
Index: i,
}
}
// Called when metadata for a torrent becomes available.
func (t *Torrent) setInfoBytesLocked(b []byte) error {
- v2Hash := infohash_v2.HashBytes(b)
- v1Hash := infohash.HashBytes(b)
- v2Short := v2Hash.ToShort()
- if v2Short != t.infoHash && v1Hash != t.infoHash {
- return errors.New("info bytes have wrong hash")
+ if t.infoHash.Ok && infohash.HashBytes(b) != t.infoHash.Value {
+ return errors.New("info bytes have wrong v1 hash")
+ }
+ var v2Hash g.Option[infohash_v2.T]
+ if t.infoHashV2.Ok {
+ v2Hash.Set(infohash_v2.HashBytes(b))
+ if v2Hash.Value != t.infoHashV2.Value {
+ return errors.New("info bytes have wrong v2 hash")
+ }
}
var info metainfo.Info
if err := bencode.Unmarshal(b, &info); err != nil {
return fmt.Errorf("error unmarshalling info bytes: %s", err)
}
- if info.HasV2() {
- t.infoHashV2.Set(v2Hash)
+ if !t.infoHashV2.Ok && info.HasV2() {
+ v2Hash.Set(infohash_v2.HashBytes(b))
+ t.infoHashV2.Set(v2Hash.Unwrap())
}
t.metadataBytes = b
t.metadataCompletedChunks = nil
if t.displayName != "" {
return t.displayName
}
- return "infohash:" + t.infoHash.HexString()
+ return "infohash:" + t.canonicalShortInfohash().HexString()
}
func (t *Torrent) pieceState(index pieceIndex) (ret PieceState) {
}
func (t *Torrent) writeStatus(w io.Writer) {
- fmt.Fprintf(w, "Infohash: %s\n", t.infoHash.HexString())
+ if t.infoHash.Ok {
+ fmt.Fprintf(w, "Infohash: %s\n", t.infoHash.Value.HexString())
+ }
+ if t.infoHashV2.Ok {
+ fmt.Fprintf(w, "Infohash v2: %s\n", t.infoHashV2.Value.HexString())
+ }
fmt.Fprintf(w, "Metadata length: %d\n", t.metadataSize())
if !t.haveInfo() {
fmt.Fprintf(w, "Metadata have: ")
t.logRunHandshookConn(pc, false, log.Debug)
}
-func (t *Torrent) startWebsocketAnnouncer(u url.URL) torrentTrackerAnnouncer {
- wtc, release := t.cl.websocketTrackers.Get(u.String(), t.infoHash)
- // This needs to run before the Torrent is dropped from the Client, to prevent a new webtorrent.TrackerClient for
- // the same info hash before the old one is cleaned up.
+func (t *Torrent) startWebsocketAnnouncer(u url.URL, shortInfohash [20]byte) torrentTrackerAnnouncer {
+ wtc, release := t.cl.websocketTrackers.Get(u.String(), shortInfohash)
+ // This needs to run before the Torrent is dropped from the Client, to prevent a new
+ // webtorrent.TrackerClient for the same info hash before the old one is cleaned up.
t.onClose = append(t.onClose, release)
wst := websocketTrackerStatus{u, wtc}
go func() {
- err := wtc.Announce(tracker.Started, t.infoHash)
+ err := wtc.Announce(tracker.Started, shortInfohash)
if err != nil {
t.logger.WithDefaultLevel(log.Warning).Printf(
"error in initial announce to %q: %v",
t.startScrapingTracker(u.String())
return
}
- if _, ok := t.trackerAnnouncers[_url]; ok {
+ if t.infoHash.Ok {
+ t.startScrapingTrackerWithInfohash(u, _url, t.infoHash.Value)
+ }
+ if t.infoHashV2.Ok {
+ t.startScrapingTrackerWithInfohash(u, _url, *t.infoHashV2.Value.ToShort())
+ }
+}
+
+func (t *Torrent) startScrapingTrackerWithInfohash(u *url.URL, urlStr string, shortInfohash [20]byte) {
+ announcerKey := torrentTrackerAnnouncerKey{
+ shortInfohash: shortInfohash,
+ url: urlStr,
+ }
+ if _, ok := t.trackerAnnouncers[announcerKey]; ok {
return
}
sl := func() torrentTrackerAnnouncer {
if t.cl.config.DisableWebtorrent {
return nil
}
- return t.startWebsocketAnnouncer(*u)
+ return t.startWebsocketAnnouncer(*u, shortInfohash)
case "udp4":
if t.cl.config.DisableIPv4Peers || t.cl.config.DisableIPv4 {
return nil
}
}
newAnnouncer := &trackerScraper{
+ shortInfohash: shortInfohash,
u: *u,
t: t,
lookupTrackerIp: t.cl.config.LookupTrackerIp,
if sl == nil {
return
}
- if t.trackerAnnouncers == nil {
- t.trackerAnnouncers = make(map[string]torrentTrackerAnnouncer)
+ g.MakeMapIfNil(&t.trackerAnnouncers)
+ if g.MapInsert(t.trackerAnnouncers, announcerKey, sl).Ok {
+ panic("tracker announcer already exists")
}
- t.trackerAnnouncers[_url] = sl
}
// Adds and starts tracker scrapers for tracker URLs that aren't already
// Returns an AnnounceRequest with fields filled out to defaults and current
// values.
-func (t *Torrent) announceRequest(event tracker.AnnounceEvent) tracker.AnnounceRequest {
+func (t *Torrent) announceRequest(
+ event tracker.AnnounceEvent,
+ shortInfohash [20]byte,
+) tracker.AnnounceRequest {
// Note that IPAddress is not set. It's set for UDP inside the tracker code, since it's
// dependent on the network in use.
return tracker.AnnounceRequest{
Event: event,
NumWant: func() int32 {
if t.wantPeers() && len(t.cl.dialers) > 0 {
- return 200 // Win has UDP packet limit. See: https://github.com/anacrolix/torrent/issues/764
+ // Windozer has UDP packet limit. See:
+ // https://github.com/anacrolix/torrent/issues/764
+ return 200
} else {
return 0
}
}(),
Port: uint16(t.cl.incomingPeerPort()),
PeerId: t.cl.peerID,
- InfoHash: t.infoHash,
+ InfoHash: shortInfohash,
Key: t.cl.announceKey(),
// The following are vaguely described in BEP 3.
}
// Announce using the provided DHT server. Peers are consumed automatically. done is closed when the
-// announce ends. stop will force the announce to end.
+// announce ends. stop will force the announce to end. This interface is really old-school, and
+// calls a private one that is much more modern. Both v1 and v2 info hashes are announced if they
+// exist.
func (t *Torrent) AnnounceToDht(s DhtServer) (done <-chan struct{}, stop func(), err error) {
- ps, err := s.Announce(t.infoHash, t.cl.incomingPeerPort(), true)
- if err != nil {
- return
+ var ihs [][20]byte
+ t.cl.lock()
+ t.eachShortInfohash(func(short [20]byte) {
+ ihs = append(ihs, short)
+ })
+ t.cl.unlock()
+ ctx, stop := context.WithCancel(context.Background())
+ eg, ctx := errgroup.WithContext(ctx)
+ for _, ih := range ihs {
+ var ann DhtAnnounce
+ ann, err = s.Announce(ih, t.cl.incomingPeerPort(), true)
+ if err != nil {
+ stop()
+ return
+ }
+ eg.Go(func() error {
+ return t.dhtAnnounceConsumer(ctx, ann)
+ })
}
_done := make(chan struct{})
done = _done
- stop = ps.Close
go func() {
- t.consumeDhtAnnouncePeers(ps.Peers())
- close(_done)
+ defer stop()
+ defer close(_done)
+ // Won't this race?
+ err = eg.Wait()
}()
return
}
+// Announce using the provided DHT server. Peers are consumed automatically. done is closed when the
+// announce ends. stop will force the announce to end.
+func (t *Torrent) dhtAnnounceConsumer(
+ ctx context.Context,
+ ps DhtAnnounce,
+) (
+ err error,
+) {
+ defer ps.Close()
+ done := make(chan struct{})
+ go func() {
+ defer close(done)
+ t.consumeDhtAnnouncePeers(ps.Peers())
+ }()
+ select {
+ case <-ctx.Done():
+ return context.Cause(ctx)
+ case <-done:
+ return nil
+ }
+}
+
func (t *Torrent) timeboxedAnnounceToDht(s DhtServer) error {
_, stop, err := t.AnnounceToDht(s)
if err != nil {
defer cl.rUnlock()
return t.dialTimeout()
}
+
+func (t *Torrent) canonicalShortInfohash() *infohash.T {
+ if t.infoHash.Ok {
+ return &t.infoHash.Value
+ }
+ return t.infoHashV2.UnwrapPtr().ToShort()
+}
+
+func (t *Torrent) eachShortInfohash(each func(short [20]byte)) {
+ if t.infoHash.Ok {
+ each(t.infoHash.Value)
+ }
+ if t.infoHashV2.Ok {
+ each(*t.infoHashV2.Value.ToShort())
+ }
+}
func TestTorrentString(t *testing.T) {
tor := &Torrent{}
+ tor.infoHash.Ok = true
+ tor.infoHash.Value[0] = 1
s := tor.InfoHash().HexString()
- if s != "0000000000000000000000000000000000000000" {
+ if s != "0100000000000000000000000000000000000000" {
t.FailNow()
}
}
)
cl := &Client{config: TestingConfig(b)}
cl.initLogger()
- t := cl.newTorrent(metainfo.Hash{}, nil)
+ t := cl.newTorrentForTesting()
require.NoError(b, t.setInfo(&metainfo.Info{
Pieces: make([]byte, metainfo.HashSize*numPieces),
PieceLength: pieceLength,
// Announces a torrent to a tracker at regular intervals, when peers are
// required.
type trackerScraper struct {
+ shortInfohash [20]byte
u url.URL
t *Torrent
lastAnnounce trackerAnnounceResult
// Return how long to wait before trying again. For most errors, we return 5
// minutes, a relatively quick turn around for DNS changes.
-func (me *trackerScraper) announce(ctx context.Context, event tracker.AnnounceEvent) (ret trackerAnnounceResult) {
+func (me *trackerScraper) announce(
+ ctx context.Context,
+ event tracker.AnnounceEvent,
+) (ret trackerAnnounceResult) {
defer func() {
ret.Completed = time.Now()
}()
return
}
me.t.cl.rLock()
- req := me.t.announceRequest(event)
+ req := me.t.announceRequest(event, me.shortInfohash)
me.t.cl.rUnlock()
// The default timeout works well as backpressure on concurrent access to the tracker. Since
// we're passing our own Context now, we will include that timeout ourselves to maintain similar
"encoding"
"encoding/hex"
"fmt"
+ "unsafe"
"github.com/multiformats/go-multihash"
}
// 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
+func (t *T) ToShort() (short *infohash.T) {
+ return (*infohash.T)(unsafe.Pointer(t))
}
var (
return
}
+func (t *T) IsZero() bool {
+ return *t == T{}
+}
+
var (
_ encoding.TextUnmarshaler = (*T)(nil)
_ encoding.TextMarshaler = T{}