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