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