]> Sergey Matveev's repositories - btrtrc.git/blob - client_test.go
Improve metainfo.MetaInfo.Magnet interface and add tests
[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"
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 TestBoltPieceCompletionClosedWhenClientClosed(t *testing.T) {
44         cfg := TestingConfig(t)
45         pc, err := storage.NewBoltPieceCompletion(cfg.DataDir)
46         require.NoError(t, err)
47         ci := storage.NewFileWithCompletion(cfg.DataDir, pc)
48         defer ci.Close()
49         cfg.DefaultStorage = ci
50         cl, err := NewClient(cfg)
51         require.NoError(t, err)
52         cl.Close()
53         // And again, https://github.com/anacrolix/torrent/issues/158
54         cl, err = NewClient(cfg)
55         require.NoError(t, err)
56         cl.Close()
57 }
58
59 func TestAddDropTorrent(t *testing.T) {
60         cl, err := NewClient(TestingConfig(t))
61         require.NoError(t, err)
62         defer cl.Close()
63         dir, mi := testutil.GreetingTestTorrent()
64         defer os.RemoveAll(dir)
65         tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
66         require.NoError(t, err)
67         assert.True(t, new)
68         tt.SetMaxEstablishedConns(0)
69         tt.SetMaxEstablishedConns(1)
70         tt.Drop()
71 }
72
73 func TestAddTorrentNoSupportedTrackerSchemes(t *testing.T) {
74         // TODO?
75         t.SkipNow()
76 }
77
78 func TestAddTorrentNoUsableURLs(t *testing.T) {
79         // TODO?
80         t.SkipNow()
81 }
82
83 func TestAddPeersToUnknownTorrent(t *testing.T) {
84         // TODO?
85         t.SkipNow()
86 }
87
88 func TestPieceHashSize(t *testing.T) {
89         assert.Equal(t, 20, pieceHash.Size())
90 }
91
92 func TestTorrentInitialState(t *testing.T) {
93         dir, mi := testutil.GreetingTestTorrent()
94         defer os.RemoveAll(dir)
95         cl := &Client{
96                 config: TestingConfig(t),
97         }
98         cl.initLogger()
99         tor := cl.newTorrent(
100                 mi.HashInfoBytes(),
101                 storage.NewFileWithCompletion(t.TempDir(), storage.NewMapPieceCompletion()),
102         )
103         tor.setChunkSize(2)
104         tor.cl.lock()
105         err := tor.setInfoBytes(mi.InfoBytes)
106         tor.cl.unlock()
107         require.NoError(t, err)
108         require.Len(t, tor.pieces, 3)
109         tor.pendAllChunkSpecs(0)
110         tor.cl.lock()
111         assert.EqualValues(t, 3, tor.pieceNumPendingChunks(0))
112         tor.cl.unlock()
113         assert.EqualValues(t, ChunkSpec{4, 1}, chunkIndexSpec(2, tor.pieceLength(0), tor.chunkSize))
114 }
115
116 func TestReducedDialTimeout(t *testing.T) {
117         cfg := NewDefaultClientConfig()
118         for _, _case := range []struct {
119                 Max             time.Duration
120                 HalfOpenLimit   int
121                 PendingPeers    int
122                 ExpectedReduced time.Duration
123         }{
124                 {cfg.NominalDialTimeout, 40, 0, cfg.NominalDialTimeout},
125                 {cfg.NominalDialTimeout, 40, 1, cfg.NominalDialTimeout},
126                 {cfg.NominalDialTimeout, 40, 39, cfg.NominalDialTimeout},
127                 {cfg.NominalDialTimeout, 40, 40, cfg.NominalDialTimeout / 2},
128                 {cfg.NominalDialTimeout, 40, 80, cfg.NominalDialTimeout / 3},
129                 {cfg.NominalDialTimeout, 40, 4000, cfg.NominalDialTimeout / 101},
130         } {
131                 reduced := reducedDialTimeout(cfg.MinDialTimeout, _case.Max, _case.HalfOpenLimit, _case.PendingPeers)
132                 expected := _case.ExpectedReduced
133                 if expected < cfg.MinDialTimeout {
134                         expected = cfg.MinDialTimeout
135                 }
136                 if reduced != expected {
137                         t.Fatalf("expected %s, got %s", _case.ExpectedReduced, reduced)
138                 }
139         }
140 }
141
142 func TestAddDropManyTorrents(t *testing.T) {
143         cl, err := NewClient(TestingConfig(t))
144         require.NoError(t, err)
145         defer cl.Close()
146         for i := range iter.N(1000) {
147                 var spec TorrentSpec
148                 binary.PutVarint(spec.InfoHash[:], int64(i))
149                 tt, new, err := cl.AddTorrentSpec(&spec)
150                 assert.NoError(t, err)
151                 assert.True(t, new)
152                 defer tt.Drop()
153         }
154 }
155
156 func fileCachePieceResourceStorage(fc *filecache.Cache) storage.ClientImpl {
157         return storage.NewResourcePiecesOpts(
158                 fc.AsResourceProvider(),
159                 storage.ResourcePiecesOpts{
160                         LeaveIncompleteChunks: true,
161                 },
162         )
163 }
164
165 func TestMergingTrackersByAddingSpecs(t *testing.T) {
166         cl, err := NewClient(TestingConfig(t))
167         require.NoError(t, err)
168         defer cl.Close()
169         spec := TorrentSpec{}
170         T, new, _ := cl.AddTorrentSpec(&spec)
171         if !new {
172                 t.FailNow()
173         }
174         spec.Trackers = [][]string{{"http://a"}, {"udp://b"}}
175         _, new, _ = cl.AddTorrentSpec(&spec)
176         assert.False(t, new)
177         assert.EqualValues(t, [][]string{{"http://a"}, {"udp://b"}}, T.metainfo.AnnounceList)
178         // Because trackers are disabled in TestingConfig.
179         assert.EqualValues(t, 0, len(T.trackerAnnouncers))
180 }
181
182 // We read from a piece which is marked completed, but is missing data.
183 func TestCompletedPieceWrongSize(t *testing.T) {
184         cfg := TestingConfig(t)
185         cfg.DefaultStorage = badStorage{}
186         cl, err := NewClient(cfg)
187         require.NoError(t, err)
188         defer cl.Close()
189         info := metainfo.Info{
190                 PieceLength: 15,
191                 Pieces:      make([]byte, 20),
192                 Files: []metainfo.FileInfo{
193                         {Path: []string{"greeting"}, Length: 13},
194                 },
195         }
196         b, err := bencode.Marshal(info)
197         require.NoError(t, err)
198         tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
199                 InfoBytes: b,
200                 InfoHash:  metainfo.HashBytes(b),
201         })
202         require.NoError(t, err)
203         defer tt.Drop()
204         assert.True(t, new)
205         r := tt.NewReader()
206         defer r.Close()
207         quicktest.Check(t, iotest.TestReader(r, []byte(testutil.GreetingFileContents)), quicktest.IsNil)
208 }
209
210 func BenchmarkAddLargeTorrent(b *testing.B) {
211         cfg := TestingConfig(b)
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(t)
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(t)
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(t)
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(t)
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(t)
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(t))
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(t))
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(t)
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                 quicktest.Check(t, iotest.TestReader(r, []byte(testutil.GreetingFileContents)), quicktest.IsNil)
415         }
416 }
417
418 func TestAddTorrentPiecesAlreadyCompleted(t *testing.T) {
419         testAddTorrentPriorPieceCompletion(t, true, fileCachePieceResourceStorage)
420 }
421
422 func TestAddTorrentPiecesNotAlreadyCompleted(t *testing.T) {
423         testAddTorrentPriorPieceCompletion(t, false, fileCachePieceResourceStorage)
424 }
425
426 func TestAddMetainfoWithNodes(t *testing.T) {
427         cfg := TestingConfig(t)
428         cfg.ListenHost = func(string) string { return "" }
429         cfg.NoDHT = false
430         cfg.DhtStartingNodes = func(string) dht.StartingNodesGetter { return func() ([]dht.Addr, error) { return nil, nil } }
431         // For now, we want to just jam the nodes into the table, without verifying them first. Also the
432         // DHT code doesn't support mixing secure and insecure nodes if security is enabled (yet).
433         // cfg.DHTConfig.NoSecurity = true
434         cl, err := NewClient(cfg)
435         require.NoError(t, err)
436         defer cl.Close()
437         sum := func() (ret int64) {
438                 cl.eachDhtServer(func(s DhtServer) {
439                         ret += s.Stats().(dht.ServerStats).OutboundQueriesAttempted
440                 })
441                 return
442         }
443         assert.EqualValues(t, 0, sum())
444         tt, err := cl.AddTorrentFromFile("metainfo/testdata/issue_65a.torrent")
445         require.NoError(t, err)
446         // Nodes are not added or exposed in Torrent's metainfo. We just randomly
447         // check if the announce-list is here instead. TODO: Add nodes.
448         assert.Len(t, tt.metainfo.AnnounceList, 5)
449         // There are 6 nodes in the torrent file.
450         for sum() != int64(6*len(cl.dhtServers)) {
451                 time.Sleep(time.Millisecond)
452         }
453 }
454
455 type testDownloadCancelParams struct {
456         SetLeecherStorageCapacity bool
457         LeecherStorageCapacity    int64
458         Cancel                    bool
459 }
460
461 func testDownloadCancel(t *testing.T, ps testDownloadCancelParams) {
462         greetingTempDir, mi := testutil.GreetingTestTorrent()
463         defer os.RemoveAll(greetingTempDir)
464         cfg := TestingConfig(t)
465         cfg.Seed = true
466         cfg.DataDir = greetingTempDir
467         seeder, err := NewClient(cfg)
468         require.NoError(t, err)
469         defer seeder.Close()
470         defer testutil.ExportStatusWriter(seeder, "s", t)()
471         seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
472         seederTorrent.VerifyData()
473         leecherDataDir, err := ioutil.TempDir("", "")
474         require.NoError(t, err)
475         defer os.RemoveAll(leecherDataDir)
476         fc, err := filecache.NewCache(leecherDataDir)
477         require.NoError(t, err)
478         if ps.SetLeecherStorageCapacity {
479                 fc.SetCapacity(ps.LeecherStorageCapacity)
480         }
481         cfg.DefaultStorage = storage.NewResourcePieces(fc.AsResourceProvider())
482         cfg.DataDir = leecherDataDir
483         leecher, err := NewClient(cfg)
484         require.NoError(t, err)
485         defer leecher.Close()
486         defer testutil.ExportStatusWriter(leecher, "l", t)()
487         leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
488                 ret = TorrentSpecFromMetaInfo(mi)
489                 ret.ChunkSize = 2
490                 return
491         }())
492         require.NoError(t, err)
493         assert.True(t, new)
494         psc := leecherGreeting.SubscribePieceStateChanges()
495         defer psc.Close()
496
497         leecherGreeting.cl.lock()
498         leecherGreeting.downloadPiecesLocked(0, leecherGreeting.numPieces())
499         if ps.Cancel {
500                 leecherGreeting.cancelPiecesLocked(0, leecherGreeting.NumPieces())
501         }
502         leecherGreeting.cl.unlock()
503         done := make(chan struct{})
504         defer close(done)
505         go leecherGreeting.AddClientPeer(seeder)
506         completes := make(map[int]bool, 3)
507         expected := func() map[int]bool {
508                 if ps.Cancel {
509                         return map[int]bool{0: false, 1: false, 2: false}
510                 } else {
511                         return map[int]bool{0: true, 1: true, 2: true}
512                 }
513         }()
514         for !reflect.DeepEqual(completes, expected) {
515                 _v := <-psc.Values
516                 v := _v.(PieceStateChange)
517                 completes[v.Index] = v.Complete
518         }
519 }
520
521 func TestTorrentDownloadAll(t *testing.T) {
522         testDownloadCancel(t, testDownloadCancelParams{})
523 }
524
525 func TestTorrentDownloadAllThenCancel(t *testing.T) {
526         testDownloadCancel(t, testDownloadCancelParams{
527                 Cancel: true,
528         })
529 }
530
531 // Ensure that it's an error for a peer to send an invalid have message.
532 func TestPeerInvalidHave(t *testing.T) {
533         cfg := TestingConfig(t)
534         cfg.DropMutuallyCompletePeers = false
535         cl, err := NewClient(cfg)
536         require.NoError(t, err)
537         defer cl.Close()
538         info := metainfo.Info{
539                 PieceLength: 1,
540                 Pieces:      make([]byte, 20),
541                 Files:       []metainfo.FileInfo{{Length: 1}},
542         }
543         infoBytes, err := bencode.Marshal(info)
544         require.NoError(t, err)
545         tt, _new, err := cl.AddTorrentSpec(&TorrentSpec{
546                 InfoBytes: infoBytes,
547                 InfoHash:  metainfo.HashBytes(infoBytes),
548                 Storage:   badStorage{},
549         })
550         require.NoError(t, err)
551         assert.True(t, _new)
552         defer tt.Drop()
553         cn := &PeerConn{Peer: Peer{
554                 t: tt,
555         }}
556         cn.peerImpl = cn
557         assert.NoError(t, cn.peerSentHave(0))
558         assert.Error(t, cn.peerSentHave(1))
559 }
560
561 func TestPieceCompletedInStorageButNotClient(t *testing.T) {
562         greetingTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
563         defer os.RemoveAll(greetingTempDir)
564         cfg := TestingConfig(t)
565         cfg.DataDir = greetingTempDir
566         seeder, err := NewClient(TestingConfig(t))
567         require.NoError(t, err)
568         seeder.AddTorrentSpec(&TorrentSpec{
569                 InfoBytes: greetingMetainfo.InfoBytes,
570         })
571 }
572
573 // Check that when the listen port is 0, all the protocols listened on have
574 // the same port, and it isn't zero.
575 func TestClientDynamicListenPortAllProtocols(t *testing.T) {
576         cl, err := NewClient(TestingConfig(t))
577         require.NoError(t, err)
578         defer cl.Close()
579         port := cl.LocalPort()
580         assert.NotEqual(t, 0, port)
581         cl.eachListener(func(s Listener) bool {
582                 assert.Equal(t, port, missinggo.AddrPort(s.Addr()))
583                 return true
584         })
585 }
586
587 func TestClientDynamicListenTCPOnly(t *testing.T) {
588         cfg := TestingConfig(t)
589         cfg.DisableUTP = true
590         cfg.DisableTCP = false
591         cl, err := NewClient(cfg)
592         require.NoError(t, err)
593         defer cl.Close()
594         assert.NotEqual(t, 0, cl.LocalPort())
595 }
596
597 func TestClientDynamicListenUTPOnly(t *testing.T) {
598         cfg := TestingConfig(t)
599         cfg.DisableTCP = true
600         cfg.DisableUTP = false
601         cl, err := NewClient(cfg)
602         require.NoError(t, err)
603         defer cl.Close()
604         assert.NotEqual(t, 0, cl.LocalPort())
605 }
606
607 func totalConns(tts []*Torrent) (ret int) {
608         for _, tt := range tts {
609                 tt.cl.lock()
610                 ret += len(tt.conns)
611                 tt.cl.unlock()
612         }
613         return
614 }
615
616 func TestSetMaxEstablishedConn(t *testing.T) {
617         var tts []*Torrent
618         ih := testutil.GreetingMetaInfo().HashInfoBytes()
619         cfg := TestingConfig(t)
620         cfg.DisableAcceptRateLimiting = true
621         cfg.DropDuplicatePeerIds = true
622         for i := range iter.N(3) {
623                 cl, err := NewClient(cfg)
624                 require.NoError(t, err)
625                 defer cl.Close()
626                 tt, _ := cl.AddTorrentInfoHash(ih)
627                 tt.SetMaxEstablishedConns(2)
628                 defer testutil.ExportStatusWriter(cl, fmt.Sprintf("%d", i), t)()
629                 tts = append(tts, tt)
630         }
631         addPeers := func() {
632                 for _, tt := range tts {
633                         for _, _tt := range tts {
634                                 // if tt != _tt {
635                                 tt.AddClientPeer(_tt.cl)
636                                 // }
637                         }
638                 }
639         }
640         waitTotalConns := func(num int) {
641                 for totalConns(tts) != num {
642                         addPeers()
643                         time.Sleep(time.Millisecond)
644                 }
645         }
646         addPeers()
647         waitTotalConns(6)
648         tts[0].SetMaxEstablishedConns(1)
649         waitTotalConns(4)
650         tts[0].SetMaxEstablishedConns(0)
651         waitTotalConns(2)
652         tts[0].SetMaxEstablishedConns(1)
653         addPeers()
654         waitTotalConns(4)
655         tts[0].SetMaxEstablishedConns(2)
656         addPeers()
657         waitTotalConns(6)
658 }
659
660 // Creates a file containing its own name as data. Make a metainfo from that, adds it to the given
661 // client, and returns a magnet link.
662 func makeMagnet(t *testing.T, cl *Client, dir string, name string) string {
663         os.MkdirAll(dir, 0770)
664         file, err := os.Create(filepath.Join(dir, name))
665         require.NoError(t, err)
666         file.Write([]byte(name))
667         file.Close()
668         mi := metainfo.MetaInfo{}
669         mi.SetDefaults()
670         info := metainfo.Info{PieceLength: 256 * 1024}
671         err = info.BuildFromFilePath(filepath.Join(dir, name))
672         require.NoError(t, err)
673         mi.InfoBytes, err = bencode.Marshal(info)
674         require.NoError(t, err)
675         magnet := mi.Magnet(nil, &info).String()
676         tr, err := cl.AddTorrent(&mi)
677         require.NoError(t, err)
678         require.True(t, tr.Seeding())
679         tr.VerifyData()
680         return magnet
681 }
682
683 // https://github.com/anacrolix/torrent/issues/114
684 func TestMultipleTorrentsWithEncryption(t *testing.T) {
685         testSeederLeecherPair(
686                 t,
687                 func(cfg *ClientConfig) {
688                         cfg.HeaderObfuscationPolicy.Preferred = true
689                         cfg.HeaderObfuscationPolicy.RequirePreferred = true
690                 },
691                 func(cfg *ClientConfig) {
692                         cfg.HeaderObfuscationPolicy.RequirePreferred = false
693                 },
694         )
695 }
696
697 // Test that the leecher can download a torrent in its entirety from the seeder. Note that the
698 // seeder config is done first.
699 func testSeederLeecherPair(t *testing.T, seeder func(*ClientConfig), leecher func(*ClientConfig)) {
700         cfg := TestingConfig(t)
701         cfg.Seed = true
702         cfg.DataDir = filepath.Join(cfg.DataDir, "server")
703         os.Mkdir(cfg.DataDir, 0755)
704         seeder(cfg)
705         server, err := NewClient(cfg)
706         require.NoError(t, err)
707         defer server.Close()
708         defer testutil.ExportStatusWriter(server, "s", t)()
709         magnet1 := makeMagnet(t, server, cfg.DataDir, "test1")
710         // Extra torrents are added to test the seeder having to match incoming obfuscated headers
711         // against more than one torrent. See issue #114
712         makeMagnet(t, server, cfg.DataDir, "test2")
713         for i := 0; i < 100; i++ {
714                 makeMagnet(t, server, cfg.DataDir, fmt.Sprintf("test%d", i+2))
715         }
716         cfg = TestingConfig(t)
717         cfg.DataDir = filepath.Join(cfg.DataDir, "client")
718         leecher(cfg)
719         client, err := NewClient(cfg)
720         require.NoError(t, err)
721         defer client.Close()
722         defer testutil.ExportStatusWriter(client, "c", t)()
723         tr, err := client.AddMagnet(magnet1)
724         require.NoError(t, err)
725         tr.AddClientPeer(server)
726         <-tr.GotInfo()
727         tr.DownloadAll()
728         client.WaitAll()
729 }
730
731 // This appears to be the situation with the S3 BitTorrent client.
732 func TestObfuscatedHeaderFallbackSeederDisallowsLeecherPrefers(t *testing.T) {
733         // Leecher prefers obfuscation, but the seeder does not allow it.
734         testSeederLeecherPair(
735                 t,
736                 func(cfg *ClientConfig) {
737                         cfg.HeaderObfuscationPolicy.Preferred = false
738                         cfg.HeaderObfuscationPolicy.RequirePreferred = true
739                 },
740                 func(cfg *ClientConfig) {
741                         cfg.HeaderObfuscationPolicy.Preferred = true
742                         cfg.HeaderObfuscationPolicy.RequirePreferred = false
743                 },
744         )
745 }
746
747 func TestObfuscatedHeaderFallbackSeederRequiresLeecherPrefersNot(t *testing.T) {
748         // Leecher prefers no obfuscation, but the seeder enforces it.
749         testSeederLeecherPair(
750                 t,
751                 func(cfg *ClientConfig) {
752                         cfg.HeaderObfuscationPolicy.Preferred = true
753                         cfg.HeaderObfuscationPolicy.RequirePreferred = true
754                 },
755                 func(cfg *ClientConfig) {
756                         cfg.HeaderObfuscationPolicy.Preferred = false
757                         cfg.HeaderObfuscationPolicy.RequirePreferred = false
758                 },
759         )
760 }
761
762 func TestClientAddressInUse(t *testing.T) {
763         s, _ := NewUtpSocket("udp", ":50007", nil)
764         if s != nil {
765                 defer s.Close()
766         }
767         cfg := TestingConfig(t).SetListenAddr(":50007")
768         cl, err := NewClient(cfg)
769         require.Error(t, err)
770         require.Nil(t, cl)
771 }
772
773 func TestClientHasDhtServersWhenUtpDisabled(t *testing.T) {
774         cc := TestingConfig(t)
775         cc.DisableUTP = true
776         cc.NoDHT = false
777         cl, err := NewClient(cc)
778         require.NoError(t, err)
779         defer cl.Close()
780         assert.NotEmpty(t, cl.DhtServers())
781 }
782
783 func TestIssue335(t *testing.T) {
784         dir, mi := testutil.GreetingTestTorrent()
785         defer os.RemoveAll(dir)
786         cfg := TestingConfig(t)
787         cfg.Seed = false
788         cfg.Debug = true
789         cfg.DataDir = dir
790         comp, err := storage.NewBoltPieceCompletion(dir)
791         require.NoError(t, err)
792         defer comp.Close()
793         cfg.DefaultStorage = storage.NewMMapWithCompletion(dir, comp)
794         cl, err := NewClient(cfg)
795         require.NoError(t, err)
796         defer cl.Close()
797         tor, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
798         require.NoError(t, err)
799         assert.True(t, new)
800         require.True(t, cl.WaitAll())
801         tor.Drop()
802         _, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
803         require.NoError(t, err)
804         assert.True(t, new)
805         require.True(t, cl.WaitAll())
806 }
807
808 func TestClientDisabledImplicitNetworksButDhtEnabled(t *testing.T) {
809         cfg := TestingConfig(t)
810         cfg.DisableTCP = true
811         cfg.DisableUTP = true
812         cfg.NoDHT = false
813         cl, err := NewClient(cfg)
814         require.NoError(t, err)
815         defer cl.Close()
816         assert.Empty(t, cl.listeners)
817         assert.NotEmpty(t, cl.DhtServers())
818 }