github.com/anacrolix/torrent@v1.61.0/client_test.go (about)

     1  package torrent
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"io"
     8  	"math/rand"
     9  	"net"
    10  	"net/netip"
    11  	"os"
    12  	"path/filepath"
    13  	"reflect"
    14  	"testing"
    15  	"testing/iotest"
    16  	"time"
    17  
    18  	"github.com/anacrolix/dht/v2"
    19  	"github.com/anacrolix/log"
    20  	"github.com/anacrolix/missinggo/v2"
    21  	"github.com/anacrolix/missinggo/v2/filecache"
    22  	"github.com/go-quicktest/qt"
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  
    26  	"github.com/anacrolix/torrent/bencode"
    27  	"github.com/anacrolix/torrent/internal/testutil"
    28  	"github.com/anacrolix/torrent/iplist"
    29  	"github.com/anacrolix/torrent/metainfo"
    30  	"github.com/anacrolix/torrent/storage"
    31  )
    32  
    33  func TestClientDefault(t *testing.T) {
    34  	cl, err := NewClient(TestingConfig(t))
    35  	require.NoError(t, err)
    36  	require.Empty(t, cl.Close())
    37  }
    38  
    39  func TestClientNilConfig(t *testing.T) {
    40  	// The default config will put crap in the working directory.
    41  	origDir, _ := os.Getwd()
    42  	defer os.Chdir(origDir)
    43  	os.Chdir(t.TempDir())
    44  	cl, err := NewClient(nil)
    45  	require.NoError(t, err)
    46  	require.Empty(t, cl.Close())
    47  }
    48  
    49  func TestAddDropTorrent(t *testing.T) {
    50  	cl, err := NewClient(TestingConfig(t))
    51  	require.NoError(t, err)
    52  	defer cl.Close()
    53  	dir, mi := testutil.GreetingTestTorrent()
    54  	defer os.RemoveAll(dir)
    55  	tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
    56  	require.NoError(t, err)
    57  	assert.True(t, new)
    58  	tt.SetMaxEstablishedConns(0)
    59  	tt.SetMaxEstablishedConns(1)
    60  	tt.Drop()
    61  }
    62  
    63  func TestAddTorrentNoSupportedTrackerSchemes(t *testing.T) {
    64  	// TODO?
    65  	t.SkipNow()
    66  }
    67  
    68  func TestAddTorrentNoUsableURLs(t *testing.T) {
    69  	// TODO?
    70  	t.SkipNow()
    71  }
    72  
    73  func TestAddPeersToUnknownTorrent(t *testing.T) {
    74  	// TODO?
    75  	t.SkipNow()
    76  }
    77  
    78  func TestPieceHashSize(t *testing.T) {
    79  	assert.Equal(t, 20, pieceHash.Size())
    80  }
    81  
    82  func TestTorrentInitialState(t *testing.T) {
    83  	dir, mi := testutil.GreetingTestTorrent()
    84  	defer os.RemoveAll(dir)
    85  	var cl Client
    86  	cl.init(TestingConfig(t))
    87  	tor := cl.newTorrent(
    88  		mi.HashInfoBytes(),
    89  		storage.NewFileWithCompletion(t.TempDir(), storage.NewMapPieceCompletion()),
    90  	)
    91  	tor.setChunkSize(2)
    92  	tor.cl.lock()
    93  	err := tor.setInfoBytesLocked(mi.InfoBytes)
    94  	tor.cl.unlock()
    95  	require.NoError(t, err)
    96  	require.Len(t, tor.pieces, 3)
    97  	tor.pendAllChunkSpecs(0)
    98  	tor.cl.lock()
    99  	assert.EqualValues(t, 3, tor.pieceNumPendingChunks(0))
   100  	tor.cl.unlock()
   101  	assert.EqualValues(t, ChunkSpec{4, 1}, chunkIndexSpec(2, tor.pieceLength(0), tor.chunkSize))
   102  }
   103  
   104  func TestReducedDialTimeout(t *testing.T) {
   105  	cfg := NewDefaultClientConfig()
   106  	for _, _case := range []struct {
   107  		Max             time.Duration
   108  		HalfOpenLimit   int
   109  		PendingPeers    int
   110  		ExpectedReduced time.Duration
   111  	}{
   112  		{cfg.NominalDialTimeout, 40, 0, cfg.NominalDialTimeout},
   113  		{cfg.NominalDialTimeout, 40, 1, cfg.NominalDialTimeout},
   114  		{cfg.NominalDialTimeout, 40, 39, cfg.NominalDialTimeout},
   115  		{cfg.NominalDialTimeout, 40, 40, cfg.NominalDialTimeout / 2},
   116  		{cfg.NominalDialTimeout, 40, 80, cfg.NominalDialTimeout / 3},
   117  		{cfg.NominalDialTimeout, 40, 4000, cfg.NominalDialTimeout / 101},
   118  	} {
   119  		reduced := reducedDialTimeout(cfg.MinDialTimeout, _case.Max, _case.HalfOpenLimit, _case.PendingPeers)
   120  		expected := _case.ExpectedReduced
   121  		if expected < cfg.MinDialTimeout {
   122  			expected = cfg.MinDialTimeout
   123  		}
   124  		if reduced != expected {
   125  			t.Fatalf("expected %s, got %s", _case.ExpectedReduced, reduced)
   126  		}
   127  	}
   128  }
   129  
   130  func TestAddDropManyTorrents(t *testing.T) {
   131  	cl, err := NewClient(TestingConfig(t))
   132  	require.NoError(t, err)
   133  	defer cl.Close()
   134  	for i := range 1000 {
   135  		var opts AddTorrentOpts
   136  		binary.PutVarint(opts.InfoHash[:], int64(i+1))
   137  		tt, new := cl.AddTorrentOpt(opts)
   138  		assert.True(t, new)
   139  		defer tt.Drop()
   140  	}
   141  }
   142  
   143  func fileCachePieceResourceStorage(fc *filecache.Cache) storage.ClientImpl {
   144  	return storage.NewResourcePiecesOpts(
   145  		fc.AsResourceProvider(),
   146  		storage.ResourcePiecesOpts{
   147  			LeaveIncompleteChunks: true,
   148  		},
   149  	)
   150  }
   151  
   152  func TestMergingTrackersByAddingSpecs(t *testing.T) {
   153  	cl, err := NewClient(TestingConfig(t))
   154  	require.NoError(t, err)
   155  	defer cl.Close()
   156  	spec := TorrentSpec{}
   157  	rand.Read(spec.InfoHash[:])
   158  	T, new, _ := cl.AddTorrentSpec(&spec)
   159  	if !new {
   160  		t.FailNow()
   161  	}
   162  	spec.Trackers = [][]string{{"http://a"}, {"udp://b"}}
   163  	_, new, _ = cl.AddTorrentSpec(&spec)
   164  	assert.False(t, new)
   165  	assert.EqualValues(t, [][]string{{"http://a"}, {"udp://b"}}, T.announceList)
   166  	// Because trackers are disabled in TestingConfig.
   167  	assert.EqualValues(t, 0, len(T.trackerAnnouncers))
   168  }
   169  
   170  // We read from a piece which is marked completed, but is missing data.
   171  func TestCompletedPieceWrongSize(t *testing.T) {
   172  	cfg := TestingConfig(t)
   173  	cfg.DefaultStorage = badStorage{}
   174  	cl, err := NewClient(cfg)
   175  	require.NoError(t, err)
   176  	defer cl.Close()
   177  	info := metainfo.Info{
   178  		PieceLength: 15,
   179  		Pieces:      make([]byte, 20),
   180  		Files: []metainfo.FileInfo{
   181  			{Path: []string{"greeting"}, Length: 13},
   182  		},
   183  	}
   184  	b, err := bencode.Marshal(info)
   185  	require.NoError(t, err)
   186  	tt, new := cl.AddTorrentOpt(AddTorrentOpts{
   187  		InfoBytes: b,
   188  		InfoHash:  metainfo.HashBytes(b),
   189  	})
   190  	defer tt.Drop()
   191  	assert.True(t, new)
   192  	r := tt.NewReader()
   193  	defer r.Close()
   194  	r.SetContext(t.Context())
   195  	qt.Check(t, qt.IsNil(iotest.TestReader(r, []byte(testutil.GreetingFileContents))))
   196  }
   197  
   198  func BenchmarkAddLargeTorrent(b *testing.B) {
   199  	cfg := TestingConfig(b)
   200  	cfg.DisableTCP = true
   201  	cfg.DisableUTP = true
   202  	cl, err := NewClient(cfg)
   203  	require.NoError(b, err)
   204  	defer cl.Close()
   205  	b.ReportAllocs()
   206  	for i := 0; i < b.N; i += 1 {
   207  		t, err := cl.AddTorrentFromFile("testdata/bootstrap.dat.torrent")
   208  		if err != nil {
   209  			b.Fatal(err)
   210  		}
   211  		t.Drop()
   212  	}
   213  }
   214  
   215  func TestResponsive(t *testing.T) {
   216  	seederDataDir, mi := testutil.GreetingTestTorrent()
   217  	defer os.RemoveAll(seederDataDir)
   218  	cfg := TestingConfig(t)
   219  	cfg.Seed = true
   220  	cfg.DataDir = seederDataDir
   221  	seeder, err := NewClient(cfg)
   222  	require.Nil(t, err)
   223  	defer seeder.Close()
   224  	seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
   225  	seederTorrent.VerifyData()
   226  	leecherDataDir := t.TempDir()
   227  	cfg = TestingConfig(t)
   228  	cfg.DataDir = leecherDataDir
   229  	leecher, err := NewClient(cfg)
   230  	require.Nil(t, err)
   231  	defer leecher.Close()
   232  	leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
   233  		ret = TorrentSpecFromMetaInfo(mi)
   234  		ret.ChunkSize = 2
   235  		return
   236  	}())
   237  	leecherTorrent.AddClientPeer(seeder)
   238  	reader := leecherTorrent.NewReader()
   239  	defer reader.Close()
   240  	reader.SetReadahead(0)
   241  	reader.SetResponsive()
   242  	b := make([]byte, 2)
   243  	_, err = reader.Seek(3, io.SeekStart)
   244  	require.NoError(t, err)
   245  	_, err = io.ReadFull(reader, b)
   246  	assert.Nil(t, err)
   247  	assert.EqualValues(t, "lo", string(b))
   248  	_, err = reader.Seek(11, io.SeekStart)
   249  	require.NoError(t, err)
   250  	n, err := io.ReadFull(reader, b)
   251  	assert.Nil(t, err)
   252  	assert.EqualValues(t, 2, n)
   253  	assert.EqualValues(t, "d\n", string(b))
   254  }
   255  
   256  // TestResponsive was the first test to fail if uTP is disabled and TCP sockets dial from the
   257  // listening port.
   258  func TestResponsiveTcpOnly(t *testing.T) {
   259  	seederDataDir, mi := testutil.GreetingTestTorrent()
   260  	defer os.RemoveAll(seederDataDir)
   261  	cfg := TestingConfig(t)
   262  	cfg.DisableUTP = true
   263  	cfg.Seed = true
   264  	cfg.DataDir = seederDataDir
   265  	seeder, err := NewClient(cfg)
   266  	require.Nil(t, err)
   267  	defer seeder.Close()
   268  	seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
   269  	seederTorrent.VerifyData()
   270  	leecherDataDir := t.TempDir()
   271  	cfg = TestingConfig(t)
   272  	cfg.DataDir = leecherDataDir
   273  	leecher, err := NewClient(cfg)
   274  	require.Nil(t, err)
   275  	defer leecher.Close()
   276  	leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
   277  		ret = TorrentSpecFromMetaInfo(mi)
   278  		ret.ChunkSize = 2
   279  		return
   280  	}())
   281  	leecherTorrent.AddClientPeer(seeder)
   282  	reader := leecherTorrent.NewReader()
   283  	defer reader.Close()
   284  	reader.SetReadahead(0)
   285  	reader.SetResponsive()
   286  	b := make([]byte, 2)
   287  	_, err = reader.Seek(3, io.SeekStart)
   288  	require.NoError(t, err)
   289  	_, err = io.ReadFull(reader, b)
   290  	assert.Nil(t, err)
   291  	assert.EqualValues(t, "lo", string(b))
   292  	_, err = reader.Seek(11, io.SeekStart)
   293  	require.NoError(t, err)
   294  	n, err := io.ReadFull(reader, b)
   295  	assert.Nil(t, err)
   296  	assert.EqualValues(t, 2, n)
   297  	assert.EqualValues(t, "d\n", string(b))
   298  }
   299  
   300  func TestTorrentDroppedDuringResponsiveRead(t *testing.T) {
   301  	seederDataDir, mi := testutil.GreetingTestTorrent()
   302  	defer os.RemoveAll(seederDataDir)
   303  	cfg := TestingConfig(t)
   304  	cfg.Seed = true
   305  	cfg.DataDir = seederDataDir
   306  	seeder, err := NewClient(cfg)
   307  	require.Nil(t, err)
   308  	defer seeder.Close()
   309  	seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
   310  	seederTorrent.VerifyData()
   311  	leecherDataDir := t.TempDir()
   312  	cfg = TestingConfig(t)
   313  	cfg.DataDir = leecherDataDir
   314  	leecher, err := NewClient(cfg)
   315  	require.Nil(t, err)
   316  	defer leecher.Close()
   317  	leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
   318  		ret = TorrentSpecFromMetaInfo(mi)
   319  		ret.ChunkSize = 2
   320  		return
   321  	}())
   322  	leecherTorrent.AddClientPeer(seeder)
   323  	rdr := leecherTorrent.NewReader()
   324  	t.Cleanup(func() { rdr.Close() })
   325  	rdr.SetReadahead(0)
   326  	rdr.SetResponsive()
   327  	b := make([]byte, 2)
   328  	_, err = rdr.Seek(3, io.SeekStart)
   329  	require.NoError(t, err)
   330  	_, err = io.ReadFull(rdr, b)
   331  	assert.Nil(t, err)
   332  	assert.EqualValues(t, "lo", string(b))
   333  	_, err = rdr.Seek(11, io.SeekStart)
   334  	require.NoError(t, err)
   335  	leecherTorrent.Drop()
   336  	n, err := rdr.Read(b)
   337  	qt.Assert(t, qt.Equals(err, errTorrentClosed))
   338  	assert.EqualValues(t, 0, n)
   339  }
   340  
   341  func TestDhtInheritBlocklist(t *testing.T) {
   342  	ipl := iplist.New(nil)
   343  	require.NotNil(t, ipl)
   344  	cfg := TestingConfig(t)
   345  	cfg.IPBlocklist = ipl
   346  	cfg.NoDHT = false
   347  	cl, err := NewClient(cfg)
   348  	require.NoError(t, err)
   349  	defer cl.Close()
   350  	numServers := 0
   351  	cl.eachDhtServer(func(s DhtServer) {
   352  		t.Log(s)
   353  		assert.Equal(t, ipl, s.(AnacrolixDhtServerWrapper).Server.IPBlocklist())
   354  		numServers++
   355  	})
   356  	qt.Assert(t, qt.Not(qt.Equals(numServers, 0)))
   357  }
   358  
   359  // Check that stuff is merged in subsequent AddTorrentSpec for the same
   360  // infohash.
   361  func TestAddTorrentSpecMerging(t *testing.T) {
   362  	cl, err := NewClient(TestingConfig(t))
   363  	require.NoError(t, err)
   364  	defer cl.Close()
   365  	dir, mi := testutil.GreetingTestTorrent()
   366  	defer os.RemoveAll(dir)
   367  	tt, new := cl.AddTorrentOpt(AddTorrentOpts{
   368  		InfoHash: mi.HashInfoBytes(),
   369  	})
   370  	require.True(t, new)
   371  	require.Nil(t, tt.Info())
   372  	_, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
   373  	require.NoError(t, err)
   374  	require.False(t, new)
   375  	require.NotNil(t, tt.Info())
   376  }
   377  
   378  func TestTorrentDroppedBeforeGotInfo(t *testing.T) {
   379  	dir, mi := testutil.GreetingTestTorrent()
   380  	os.RemoveAll(dir)
   381  	cl, _ := NewClient(TestingConfig(t))
   382  	defer cl.Close()
   383  	tt, _ := cl.AddTorrentOpt(AddTorrentOpts{
   384  		InfoHash: mi.HashInfoBytes(),
   385  	})
   386  	tt.Drop()
   387  	assert.EqualValues(t, 0, len(cl.Torrents()))
   388  	select {
   389  	case <-tt.GotInfo():
   390  		t.FailNow()
   391  	default:
   392  	}
   393  }
   394  
   395  func writeTorrentData(ts *storage.Torrent, info metainfo.Info, b []byte) {
   396  	for i := 0; i < info.NumPieces(); i += 1 {
   397  		p := info.Piece(i)
   398  		ts.Piece(p).WriteAt(b[p.Offset():p.Offset()+p.Length()], 0)
   399  	}
   400  }
   401  
   402  func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool, csf func(*filecache.Cache) storage.ClientImpl) {
   403  	fileCacheDir := t.TempDir()
   404  	fileCache, err := filecache.NewCache(fileCacheDir)
   405  	require.NoError(t, err)
   406  	greetingDataTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
   407  	defer os.RemoveAll(greetingDataTempDir)
   408  	filePieceStore := csf(fileCache)
   409  	info, err := greetingMetainfo.UnmarshalInfo()
   410  	require.NoError(t, err)
   411  	ih := greetingMetainfo.HashInfoBytes()
   412  	greetingData, err := storage.NewClient(filePieceStore).OpenTorrent(context.Background(), &info, ih)
   413  	require.NoError(t, err)
   414  	writeTorrentData(greetingData, info, []byte(testutil.GreetingFileContents))
   415  	// require.Equal(t, len(testutil.GreetingFileContents), written)
   416  	// require.NoError(t, err)
   417  	for i := 0; i < info.NumPieces(); i++ {
   418  		p := info.Piece(i)
   419  		if alreadyCompleted {
   420  			require.NoError(t, greetingData.Piece(p).MarkComplete())
   421  		}
   422  	}
   423  	cfg := TestingConfig(t)
   424  	// TODO: Disable network option?
   425  	cfg.DisableTCP = true
   426  	cfg.DisableUTP = true
   427  	cfg.DefaultStorage = filePieceStore
   428  	cl, err := NewClient(cfg)
   429  	require.NoError(t, err)
   430  	defer cl.Close()
   431  	tt, err := cl.AddTorrent(greetingMetainfo)
   432  	require.NoError(t, err)
   433  	psrs := tt.PieceStateRuns()
   434  	assert.Len(t, psrs, 1)
   435  	assert.EqualValues(t, 3, psrs[0].Length)
   436  	assert.Equal(t, alreadyCompleted, psrs[0].Complete)
   437  	if alreadyCompleted {
   438  		r := tt.NewReader()
   439  		qt.Check(t, qt.IsNil(iotest.TestReader(r, []byte(testutil.GreetingFileContents))))
   440  	}
   441  }
   442  
   443  func TestAddTorrentPiecesAlreadyCompleted(t *testing.T) {
   444  	testAddTorrentPriorPieceCompletion(t, true, fileCachePieceResourceStorage)
   445  }
   446  
   447  func TestAddTorrentPiecesNotAlreadyCompleted(t *testing.T) {
   448  	testAddTorrentPriorPieceCompletion(t, false, fileCachePieceResourceStorage)
   449  }
   450  
   451  func TestAddMetainfoWithNodes(t *testing.T) {
   452  	cfg := TestingConfig(t)
   453  	cfg.ListenHost = func(string) string { return "" }
   454  	cfg.NoDHT = false
   455  	cfg.DhtStartingNodes = func(string) dht.StartingNodesGetter { return func() ([]dht.Addr, error) { return nil, nil } }
   456  	// For now, we want to just jam the nodes into the table, without verifying them first. Also the
   457  	// DHT code doesn't support mixing secure and insecure nodes if security is enabled (yet).
   458  	// cfg.DHTConfig.NoSecurity = true
   459  	cl, err := NewClient(cfg)
   460  	require.NoError(t, err)
   461  	defer cl.Close()
   462  	sum := func() (ret int64) {
   463  		cl.eachDhtServer(func(s DhtServer) {
   464  			ret += s.Stats().(dht.ServerStats).OutboundQueriesAttempted
   465  		})
   466  		return
   467  	}
   468  	assert.EqualValues(t, 0, sum())
   469  	tt, err := cl.AddTorrentFromFile("metainfo/testdata/issue_65a.torrent")
   470  	require.NoError(t, err)
   471  	// Nodes are not added or exposed in Torrent's metainfo. We just randomly
   472  	// check if the announce-list is here instead. TODO: Add nodes.
   473  	assert.Len(t, tt.announceList, 5)
   474  	// There are 6 nodes in the torrent file.
   475  	for sum() != int64(6*len(cl.dhtServers)) {
   476  		time.Sleep(time.Millisecond)
   477  	}
   478  }
   479  
   480  type testDownloadCancelParams struct {
   481  	SetLeecherStorageCapacity bool
   482  	LeecherStorageCapacity    int64
   483  	Cancel                    bool
   484  }
   485  
   486  func testDownloadCancel(t *testing.T, ps testDownloadCancelParams) {
   487  	greetingTempDir, mi := testutil.GreetingTestTorrent()
   488  	defer os.RemoveAll(greetingTempDir)
   489  	cfg := TestingConfig(t)
   490  	cfg.Seed = true
   491  	cfg.DataDir = greetingTempDir
   492  	seeder, err := NewClient(cfg)
   493  	require.NoError(t, err)
   494  	defer seeder.Close()
   495  	defer testutil.ExportStatusWriter(seeder, "s", t)()
   496  	seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
   497  	seederTorrent.VerifyData()
   498  	leecherDataDir := t.TempDir()
   499  	fc, err := filecache.NewCache(leecherDataDir)
   500  	require.NoError(t, err)
   501  	if ps.SetLeecherStorageCapacity {
   502  		fc.SetCapacity(ps.LeecherStorageCapacity)
   503  	}
   504  	cfg.DefaultStorage = storage.NewResourcePieces(fc.AsResourceProvider())
   505  	cfg.DataDir = leecherDataDir
   506  	leecher, err := NewClient(cfg)
   507  	require.NoError(t, err)
   508  	defer leecher.Close()
   509  	defer testutil.ExportStatusWriter(leecher, "l", t)()
   510  	leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
   511  		ret = TorrentSpecFromMetaInfo(mi)
   512  		ret.ChunkSize = 2
   513  		return
   514  	}())
   515  	require.NoError(t, err)
   516  	assert.True(t, new)
   517  	psc := leecherGreeting.SubscribePieceStateChanges()
   518  	defer psc.Close()
   519  
   520  	leecherGreeting.cl.lock()
   521  	leecherGreeting.downloadPiecesLocked(0, leecherGreeting.numPieces())
   522  	if ps.Cancel {
   523  		leecherGreeting.cancelPiecesLocked(0, leecherGreeting.NumPieces(), "")
   524  	}
   525  	leecherGreeting.cl.unlock()
   526  	done := make(chan struct{})
   527  	defer close(done)
   528  	go leecherGreeting.AddClientPeer(seeder)
   529  	completes := make(map[int]bool, 3)
   530  	expected := func() map[int]bool {
   531  		if ps.Cancel {
   532  			return map[int]bool{0: false, 1: false, 2: false}
   533  		} else {
   534  			return map[int]bool{0: true, 1: true, 2: true}
   535  		}
   536  	}()
   537  	for !reflect.DeepEqual(completes, expected) {
   538  		v := <-psc.Values
   539  		completes[v.Index] = v.Complete
   540  	}
   541  }
   542  
   543  func TestTorrentDownloadAll(t *testing.T) {
   544  	testDownloadCancel(t, testDownloadCancelParams{})
   545  }
   546  
   547  func TestTorrentDownloadAllThenCancel(t *testing.T) {
   548  	testDownloadCancel(t, testDownloadCancelParams{
   549  		Cancel: true,
   550  	})
   551  }
   552  
   553  // Ensure that it's an error for a peer to send an invalid have message.
   554  func TestPeerInvalidHave(t *testing.T) {
   555  	cfg := TestingConfig(t)
   556  	cfg.DropMutuallyCompletePeers = false
   557  	cl, err := NewClient(cfg)
   558  	require.NoError(t, err)
   559  	defer cl.Close()
   560  	info := metainfo.Info{
   561  		PieceLength: 1,
   562  		Pieces:      make([]byte, 20),
   563  		Files:       []metainfo.FileInfo{{Length: 1}},
   564  	}
   565  	infoBytes, err := bencode.Marshal(info)
   566  	require.NoError(t, err)
   567  	tt, _new := cl.AddTorrentOpt(AddTorrentOpts{
   568  		InfoBytes: infoBytes,
   569  		InfoHash:  metainfo.HashBytes(infoBytes),
   570  		Storage:   badStorage{},
   571  	})
   572  	assert.True(t, _new)
   573  	defer tt.Drop()
   574  	cn := &PeerConn{Peer: Peer{
   575  		t:         tt,
   576  		callbacks: &cfg.Callbacks,
   577  	}}
   578  	tt.conns[cn] = struct{}{}
   579  	cn.legacyPeerImpl = cn
   580  	cl.lock()
   581  	defer cl.unlock()
   582  	assert.NoError(t, cn.peerSentHave(0))
   583  	assert.Error(t, cn.peerSentHave(1))
   584  }
   585  
   586  func TestPieceCompletedInStorageButNotClient(t *testing.T) {
   587  	greetingTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
   588  	defer os.RemoveAll(greetingTempDir)
   589  	cfg := TestingConfig(t)
   590  	cfg.DataDir = greetingTempDir
   591  	seeder, err := NewClient(TestingConfig(t))
   592  	require.NoError(t, err)
   593  	defer seeder.Close()
   594  	_, new := seeder.AddTorrentOpt(AddTorrentOpts{
   595  		InfoBytes: greetingMetainfo.InfoBytes,
   596  		InfoHash:  greetingMetainfo.HashInfoBytes(),
   597  	})
   598  	qt.Check(t, qt.IsNil(err))
   599  	qt.Check(t, qt.IsTrue(new))
   600  }
   601  
   602  // Check that when the listen port is 0, all the protocols listened on have
   603  // the same port, and it isn't zero.
   604  func TestClientDynamicListenPortAllProtocols(t *testing.T) {
   605  	cl, err := NewClient(TestingConfig(t))
   606  	require.NoError(t, err)
   607  	defer cl.Close()
   608  	port := cl.LocalPort()
   609  	assert.NotEqual(t, 0, port)
   610  	cl.eachListener(func(s Listener) bool {
   611  		assert.Equal(t, port, missinggo.AddrPort(s.Addr()))
   612  		return true
   613  	})
   614  }
   615  
   616  func TestClientDynamicListenTCPOnly(t *testing.T) {
   617  	cfg := TestingConfig(t)
   618  	cfg.DisableUTP = true
   619  	cfg.DisableTCP = false
   620  	cl, err := NewClient(cfg)
   621  	require.NoError(t, err)
   622  	defer cl.Close()
   623  	assert.NotEqual(t, 0, cl.LocalPort())
   624  }
   625  
   626  func TestClientDynamicListenUTPOnly(t *testing.T) {
   627  	cfg := TestingConfig(t)
   628  	cfg.DisableTCP = true
   629  	cfg.DisableUTP = false
   630  	cl, err := NewClient(cfg)
   631  	require.NoError(t, err)
   632  	defer cl.Close()
   633  	assert.NotEqual(t, 0, cl.LocalPort())
   634  }
   635  
   636  func totalConns(tts []*Torrent) (ret int) {
   637  	for _, tt := range tts {
   638  		tt.cl.lock()
   639  		ret += len(tt.conns)
   640  		tt.cl.unlock()
   641  	}
   642  	return
   643  }
   644  
   645  func TestSetMaxEstablishedConn(t *testing.T) {
   646  	var tts []*Torrent
   647  	ih := testutil.GreetingMetaInfo().HashInfoBytes()
   648  	cfg := TestingConfig(t)
   649  	cfg.DisableAcceptRateLimiting = true
   650  	cfg.DropDuplicatePeerIds = true
   651  	for i := 0; i < 3; i += 1 {
   652  		cl, err := NewClient(cfg)
   653  		require.NoError(t, err)
   654  		defer cl.Close()
   655  		tt, _ := cl.AddTorrentInfoHash(ih)
   656  		tt.SetMaxEstablishedConns(2)
   657  		defer testutil.ExportStatusWriter(cl, fmt.Sprintf("%d", i), t)()
   658  		tts = append(tts, tt)
   659  	}
   660  	addPeers := func() {
   661  		for _, tt := range tts {
   662  			for _, _tt := range tts {
   663  				// if tt != _tt {
   664  				tt.AddClientPeer(_tt.cl)
   665  				// }
   666  			}
   667  		}
   668  	}
   669  	waitTotalConns := func(num int) {
   670  		for totalConns(tts) != num {
   671  			addPeers()
   672  			time.Sleep(time.Millisecond)
   673  		}
   674  	}
   675  	addPeers()
   676  	waitTotalConns(6)
   677  	tts[0].SetMaxEstablishedConns(1)
   678  	waitTotalConns(4)
   679  	tts[0].SetMaxEstablishedConns(0)
   680  	waitTotalConns(2)
   681  	tts[0].SetMaxEstablishedConns(1)
   682  	addPeers()
   683  	waitTotalConns(4)
   684  	tts[0].SetMaxEstablishedConns(2)
   685  	addPeers()
   686  	waitTotalConns(6)
   687  }
   688  
   689  // Creates a file containing its own name as data. Make a metainfo from that, adds it to the given
   690  // client, and returns a magnet link.
   691  func makeMagnet(t *testing.T, cl *Client, dir, name string) string {
   692  	os.MkdirAll(dir, 0o770)
   693  	file, err := os.Create(filepath.Join(dir, name))
   694  	require.NoError(t, err)
   695  	file.Write([]byte(name))
   696  	file.Close()
   697  	mi := metainfo.MetaInfo{}
   698  	mi.SetDefaults()
   699  	info := metainfo.Info{PieceLength: 256 * 1024}
   700  	err = info.BuildFromFilePath(filepath.Join(dir, name))
   701  	require.NoError(t, err)
   702  	mi.InfoBytes, err = bencode.Marshal(info)
   703  	require.NoError(t, err)
   704  	magnet := mi.Magnet(nil, &info).String()
   705  	tr, err := cl.AddTorrent(&mi)
   706  	require.NoError(t, err)
   707  	require.True(t, tr.Seeding())
   708  	tr.VerifyData()
   709  	return magnet
   710  }
   711  
   712  // https://github.com/anacrolix/torrent/issues/114
   713  func TestMultipleTorrentsWithEncryption(t *testing.T) {
   714  	testSeederLeecherPair(
   715  		t,
   716  		func(cfg *ClientConfig) {
   717  			cfg.HeaderObfuscationPolicy.Preferred = true
   718  			cfg.HeaderObfuscationPolicy.RequirePreferred = true
   719  		},
   720  		func(cfg *ClientConfig) {
   721  			cfg.HeaderObfuscationPolicy.RequirePreferred = false
   722  		},
   723  	)
   724  }
   725  
   726  // Test that the leecher can download a torrent in its entirety from the seeder. Note that the
   727  // seeder config is done first.
   728  func testSeederLeecherPair(t *testing.T, seeder, leecher func(*ClientConfig)) {
   729  	cfg := TestingConfig(t)
   730  	cfg.Seed = true
   731  	cfg.DataDir = filepath.Join(cfg.DataDir, "server")
   732  	os.Mkdir(cfg.DataDir, 0o755)
   733  	seeder(cfg)
   734  	server, err := NewClient(cfg)
   735  	require.NoError(t, err)
   736  	defer server.Close()
   737  	defer testutil.ExportStatusWriter(server, "s", t)()
   738  	magnet1 := makeMagnet(t, server, cfg.DataDir, "test1")
   739  	// Extra torrents are added to test the seeder having to match incoming obfuscated headers
   740  	// against more than one torrent. See issue #114
   741  	makeMagnet(t, server, cfg.DataDir, "test2")
   742  	for i := 0; i < 100; i++ {
   743  		makeMagnet(t, server, cfg.DataDir, fmt.Sprintf("test%d", i+3))
   744  	}
   745  	cfg = TestingConfig(t)
   746  	cfg.DataDir = filepath.Join(cfg.DataDir, "client")
   747  	leecher(cfg)
   748  	client, err := NewClient(cfg)
   749  	require.NoError(t, err)
   750  	defer client.Close()
   751  	defer testutil.ExportStatusWriter(client, "c", t)()
   752  	tr, err := client.AddMagnet(magnet1)
   753  	require.NoError(t, err)
   754  	tr.AddClientPeer(server)
   755  	<-tr.GotInfo()
   756  	tr.DownloadAll()
   757  	client.WaitAll()
   758  }
   759  
   760  // This appears to be the situation with the S3 BitTorrent client.
   761  func TestObfuscatedHeaderFallbackSeederDisallowsLeecherPrefers(t *testing.T) {
   762  	// Leecher prefers obfuscation, but the seeder does not allow it.
   763  	testSeederLeecherPair(
   764  		t,
   765  		func(cfg *ClientConfig) {
   766  			cfg.HeaderObfuscationPolicy.Preferred = false
   767  			cfg.HeaderObfuscationPolicy.RequirePreferred = true
   768  		},
   769  		func(cfg *ClientConfig) {
   770  			cfg.HeaderObfuscationPolicy.Preferred = true
   771  			cfg.HeaderObfuscationPolicy.RequirePreferred = false
   772  		},
   773  	)
   774  }
   775  
   776  func TestObfuscatedHeaderFallbackSeederRequiresLeecherPrefersNot(t *testing.T) {
   777  	// Leecher prefers no obfuscation, but the seeder enforces it.
   778  	testSeederLeecherPair(
   779  		t,
   780  		func(cfg *ClientConfig) {
   781  			cfg.HeaderObfuscationPolicy.Preferred = true
   782  			cfg.HeaderObfuscationPolicy.RequirePreferred = true
   783  		},
   784  		func(cfg *ClientConfig) {
   785  			cfg.HeaderObfuscationPolicy.Preferred = false
   786  			cfg.HeaderObfuscationPolicy.RequirePreferred = false
   787  		},
   788  	)
   789  }
   790  
   791  func TestClientAddressInUse(t *testing.T) {
   792  	s, _ := NewUtpSocket("udp", "localhost:50007", nil, log.Default)
   793  	if s != nil {
   794  		defer s.Close()
   795  	}
   796  	cfg := TestingConfig(t).SetListenAddr("localhost:50007")
   797  	cfg.DisableUTP = false
   798  	cl, err := NewClient(cfg)
   799  	if err == nil {
   800  		assert.Nil(t, cl.Close())
   801  	}
   802  	require.Error(t, err)
   803  	require.Nil(t, cl)
   804  }
   805  
   806  func TestClientHasDhtServersWhenUtpDisabled(t *testing.T) {
   807  	cc := TestingConfig(t)
   808  	cc.DisableUTP = true
   809  	cc.NoDHT = false
   810  	cl, err := NewClient(cc)
   811  	require.NoError(t, err)
   812  	defer cl.Close()
   813  	assert.NotEmpty(t, cl.DhtServers())
   814  }
   815  
   816  func TestClientDisabledImplicitNetworksButDhtEnabled(t *testing.T) {
   817  	cfg := TestingConfig(t)
   818  	cfg.DisableTCP = true
   819  	cfg.DisableUTP = true
   820  	cfg.NoDHT = false
   821  	cl, err := NewClient(cfg)
   822  	require.NoError(t, err)
   823  	defer cl.Close()
   824  	assert.Empty(t, cl.listeners)
   825  	assert.NotEmpty(t, cl.DhtServers())
   826  }
   827  
   828  func TestBadPeerIpPort(t *testing.T) {
   829  	for _, tc := range []struct {
   830  		title      string
   831  		ip         net.IP
   832  		port       int
   833  		expectedOk bool
   834  		setup      func(*Client)
   835  	}{
   836  		{"empty both", nil, 0, true, func(*Client) {}},
   837  		{"empty/nil ip", nil, 6666, true, func(*Client) {}},
   838  		{
   839  			"empty port",
   840  			net.ParseIP("127.0.0.1/32"),
   841  			0, true,
   842  			func(*Client) {},
   843  		},
   844  		{
   845  			"in doppleganger addresses",
   846  			net.ParseIP("127.0.0.1/32"),
   847  			2322,
   848  			true,
   849  			func(cl *Client) {
   850  				cl.dopplegangerAddrs["10.0.0.1:2322"] = struct{}{}
   851  			},
   852  		},
   853  		{
   854  			"in IP block list",
   855  			net.ParseIP("10.0.0.1"),
   856  			2322,
   857  			true,
   858  			func(cl *Client) {
   859  				cl.ipBlockList = iplist.New([]iplist.Range{
   860  					{First: net.ParseIP("10.0.0.1"), Last: net.ParseIP("10.0.0.255")},
   861  				})
   862  			},
   863  		},
   864  		{
   865  			"in bad peer IPs",
   866  			net.ParseIP("10.0.0.1"),
   867  			2322,
   868  			true,
   869  			func(cl *Client) {
   870  				ipAddr, ok := netip.AddrFromSlice(net.ParseIP("10.0.0.1"))
   871  				require.True(t, ok)
   872  				cl.badPeerIPs = map[netip.Addr]struct{}{}
   873  				cl.badPeerIPs[ipAddr] = struct{}{}
   874  			},
   875  		},
   876  		{
   877  			"good",
   878  			net.ParseIP("10.0.0.1"),
   879  			2322,
   880  			false,
   881  			func(cl *Client) {},
   882  		},
   883  	} {
   884  		t.Run(tc.title, func(t *testing.T) {
   885  			cfg := TestingConfig(t)
   886  			cfg.DisableTCP = true
   887  			cfg.DisableUTP = true
   888  			cfg.NoDHT = false
   889  			cl, err := NewClient(cfg)
   890  			require.NoError(t, err)
   891  			defer cl.Close()
   892  
   893  			tc.setup(cl)
   894  			require.Equal(t, tc.expectedOk, cl.badPeerIPPort(tc.ip, tc.port))
   895  		})
   896  	}
   897  }
   898  
   899  // https://github.com/anacrolix/torrent/issues/837
   900  func TestClientConfigSetHandlerNotIgnored(t *testing.T) {
   901  	cfg := TestingConfig(t)
   902  	cfg.Logger.SetHandlers(log.DiscardHandler)
   903  	cl, err := NewClient(cfg)
   904  	qt.Assert(t, qt.IsNil(err))
   905  	defer cl.Close()
   906  	qt.Assert(t, qt.HasLen(cl.logger.Handlers, 1))
   907  	h := cl.logger.Handlers[0].(log.StreamHandler)
   908  	qt.Check(t, qt.Equals(h.W, io.Discard))
   909  }
   910  
   911  func TestDroppedTorrentsNotReturned(t *testing.T) {
   912  	cl := newTestingClient(t)
   913  	tt, _ := cl.AddTorrentOpt(testingAddTorrentOpts)
   914  	tt1, ok := cl.Torrent(tt.InfoHash())
   915  	qt.Check(t, qt.IsTrue(ok))
   916  	qt.Check(t, qt.Equals(tt1, tt))
   917  	qt.Check(t, qt.SliceContains(cl.Torrents(), tt))
   918  	tt.Drop()
   919  	tt1, ok = cl.Torrent(tt.InfoHash())
   920  	qt.Check(t, qt.IsFalse(ok))
   921  	qt.Check(t, qt.HasLen(cl.Torrents(), 0))
   922  }