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