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