// Creates a seeder and a leecher, and ensures the data transfers when a read // is attempted on the leecher. func testClientTransfer(t *testing.T, ps testClientTransferParams) { greetingTempDir, mi := testutil.GreetingTestTorrent() defer os.RemoveAll(greetingTempDir) cfg := TestingConfig cfg.Seed = true if ps.SeederStorage != nil { cfg.DefaultStorage = ps.SeederStorage(greetingTempDir) } else { cfg.DataDir = greetingTempDir } seeder, err := NewClient(&cfg) require.NoError(t, err) defer seeder.Close() if ps.ExportClientStatus { testutil.ExportStatusWriter(seeder, "s") } seederTorrent, new, err := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi)) require.NoError(t, err) assert.True(t, new) leecherDataDir, err := ioutil.TempDir("", "") require.NoError(t, err) defer os.RemoveAll(leecherDataDir) cfg.DefaultStorage = ps.LeecherStorage(leecherDataDir) leecher, err := NewClient(&cfg) require.NoError(t, err) defer leecher.Close() if ps.ExportClientStatus { testutil.ExportStatusWriter(leecher, "l") } leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) { ret = TorrentSpecFromMetaInfo(mi) ret.ChunkSize = 2 ret.Storage = storage.NewFile(leecherDataDir) return }()) require.NoError(t, err) assert.True(t, new) addClientPeer(leecherGreeting, seeder) r := leecherGreeting.NewReader() defer r.Close() if ps.Responsive { r.SetResponsive() } if ps.SetReadahead { r.SetReadahead(ps.Readahead) } assertReadAllGreeting(t, r) // After one read through, we can assume certain torrent statistics. // These are not a strict requirement. It is however interesting to // follow. t.Logf("%#v", seederTorrent.Stats()) assert.EqualValues(t, 13, seederTorrent.Stats().DataBytesWritten) assert.EqualValues(t, 8, seederTorrent.Stats().ChunksWritten) assert.EqualValues(t, 13, leecherGreeting.Stats().DataBytesRead) assert.EqualValues(t, 8, leecherGreeting.Stats().ChunksRead) // Read through again for the cases where the torrent data size exceeds // the size of the cache. assertReadAllGreeting(t, r) }
func TestHashPieceAfterStorageClosed(t *testing.T) { td, err := ioutil.TempDir("", "") require.NoError(t, err) defer os.RemoveAll(td) cs := storage.NewFile(td) tt := &Torrent{} tt.info = &testutil.GreetingMetaInfo().Info tt.makePieces() tt.storage, err = cs.OpenTorrent(tt.info) require.NoError(t, err) require.NoError(t, tt.storage.Close()) tt.hashPiece(0) }
func TestTorrentInitialState(t *testing.T) { dir, mi := testutil.GreetingTestTorrent() defer os.RemoveAll(dir) tor := newTorrent(func() (ih metainfo.Hash) { missinggo.CopyExact(ih[:], mi.Info.Hash) return }()) tor.chunkSize = 2 tor.storageOpener = storage.NewFile(dir) // Needed to lock for asynchronous piece verification. tor.cl = new(Client) err := tor.setMetadata(&mi.Info.Info, mi.Info.Bytes) require.NoError(t, err) require.Len(t, tor.pieces, 3) tor.pendAllChunkSpecs(0) assert.EqualValues(t, 3, tor.pieceNumPendingChunks(0)) assert.EqualValues(t, chunkSpec{4, 1}, chunkIndexSpec(2, tor.pieceLength(0), tor.chunkSize)) }
func TestTorrentInitialState(t *testing.T) { dir, mi := testutil.GreetingTestTorrent() defer os.RemoveAll(dir) tor := &Torrent{ infoHash: mi.Info.Hash(), pieceStateChanges: pubsub.NewPubSub(), } tor.chunkSize = 2 tor.storageOpener = storage.NewFile("/dev/null") // Needed to lock for asynchronous piece verification. tor.cl = new(Client) err := tor.setInfoBytes(mi.Info.Bytes) require.NoError(t, err) require.Len(t, tor.pieces, 3) tor.pendAllChunkSpecs(0) tor.cl.mu.Lock() assert.EqualValues(t, 3, tor.pieceNumPendingChunks(0)) tor.cl.mu.Unlock() assert.EqualValues(t, chunkSpec{4, 1}, chunkIndexSpec(2, tor.pieceLength(0), tor.chunkSize)) }
func testClientTransfer(t *testing.T, ps testClientTransferParams) { greetingTempDir, mi := testutil.GreetingTestTorrent() defer os.RemoveAll(greetingTempDir) cfg := TestingConfig cfg.Seed = true if ps.SeederStorage != nil { cfg.DefaultStorage = ps.SeederStorage(greetingTempDir) } else { cfg.DataDir = greetingTempDir } seeder, err := NewClient(&cfg) require.NoError(t, err) defer seeder.Close() if ps.ExportClientStatus { testutil.ExportStatusWriter(seeder, "s") } _, new, err := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi)) require.NoError(t, err) assert.True(t, new) leecherDataDir, err := ioutil.TempDir("", "") require.NoError(t, err) defer os.RemoveAll(leecherDataDir) fc, err := filecache.NewCache(leecherDataDir) require.NoError(t, err) if ps.SetLeecherStorageCapacity { fc.SetCapacity(ps.LeecherStorageCapacity) } cfg.DefaultStorage = ps.LeecherFileCachePieceStorageFactory(fc) leecher, err := NewClient(&cfg) require.NoError(t, err) defer leecher.Close() if ps.ExportClientStatus { testutil.ExportStatusWriter(leecher, "l") } leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) { ret = TorrentSpecFromMetaInfo(mi) ret.ChunkSize = 2 ret.Storage = storage.NewFile(leecherDataDir) return }()) require.NoError(t, err) assert.True(t, new) leecherGreeting.AddPeers([]Peer{ Peer{ IP: missinggo.AddrIP(seeder.ListenAddr()), Port: missinggo.AddrPort(seeder.ListenAddr()), }, }) r := leecherGreeting.NewReader() defer r.Close() if ps.Responsive { r.SetResponsive() } if ps.SetReadahead { r.SetReadahead(ps.Readahead) } for range iter.N(2) { pos, err := r.Seek(0, os.SEEK_SET) assert.NoError(t, err) assert.EqualValues(t, 0, pos) _greeting, err := ioutil.ReadAll(r) assert.NoError(t, err) assert.EqualValues(t, testutil.GreetingFileContents, _greeting) } }
// Creates a new client. func NewClient(cfg *Config) (cl *Client, err error) { if cfg == nil { cfg = &Config{} } defer func() { if err != nil { cl = nil } }() cl = &Client{ halfOpenLimit: socketsPerTorrent, config: *cfg, defaultStorage: cfg.DefaultStorage, dopplegangerAddrs: make(map[string]struct{}), torrents: make(map[metainfo.Hash]*Torrent), } missinggo.CopyExact(&cl.extensionBytes, defaultExtensionBytes) cl.event.L = &cl.mu if cl.defaultStorage == nil { cl.defaultStorage = storage.NewFile(cfg.DataDir) } if cfg.IPBlocklist != nil { cl.ipBlockList = cfg.IPBlocklist } if cfg.PeerID != "" { missinggo.CopyExact(&cl.peerID, cfg.PeerID) } else { o := copy(cl.peerID[:], bep20) _, err = rand.Read(cl.peerID[o:]) if err != nil { panic("error generating peer id") } } cl.tcpListener, cl.utpSock, cl.listenAddr, err = listen( !cl.config.DisableTCP, !cl.config.DisableUTP, func() string { if cl.config.DisableIPv6 { return "4" } else { return "" } }(), cl.config.ListenAddr) if err != nil { return } if cl.tcpListener != nil { go cl.acceptConnections(cl.tcpListener, false) } if cl.utpSock != nil { go cl.acceptConnections(cl.utpSock, true) } if !cfg.NoDHT { dhtCfg := cfg.DHTConfig if dhtCfg.IPBlocklist == nil { dhtCfg.IPBlocklist = cl.ipBlockList } dhtCfg.Addr = firstNonEmptyString(dhtCfg.Addr, cl.listenAddr, cl.config.ListenAddr) if dhtCfg.Conn == nil && cl.utpSock != nil { dhtCfg.Conn = cl.utpSock } cl.dHT, err = dht.NewServer(&dhtCfg) if err != nil { return } } return }