// through legitimate channels.
dopplegangerAddrs map[string]struct{}
badPeerIPs map[netip.Addr]struct{}
- torrents map[InfoHash]*Torrent
+ // All Torrents once.
+ torrents map[*Torrent]struct{}
+ // All Torrents by their short infohashes (v1 if valid, and truncated v2 if valid). Unless the
+ // info has been obtained, there's no knowing if an infohash belongs to v1 or v2.
+ torrentsByShortHash map[InfoHash]*Torrent
+
pieceRequestOrder map[interface{}]*request_strategy.PieceRequestOrder
acceptLimiter map[ipStr]int
func (cl *Client) init(cfg *ClientConfig) {
cl.config = cfg
g.MakeMap(&cl.dopplegangerAddrs)
- cl.torrents = make(map[metainfo.Hash]*Torrent)
+ g.MakeMap(&cl.torrentsByShortHash)
+ g.MakeMap(&cl.torrents)
+ cl.torrentsByShortHash = make(map[metainfo.Hash]*Torrent)
cl.activeAnnounceLimiter.SlotsPerKey = 2
cl.event.L = cl.locker()
cl.ipBlockList = cfg.IPBlocklist
) {
cl.lock()
defer cl.unlock()
- t, ok := cl.torrents[infoHash]
+ t, ok := cl.torrentsByShortHash[infoHash]
if !ok {
return tracker.AnnounceRequest{}, errors.New("torrent not tracked by client")
}
OnConn: func(dc datachannel.ReadWriteCloser, dcc webtorrent.DataChannelContext) {
cl.lock()
defer cl.unlock()
- t, ok := cl.torrents[dcc.InfoHash]
+ t, ok := cl.torrentsByShortHash[dcc.InfoHash]
if !ok {
cl.logger.WithDefaultLevel(log.Warning).Printf(
"got webrtc conn for unloaded torrent with infohash %x",
cl.lock()
defer cl.unlock()
cl.dialers = append(cl.dialers, d)
- for _, t := range cl.torrents {
+ for t := range cl.torrents {
t.openNewConns()
}
}
func (cl *Client) Close() (errs []error) {
var closeGroup sync.WaitGroup // For concurrent cleanup to complete before returning
cl.lock()
- for _, t := range cl.torrents {
+ for t := range cl.torrents {
err := t.close(&closeGroup)
if err != nil {
errs = append(errs, err)
if cl.config.AlwaysWantConns {
return true
}
- for _, t := range cl.torrents {
+ for t := range cl.torrents {
if t.wantIncomingConns() {
return true
}
func (cl *Client) Torrent(ih metainfo.Hash) (t *Torrent, ok bool) {
cl.rLock()
defer cl.rUnlock()
- t, ok = cl.torrents[ih]
+ t, ok = cl.torrentsByShortHash[ih]
return
}
-func (cl *Client) torrent(ih metainfo.Hash) *Torrent {
- return cl.torrents[ih]
-}
-
type DialResult struct {
Conn net.Conn
Dialer Dialer
if cl.numHalfOpen < 0 {
panic("should not be possible")
}
- for _, t := range cl.torrents {
+ for t := range cl.torrents {
t.openNewConns()
}
}
func (cl *Client) countHalfOpenFromTorrents() (count int) {
- for _, t := range cl.torrents {
+ for t := range cl.torrents {
count += t.numHalfOpenAttempts()
}
return
defer cl.rUnlock()
if false { // Emulate the bug from #114
var firstIh InfoHash
- for ih := range cl.torrents {
+ for ih := range cl.torrentsByShortHash {
firstIh = ih
break
}
- for range cl.torrents {
+ for range cl.torrentsByShortHash {
if !f(firstIh[:]) {
break
}
}
return
}
- for ih := range cl.torrents {
+ for ih := range cl.torrentsByShortHash {
if !f(ih[:]) {
break
}
func (cl *Client) receiveHandshakes(c *PeerConn) (t *Torrent, err error) {
defer perf.ScopeTimerErr(&err)()
var rw io.ReadWriter
- rw, c.headerEncrypted, c.cryptoMethod, err = handleEncryption(c.rw(), cl.handshakeReceiverSecretKeys(), cl.config.HeaderObfuscationPolicy, cl.config.CryptoSelector)
+ rw, c.headerEncrypted, c.cryptoMethod, err = handleEncryption(
+ c.rw(),
+ cl.handshakeReceiverSecretKeys(),
+ cl.config.HeaderObfuscationPolicy,
+ cl.config.CryptoSelector,
+ )
c.setRW(rw)
if err == nil || err == mse.ErrNoSecretKeyMatch {
if c.headerEncrypted {
return nil, fmt.Errorf("during bt handshake: %w", err)
}
cl.lock()
- t = cl.torrents[ih]
+ t = cl.torrentsByShortHash[ih]
cl.unlock()
return
}
) (t *Torrent, new bool) {
cl.lock()
defer cl.unlock()
- t, ok := cl.torrents[infoHash]
+ t, ok := cl.torrentsByShortHash[infoHash]
if ok {
return
}
go t.dhtAnnouncer(s)
}
})
- cl.torrents[infoHash] = t
+ cl.torrentsByShortHash[infoHash] = t
+ cl.torrents[t] = struct{}{}
cl.clearAcceptLimits()
t.updateWantPeersEvent()
// Tickle Client.waitAccept, new torrent may want conns.
infoHash := opts.InfoHash
cl.lock()
defer cl.unlock()
- t, ok := cl.torrents[infoHash]
+ t, ok := cl.torrentsByShortHash[infoHash]
if ok {
return
}
+ if opts.InfoHashV2.Ok {
+ t, ok = cl.torrentsByShortHash[*opts.InfoHashV2.Value.ToShort()]
+ if ok {
+ return
+ }
+ }
new = true
t = cl.newTorrentOpt(opts)
go t.dhtAnnouncer(s)
}
})
- cl.torrents[infoHash] = t
+ cl.torrentsByShortHash[infoHash] = t
+ cl.torrents[t] = struct{}{}
t.setInfoBytesLocked(opts.InfoBytes)
cl.clearAcceptLimits()
t.updateWantPeersEvent()
return t.AddPieceLayers(spec.PieceLayers)
}
-func (cl *Client) dropTorrent(infoHash metainfo.Hash, wg *sync.WaitGroup) (err error) {
- t, ok := cl.torrents[infoHash]
- if !ok {
- err = fmt.Errorf("no such torrent")
- return
- }
+func (cl *Client) dropTorrent(t *Torrent, wg *sync.WaitGroup) (err error) {
+ t.eachShortInfohash(func(short [20]byte) {
+ delete(cl.torrentsByShortHash, short)
+ })
err = t.close(wg)
- delete(cl.torrents, infoHash)
+ delete(cl.torrents, t)
return
}
func (cl *Client) allTorrentsCompleted() bool {
- for _, t := range cl.torrents {
+ for t := range cl.torrents {
if !t.haveInfo() {
return false
}
}
func (cl *Client) torrentsAsSlice() (ret []*Torrent) {
- for _, t := range cl.torrents {
+ for t := range cl.torrents {
ret = append(ret, t)
}
return
panic(ip)
}
g.MakeMapIfNilAndSet(&cl.badPeerIPs, ipAddr, struct{}{})
- for _, t := range cl.torrents {
+ for t := range cl.torrents {
t.iterPeers(func(p *Peer) {
if p.remoteIp().Equal(ip) {
t.logger.Levelf(log.Warning, "dropping peer %v with banned ip %v", p, ip)
func (cl *Client) onDHTAnnouncePeer(ih metainfo.Hash, ip net.IP, port int, portOk bool) {
cl.lock()
defer cl.unlock()
- t := cl.torrent(ih)
+ t := cl.torrentsByShortHash[ih]
if t == nil {
return
}
})
}
-// Called when metadata for a torrent becomes available.
-func (t *Torrent) setInfoBytesLocked(b []byte) error {
- 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")
+// Checks the info bytes hash to expected values. Fills in any missing infohashes.
+func (t *Torrent) hashInfoBytes(b []byte, info *metainfo.Info) error {
+ v1Hash := infohash.HashBytes(b)
+ v2Hash := infohash_v2.HashBytes(b)
+ cl := t.cl
+ if t.infoHash.Ok && !t.infoHashV2.Ok {
+ if v1Hash == t.infoHash.Value {
+ if info.HasV2() {
+ t.infoHashV2.Set(v2Hash)
+ cl.torrentsByShortHash[*v2Hash.ToShort()] = t
+ }
+ } else if *v2Hash.ToShort() == t.infoHash.Value {
+ if !info.HasV2() {
+ return errors.New("invalid v2 info")
+ }
+ t.infoHashV2.Set(v2Hash)
+ if info.HasV1() {
+ cl.torrentsByShortHash[v1Hash] = t
+ }
+ }
+ } else if t.infoHash.Ok && t.infoHashV2.Ok {
+ if v1Hash != t.infoHash.Value {
+ return errors.New("incorrect v1 infohash")
+ }
+ if v2Hash != t.infoHashV2.Value {
+ return errors.New("incorrect v2 infohash")
+ }
+ } else if !t.infoHash.Ok && t.infoHashV2.Ok {
+ if v2Hash != t.infoHashV2.Value {
+ return errors.New("incorrect v2 infohash")
}
+ if info.HasV1() {
+ t.infoHash.Set(v1Hash)
+ cl.torrentsByShortHash[v1Hash] = t
+ }
+ } else {
+ panic("no expected infohashes")
}
+ return nil
+}
+
+// Called when metadata for a torrent becomes available.
+func (t *Torrent) setInfoBytesLocked(b []byte) (err error) {
var info metainfo.Info
- if err := bencode.Unmarshal(b, &info); err != nil {
- return fmt.Errorf("error unmarshalling info bytes: %s", err)
+ err = bencode.Unmarshal(b, &info)
+ if err != nil {
+ err = fmt.Errorf("unmarshalling info bytes: %w", err)
+ return
}
- if !t.infoHashV2.Ok && info.HasV2() {
- v2Hash.Set(infohash_v2.HashBytes(b))
- t.infoHashV2.Set(v2Hash.Unwrap())
+ err = t.hashInfoBytes(b, &info)
+ if err != nil {
+ return
}
t.metadataBytes = b
t.metadataCompletedChunks = nil