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