diff --git a/chihaya_test.go b/chihaya_test.go index 0186c52..20805e3 100644 --- a/chihaya_test.go +++ b/chihaya_test.go @@ -23,18 +23,19 @@ var ( {"-BS5820-oy4La2MWGEFj", "fd0a:29a8:8445::38", 2878}, {"-BS5820-oy4La2MWGEFj", "fd45:7856:3dae::48", 8999}, } - builtPeers []*Peer ) func TestPeerEquality(t *testing.T) { // Build peers from test data. - for i := 0; i < len(peers); i++ { + var builtPeers []*Peer + for _, peer := range peers { builtPeers = append(builtPeers, &Peer{ - ID: PeerID(peers[i].peerID), - IP: net.ParseIP(peers[i].ip), - Port: peers[i].port, + ID: PeerID(peer.peerID), + IP: net.ParseIP(peer.ip), + Port: peer.port, }) } + assert.True(t, builtPeers[0].Equal(builtPeers[0])) assert.False(t, builtPeers[0].Equal(builtPeers[1])) assert.True(t, builtPeers[1].Equal(builtPeers[1])) diff --git a/server/store/memory/peer_store.go b/server/store/memory/peer_store.go index e6004bc..846256e 100644 --- a/server/store/memory/peer_store.go +++ b/server/store/memory/peer_store.go @@ -28,8 +28,13 @@ func (d *peerStoreDriver) New(storecfg *store.DriverConfig) (store.PeerStore, er return nil, err } + shards := make([]*peerShard, cfg.Shards) + for i := 0; i < cfg.Shards; i++ { + shards[i] = &peerShard{} + shards[i].peers = make(map[string]map[string]peer) + } return &peerStore{ - shards: make([]*peerShard, cfg.Shards), + shards: shards, }, nil } @@ -49,6 +54,9 @@ func newPeerStoreConfig(storecfg *store.DriverConfig) (*peerStoreConfig, error) return nil, err } + if cfg.Shards < 1 { + cfg.Shards = 1 + } return &cfg, nil } @@ -88,7 +96,6 @@ func leechersKey(infoHash chihaya.InfoHash) string { func (s *peerStore) PutSeeder(infoHash chihaya.InfoHash, p chihaya.Peer) error { key := seedersKey(infoHash) - shard := s.shards[s.shardIndex(infoHash)] shard.Lock() defer shard.Unlock() @@ -107,7 +114,6 @@ func (s *peerStore) PutSeeder(infoHash chihaya.InfoHash, p chihaya.Peer) error { func (s *peerStore) DeleteSeeder(infoHash chihaya.InfoHash, p chihaya.Peer) error { key := seedersKey(infoHash) - shard := s.shards[s.shardIndex(infoHash)] shard.Lock() defer shard.Unlock() @@ -127,7 +133,6 @@ func (s *peerStore) DeleteSeeder(infoHash chihaya.InfoHash, p chihaya.Peer) erro func (s *peerStore) PutLeecher(infoHash chihaya.InfoHash, p chihaya.Peer) error { key := leechersKey(infoHash) - shard := s.shards[s.shardIndex(infoHash)] shard.Lock() defer shard.Unlock() @@ -146,7 +151,6 @@ func (s *peerStore) PutLeecher(infoHash chihaya.InfoHash, p chihaya.Peer) error func (s *peerStore) DeleteLeecher(infoHash chihaya.InfoHash, p chihaya.Peer) error { key := leechersKey(infoHash) - shard := s.shards[s.shardIndex(infoHash)] shard.Lock() defer shard.Unlock() @@ -167,7 +171,6 @@ func (s *peerStore) DeleteLeecher(infoHash chihaya.InfoHash, p chihaya.Peer) err func (s *peerStore) GraduateLeecher(infoHash chihaya.InfoHash, p chihaya.Peer) error { lkey := leechersKey(infoHash) skey := seedersKey(infoHash) - shard := s.shards[s.shardIndex(infoHash)] shard.Lock() defer shard.Unlock() @@ -224,7 +227,6 @@ func (s *peerStore) CollectGarbage(cutoff time.Time) error { func (s *peerStore) AnnouncePeers(infoHash chihaya.InfoHash, seeder bool, numWant int) (peers, peers6 []chihaya.Peer, err error) { lkey := leechersKey(infoHash) skey := seedersKey(infoHash) - shard := s.shards[s.shardIndex(infoHash)] shard.RLock() defer shard.RUnlock() @@ -280,3 +282,55 @@ func (s *peerStore) AnnouncePeers(infoHash chihaya.InfoHash, seeder bool, numWan return } + +func (s *peerStore) GetSeeders(infoHash chihaya.InfoHash) (peers, peers6 []chihaya.Peer, err error) { + key := seedersKey(infoHash) + shard := s.shards[s.shardIndex(infoHash)] + shard.RLock() + defer shard.RUnlock() + + seeders := shard.peers[key] + for _, p := range seeders { + if p.IP.To4() == nil { + peers6 = append(peers6, p.Peer) + } else { + peers = append(peers, p.Peer) + } + } + return +} + +func (s *peerStore) GetLeechers(infoHash chihaya.InfoHash) (peers, peers6 []chihaya.Peer, err error) { + key := leechersKey(infoHash) + shard := s.shards[s.shardIndex(infoHash)] + shard.RLock() + defer shard.RUnlock() + + leechers := shard.peers[key] + for _, p := range leechers { + if p.IP.To4() == nil { + peers6 = append(peers6, p.Peer) + } else { + peers = append(peers, p.Peer) + } + } + return +} + +func (s *peerStore) NumSeeders(infoHash chihaya.InfoHash) int { + key := seedersKey(infoHash) + shard := s.shards[s.shardIndex(infoHash)] + shard.RLock() + defer shard.RUnlock() + + return len(shard.peers[key]) +} + +func (s *peerStore) NumLeechers(infoHash chihaya.InfoHash) int { + key := leechersKey(infoHash) + shard := s.shards[s.shardIndex(infoHash)] + shard.RLock() + defer shard.RUnlock() + + return len(shard.peers[key]) +} diff --git a/server/store/memory/peer_store_test.go b/server/store/memory/peer_store_test.go new file mode 100644 index 0000000..62e350f --- /dev/null +++ b/server/store/memory/peer_store_test.go @@ -0,0 +1,159 @@ +// Copyright 2016 The Chihaya Authors. All rights reserved. +// Use of this source code is governed by the BSD 2-Clause license, +// which can be found in the LICENSE file. + +package memory + +import ( + "net" + "testing" + "time" + + "github.com/chihaya/chihaya" + "github.com/chihaya/chihaya/server/store" + "github.com/stretchr/testify/assert" +) + +func peerInSlice(peer chihaya.Peer, peers []chihaya.Peer) bool { + for _, v := range peers { + if v.Equal(&peer) { + return true + } + } + return false +} + +func TestPeerStoreAPI(t *testing.T) { + var ( + hash = chihaya.InfoHash("11111111111111111111") + + peers = []struct { + seeder bool + peerID string + ip string + port uint16 + }{ + {false, "-AZ3034-6wfG2wk6wWLc", "250.183.81.177", 5720}, + {false, "-AZ3042-6ozMq5q6Q3NX", "38.241.13.19", 4833}, + {false, "-BS5820-oy4La2MWGEFj", "fd45:7856:3dae::48", 2878}, + {false, "-AR6360-6oZyyMWoOOBe", "fd0a:29a8:8445::38", 3167}, + {true, "-AG2083-s1hiF8vGAAg0", "231.231.49.173", 1453}, + {true, "-AG3003-lEl2Mm4NEO4n", "254.99.84.77", 7032}, + {true, "-MR1100-00HS~T7*65rm", "211.229.100.129", 2614}, + {true, "-LK0140-ATIV~nbEQAMr", "fdad:c435:bf79::12", 4114}, + {true, "-KT2210-347143496631", "fdda:1b35:7d6e::9", 6179}, + {true, "-TR0960-6ep6svaa61r4", "fd7f:78f0:4c77::55", 4727}, + } + unmarshalledConfig = struct { + Shards int + }{ + 1, + } + config = store.DriverConfig{ + "memory", + unmarshalledConfig, + } + d = &peerStoreDriver{} + ) + s, err := d.New(&config) + assert.Nil(t, err) + assert.NotNil(t, s) + + for _, p := range peers { + // Construct chihaya.Peer from test data. + peer := chihaya.Peer{ + chihaya.PeerID(p.peerID), + net.ParseIP(p.ip), + p.port, + } + + if p.seeder { + err = s.PutSeeder(hash, peer) + } else { + err = s.PutLeecher(hash, peer) + } + assert.Nil(t, err) + } + + leechers1, leechers61, err := s.GetLeechers(hash) + assert.Nil(t, err) + assert.NotEmpty(t, leechers1) + assert.NotEmpty(t, leechers61) + num := s.NumLeechers(hash) + assert.Equal(t, len(leechers1)+len(leechers61), num) + + seeders1, seeders61, err := s.GetSeeders(hash) + assert.Nil(t, err) + assert.NotEmpty(t, seeders1) + assert.NotEmpty(t, seeders61) + num = s.NumSeeders(hash) + assert.Equal(t, len(seeders1)+len(seeders61), num) + + leechers := append(leechers1, leechers61...) + seeders := append(seeders1, seeders61...) + + for _, p := range peers { + // Construct chihaya.Peer from test data. + peer := chihaya.Peer{ + chihaya.PeerID(p.peerID), + net.ParseIP(p.ip), + p.port, + } + + if p.seeder { + assert.True(t, peerInSlice(peer, seeders)) + } else { + assert.True(t, peerInSlice(peer, leechers)) + } + + if p.seeder { + err = s.DeleteSeeder(hash, peer) + } else { + err = s.DeleteLeecher(hash, peer) + } + assert.Nil(t, err) + } + + assert.Zero(t, s.NumLeechers(hash)) + assert.Zero(t, s.NumSeeders(hash)) + + // Re-add all the peers to the peerStore. + for _, p := range peers { + // Construct chihaya.Peer from test data. + peer := chihaya.Peer{ + chihaya.PeerID(p.peerID), + net.ParseIP(p.ip), + p.port, + } + if p.seeder { + s.PutSeeder(hash, peer) + } else { + s.PutLeecher(hash, peer) + } + } + + // Check that there are 6 seeders, and 4 leechers. + assert.Equal(t, 6, s.NumSeeders(hash)) + assert.Equal(t, 4, s.NumLeechers(hash)) + peer := chihaya.Peer{ + chihaya.PeerID(peers[0].peerID), + net.ParseIP(peers[0].ip), + peers[0].port, + } + err = s.GraduateLeecher(hash, peer) + assert.Nil(t, err) + // Check that there are 7 seeders, and 3 leechers after graduating a + // leecher to a seeder. + assert.Equal(t, 7, s.NumSeeders(hash)) + assert.Equal(t, 3, s.NumLeechers(hash)) + + peers1, peers61, err := s.AnnouncePeers(hash, true, 5) + assert.Nil(t, err) + assert.NotNil(t, peers1) + assert.NotNil(t, peers61) + + err = s.CollectGarbage(time.Now()) + assert.Nil(t, err) + assert.Equal(t, s.NumLeechers(hash), 0) + assert.Equal(t, s.NumSeeders(hash), 0) +} diff --git a/server/store/peer_store.go b/server/store/peer_store.go index e2d4936..8aadd47 100644 --- a/server/store/peer_store.go +++ b/server/store/peer_store.go @@ -15,15 +15,42 @@ var peerStoreDrivers = make(map[string]PeerStoreDriver) // PeerStore represents an interface for manipulating peers. type PeerStore interface { + // PutSeeder adds a seeder for the infoHash to the PeerStore. PutSeeder(infoHash chihaya.InfoHash, p chihaya.Peer) error + // DeleteSeeder removes a seeder for the infoHash from the PeerStore. DeleteSeeder(infoHash chihaya.InfoHash, p chihaya.Peer) error + // PutLeecher adds a leecher for the infoHash to the PeerStore. PutLeecher(infoHash chihaya.InfoHash, p chihaya.Peer) error + // DeleteLeecher removes a leecher for the infoHash from the PeerStore. DeleteLeecher(infoHash chihaya.InfoHash, p chihaya.Peer) error + // GraduateLeecher promotes a peer from a leecher to a seeder for the + // infoHash within the PeerStore. GraduateLeecher(infoHash chihaya.InfoHash, p chihaya.Peer) error + // AnnouncePeers returns a list of both IPv4, and IPv6 peers for an + // announce. + // + // If seeder is true then the peers returned will only be leechers, the + // ammount of leechers returned will be the smaller value of numWant or the + // available leechers. + // If it is false then seeders will be returned up until numWant or the + // available seeders, whichever is smaller. If the available seeders is less + // than numWant then peers are returned until numWant or they run out. AnnouncePeers(infoHash chihaya.InfoHash, seeder bool, numWant int) (peers, peers6 []chihaya.Peer, err error) + // CollectGarbage deletes peers from the peerStore which are older than the + // cutoff time. CollectGarbage(cutoff time.Time) error + + // GetSeeders gets all the seeders for a particular infoHash. + GetSeeders(infoHash chihaya.InfoHash) (peers, peers6 []chihaya.Peer, err error) + // GetLeechers gets all the leechers for a particular infoHash. + GetLeechers(infoHash chihaya.InfoHash) (peers, peers6 []chihaya.Peer, err error) + + // NumSeeders gets the amount of seeders for a particular infoHash. + NumSeeders(infoHash chihaya.InfoHash) int + // NumLeechers gets the amount of leechers for a particular infoHash. + NumLeechers(infoHash chihaya.InfoHash) int } // PeerStoreDriver represents an interface for creating a handle to the storage