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