// 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
}