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