└── kademlia ├── peer.go ├── peer_info.go ├── util.go ├── integration_test.go ├── filler.go ├── pinger.go ├── key_test.go ├── key.go ├── kademlia.go ├── seek.go └── bucket.go /kademlia/peer.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "time" 5 | 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | type Peer interface { 10 | KademliaKey() Key 11 | Ping(ctx context.Context) error 12 | Lookup(ctx context.Context, key Key, n int) ([]Peer, error) 13 | } 14 | 15 | func ping(ctx context.Context, peer Peer) (latency time.Duration, err error) { 16 | start := time.Now() 17 | 18 | ctx, cancel := context.WithTimeout(ctx, 55*time.Second) 19 | defer cancel() 20 | 21 | err = peer.Ping(ctx) 22 | 23 | return time.Since(start), err 24 | } 25 | -------------------------------------------------------------------------------- /kademlia/peer_info.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "bytes" 5 | "time" 6 | ) 7 | 8 | type peerInfo struct { 9 | key Key 10 | peer Peer 11 | 12 | lastSeen time.Time 13 | firstSeen time.Time 14 | latency time.Duration 15 | } 16 | 17 | type fastestPeers []*peerInfo 18 | 19 | func (s fastestPeers) Len() int { return len(s) } 20 | func (s fastestPeers) Less(i, j int) bool { return s[i].latency < s[j].latency } 21 | func (s fastestPeers) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 22 | 23 | type oldestPeers []*peerInfo 24 | 25 | func (s oldestPeers) Len() int { return len(s) } 26 | func (s oldestPeers) Less(i, j int) bool { return s[i].firstSeen.Before(s[j].firstSeen) } 27 | func (s oldestPeers) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 28 | 29 | type lexographicPeers []*peerInfo 30 | 31 | func (s lexographicPeers) Len() int { return len(s) } 32 | func (s lexographicPeers) Less(i, j int) bool { return bytes.Compare(s[i].key[:], s[j].key[:]) < 0 } 33 | func (s lexographicPeers) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 34 | -------------------------------------------------------------------------------- /kademlia/util.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | var debug = false 11 | 12 | func tokenStream(n int, duration time.Duration) <-chan struct{} { 13 | var out = make(chan struct{}, n) 14 | if n == 0 { 15 | close(out) 16 | return out 17 | } 18 | 19 | go func() { 20 | defer close(out) 21 | 22 | ticker := time.NewTicker(duration / time.Duration(n)) 23 | defer ticker.Stop() 24 | 25 | for ; n > 0; n-- { 26 | <-ticker.C 27 | out <- struct{}{} 28 | } 29 | }() 30 | 31 | return out 32 | } 33 | 34 | type semaphore chan struct{} 35 | 36 | func newSema(n int) semaphore { 37 | c := make(semaphore, n) 38 | for i := 0; i < n; i++ { 39 | c <- struct{}{} 40 | } 41 | return c 42 | } 43 | 44 | func (s semaphore) acquire(ctx context.Context) bool { 45 | select { 46 | case <-s: 47 | return true 48 | case <-ctx.Done(): 49 | return false 50 | } 51 | } 52 | 53 | func (s semaphore) release() { 54 | s <- struct{}{} 55 | } 56 | 57 | func logf(format string, args ...interface{}) { 58 | if debug { 59 | log.Printf(format, args...) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /kademlia/integration_test.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | func setup() { 13 | debug = true 14 | log.SetOutput(os.Stderr) 15 | } 16 | 17 | func TestIntegration(t *testing.T) { 18 | setup() 19 | 20 | ctx, cancel := context.WithCancel(context.Background()) 21 | defer cancel() 22 | 23 | var ( 24 | peers = make(map[Key]*testPeer) 25 | seed Key 26 | ) 27 | 28 | for i := 0; i < 100; i++ { 29 | key := randKey() 30 | 31 | if i == 0 { 32 | seed = key 33 | } 34 | 35 | peers[key] = &testPeer{ 36 | Key: key, 37 | DHT: Start(ctx, key), 38 | peers: peers, 39 | } 40 | } 41 | 42 | for key, peer := range peers { 43 | if key == seed { 44 | continue 45 | } 46 | peers[seed].DHT.Add(peer) 47 | peer.DHT.Add(peers[seed]) 48 | } 49 | 50 | time.Sleep(5 * time.Minute) 51 | } 52 | 53 | type testPeer struct { 54 | Key Key 55 | DHT *DHT 56 | peers map[Key]*testPeer 57 | } 58 | 59 | func (p *testPeer) KademliaKey() Key { 60 | return p.Key 61 | } 62 | 63 | func (p *testPeer) Ping(ctx context.Context) error { 64 | return nil 65 | } 66 | 67 | func (p *testPeer) Lookup(ctx context.Context, key Key, n int) ([]Peer, error) { 68 | return p.DHT.LookupN(key, n), nil 69 | } 70 | -------------------------------------------------------------------------------- /kademlia/filler.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "time" 5 | 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | func (dht *DHT) runFiller(ctx context.Context) { 10 | timer := time.NewTicker(15 * time.Second) 11 | defer timer.Stop() 12 | 13 | for { 14 | select { 15 | case <-timer.C: 16 | dht.fillAll(ctx) 17 | case <-ctx.Done(): 18 | return 19 | } 20 | } 21 | } 22 | 23 | func (dht *DHT) fillAll(ctx context.Context) { 24 | for i := 0; i < numBuckets; i++ { 25 | // var ( 26 | // fill = dht.getBucket(i-1).GetSize() > 0 || 27 | // dht.getBucket(i).GetSize() > 0 || 28 | // dht.getBucket(i+1).GetSize() > 0 29 | // ) 30 | // if !fill { 31 | // continue 32 | // } 33 | 34 | go dht.fillBucket(ctx, i) 35 | } 36 | } 37 | 38 | func (dht *DHT) fillBucket(ctx context.Context, bucket int) { 39 | logf("FILL begin bucket=%d", bucket) 40 | defer logf("FILL end bucket=%d", bucket) 41 | 42 | ctx, cancel := context.WithTimeout(ctx, 15*time.Second) 43 | defer cancel() 44 | 45 | dst := randKeyInBucket(dht.key, bucket) 46 | peers, err := dht.Seek(ctx, dst, 25) 47 | if err == context.Canceled || err == context.DeadlineExceeded { 48 | err = nil 49 | } 50 | if err != nil { 51 | // handle error 52 | // do not return 53 | } 54 | 55 | for _, peer := range peers { 56 | dht.Add(peer) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /kademlia/pinger.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "sort" 5 | "time" 6 | 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | func (dht *DHT) runPingScheduler(ctx context.Context) { 11 | timer := time.NewTimer(1 * time.Minute) 12 | defer timer.Stop() 13 | 14 | for { 15 | select { 16 | case <-timer.C: 17 | go dht.runPinger(ctx) 18 | case <-ctx.Done(): 19 | return 20 | } 21 | } 22 | } 23 | 24 | func (dht *DHT) runPinger(ctx context.Context) { 25 | // logf("run pinger") 26 | var peers = make([]*peerInfo, 0, 1024) 27 | 28 | for i := 0; i < numBuckets; i++ { 29 | table := dht.getBucket(i).GetLookupTable() 30 | if table == nil || len(table.peers) == 0 { 31 | continue 32 | } 33 | 34 | peers = append(peers, table.peers...) 35 | } 36 | 37 | sort.Sort(sort.Reverse(fastestPeers(peers))) 38 | 39 | var ( 40 | ticker = tokenStream(len(peers), 1*time.Minute) 41 | ) 42 | 43 | for len(peers) > 0 { 44 | select { 45 | case <-ctx.Done(): 46 | return 47 | case <-ticker: 48 | } 49 | 50 | info := peers[0] 51 | peers = peers[1:] 52 | 53 | go dht.ping(ctx, info.peer) 54 | } 55 | } 56 | 57 | func (dht *DHT) ping(ctx context.Context, peer Peer) { 58 | // logf("ping: %x", peer.KademliaKey()) 59 | latency, err := ping(ctx, peer) 60 | if err != nil { 61 | dht.Remove(peer) 62 | } else { 63 | dht.touch(peer, latency) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /kademlia/key_test.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import "testing" 4 | 5 | func TestBucketForKey(t *testing.T) { 6 | const m = 255 7 | 8 | var tab = []struct { 9 | key Key 10 | bucket int 11 | }{ 12 | {Key{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 0}, 13 | {Key{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, 1}, 14 | {Key{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}, 2}, 15 | {Key{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3}, 2}, 16 | {Key{m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m}, 256}, 17 | } 18 | 19 | var self Key 20 | 21 | for _, test := range tab { 22 | actual := bucketForKey(self, test.key) 23 | if actual != test.bucket { 24 | t.Errorf("expected bucket %d but was %d (key = %x)", test.bucket, actual, test.key) 25 | } 26 | } 27 | } 28 | 29 | func TestRandomKeyInBucket(t *testing.T) { 30 | for i := 0; i < 128; i++ { 31 | var self = randKey() 32 | var mustBeInBucket = 4 33 | 34 | for i := 0; i < 128; i++ { 35 | key := randKeyInBucket(self, mustBeInBucket) 36 | if bucket := bucketForKey(self, key); bucket != mustBeInBucket { 37 | t.Errorf("expected %x to be in bucket %d but was in %d", key, mustBeInBucket, bucket) 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /kademlia/key.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "crypto/rand" 5 | "io" 6 | ) 7 | 8 | const ( 9 | keyLen = 32 10 | ) 11 | 12 | type Key [keyLen]byte 13 | 14 | func less(a, b Key) bool { 15 | for i, x := range a { 16 | y := b[i] 17 | if x < y { 18 | return true 19 | } 20 | if x > y { 21 | return false 22 | } 23 | } 24 | return false 25 | } 26 | 27 | func keyDistance(a, b Key) Key { 28 | var c Key 29 | for i, x := range a { 30 | c[i] = x ^ b[i] 31 | } 32 | return c 33 | } 34 | 35 | func bucketForKey(self, key Key) int { 36 | r := 256 37 | 38 | for i, x := range self { 39 | y := key[i] 40 | d := x ^ y 41 | 42 | if d == 0 { 43 | r -= 8 44 | continue 45 | } 46 | 47 | switch { 48 | case 0x80&d > 0: 49 | return r 50 | case 0x40&d > 0: 51 | return r - 1 52 | case 0x20&d > 0: 53 | return r - 2 54 | case 0x10&d > 0: 55 | return r - 3 56 | case 0x08&d > 0: 57 | return r - 4 58 | case 0x04&d > 0: 59 | return r - 5 60 | case 0x02&d > 0: 61 | return r - 6 62 | case 0x01&d > 0: 63 | return r - 7 64 | } 65 | } 66 | 67 | return r 68 | } 69 | 70 | func randKey() Key { 71 | var ( 72 | rnd Key 73 | ) 74 | 75 | _, err := io.ReadFull(rand.Reader, rnd[:]) 76 | if err != nil { 77 | panic("crypto/rand failed") 78 | } 79 | 80 | return rnd 81 | } 82 | 83 | func randKeyInBucket(base Key, bucket int) Key { 84 | if bucket == 0 { 85 | return base 86 | } 87 | 88 | var ( 89 | rnd = randKey() 90 | key Key 91 | ) 92 | 93 | bucket-- 94 | split := keyLen - (bucket / 8) - 1 95 | copy(key[:], base[:split]) 96 | copy(key[split+1:], rnd[split+1:]) 97 | 98 | shift := uint(bucket % 8) 99 | maskBase := byte(0xFF) << (shift + 1) 100 | maskRnd := ^(byte(0xFF) << (shift)) 101 | 102 | key[split] = (maskBase & base[split]) | 103 | (maskRnd & rnd[split]) | 104 | ((^base[split]) & (byte(1) << shift)) 105 | 106 | return key 107 | } 108 | -------------------------------------------------------------------------------- /kademlia/kademlia.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | const ( 11 | numBuckets = (keyLen * 8) + 1 12 | ) 13 | 14 | type DHT struct { 15 | key Key 16 | 17 | mtx sync.RWMutex 18 | buckets [numBuckets]*bucket 19 | } 20 | 21 | func Start(ctx context.Context, self Key) *DHT { 22 | dht := &DHT{} 23 | dht.key = self 24 | 25 | go dht.runPingScheduler(ctx) 26 | go dht.runFiller(ctx) 27 | 28 | return dht 29 | } 30 | 31 | func (dht *DHT) getBucket(idx int) *bucket { 32 | if idx < 0 || numBuckets <= idx { 33 | return nil 34 | } 35 | 36 | dht.mtx.RLock() 37 | bkt := dht.buckets[idx] 38 | dht.mtx.RUnlock() 39 | 40 | if bkt == nil { 41 | dht.mtx.Lock() 42 | bkt = dht.buckets[idx] 43 | if bkt == nil { 44 | bkt = &bucket{} 45 | dht.buckets[idx] = bkt 46 | } 47 | dht.mtx.Unlock() 48 | } 49 | 50 | return bkt 51 | } 52 | 53 | func (dht *DHT) Add(peer Peer) error { 54 | latency, err := ping(context.Background(), peer) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | dht.touch(peer, latency) 60 | return nil 61 | } 62 | 63 | func (dht *DHT) touch(peer Peer, latency time.Duration) { 64 | bucket := dht.getBucket(bucketForKey(dht.key, peer.KademliaKey())) 65 | if bucket != nil { 66 | bucket.Touch(peer, latency) 67 | } 68 | } 69 | 70 | func (dht *DHT) Remove(peer Peer) { 71 | bucket := dht.getBucket(bucketForKey(dht.key, peer.KademliaKey())) 72 | if bucket != nil { 73 | bucket.Remove(peer) 74 | } 75 | } 76 | 77 | func (dht *DHT) LookupN(key Key, n int) []Peer { 78 | var ( 79 | bucketIndex = bucketForKey(dht.key, key) 80 | bucketOffset = 0 81 | res = make([]Peer, 0, n) 82 | ) 83 | 84 | table := dht.getBucket(bucketIndex).GetLookupTable() 85 | if table == nil { 86 | return nil 87 | } 88 | 89 | // find exact match 90 | for _, info := range table.peers { 91 | if info.key == key { 92 | res = append(res, info.peer) 93 | break 94 | } 95 | } 96 | 97 | res = appendAtMost(res, table.fastest, n-len(res)) 98 | 99 | for { 100 | bucketOffset++ 101 | 102 | if bucketIndex+bucketOffset >= numBuckets && bucketIndex-bucketOffset < 0 { 103 | break 104 | } 105 | 106 | table1 := dht.getBucket(bucketIndex + bucketOffset).GetLookupTable() 107 | table2 := dht.getBucket(bucketIndex - bucketOffset).GetLookupTable() 108 | 109 | if table1 != nil { 110 | res = appendAtMost(res, table1.fastest, n-len(res)) 111 | if len(res) >= n { 112 | break 113 | } 114 | } 115 | 116 | if table2 != nil { 117 | res = appendAtMost(res, table2.fastest, n-len(res)) 118 | if len(res) >= n { 119 | break 120 | } 121 | } 122 | } 123 | 124 | return res 125 | } 126 | 127 | func appendAtMost(dst []Peer, src []*peerInfo, n int) []Peer { 128 | if n <= 0 { 129 | return dst 130 | } 131 | if len(src) > n { 132 | src = src[:n] 133 | } 134 | 135 | for _, info := range src { 136 | dst = append(dst, info.peer) 137 | } 138 | 139 | return dst 140 | } 141 | -------------------------------------------------------------------------------- /kademlia/seek.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | func (dht *DHT) Seek(ctx context.Context, dst Key, n int) ([]Peer, error) { 11 | if n <= 0 { 12 | n = 25 13 | } 14 | 15 | s := &seek{ 16 | dht: dht, 17 | dst: dst, 18 | n: n, 19 | smallSema: newSema(5), 20 | largeSema: newSema(5), 21 | queue: make(chan Peer), 22 | results: make([]*seekEntry, n), 23 | } 24 | 25 | return s.Seek(ctx) 26 | } 27 | 28 | type seek struct { 29 | dht *DHT 30 | dst Key 31 | n int 32 | 33 | smallSema semaphore 34 | largeSema semaphore 35 | queue chan Peer 36 | 37 | wg sync.WaitGroup 38 | mtx sync.Mutex 39 | results []*seekEntry 40 | } 41 | 42 | type seekEntry struct { 43 | distance Key 44 | peer Peer 45 | } 46 | 47 | func (s *seek) AddEntry(newEntry *seekEntry) { 48 | s.mtx.Lock() 49 | defer s.mtx.Unlock() 50 | 51 | for idx, entry := range s.results { 52 | 53 | if entry != nil { 54 | if less(entry.distance, newEntry.distance) { 55 | continue 56 | } 57 | 58 | if entry.distance == newEntry.distance { 59 | // ignore; already in result 60 | // logf("ignored %x", newEntry.peer.KademliaKey()) 61 | return 62 | } 63 | } 64 | 65 | // insert new entry 66 | copy(s.results[idx+1:], s.results[idx:]) 67 | s.results[idx] = newEntry 68 | // logf("added %x", newEntry.peer.KademliaKey()) 69 | 70 | s.wg.Add(1) 71 | s.queue <- newEntry.peer 72 | return 73 | } 74 | } 75 | 76 | func (s *seek) HandleError(err error) { 77 | logf("DHT/error: %s", err) 78 | } 79 | 80 | func (s *seek) Seek(ctx context.Context) ([]Peer, error) { 81 | // logf("SEEK begin %x", s.dst) 82 | // defer logf("SEEK end %x", s.dst) 83 | 84 | ctx, cancel := context.WithCancel(ctx) 85 | defer cancel() 86 | 87 | s.wg.Add(1) 88 | go s.localSeek() 89 | 90 | go func() { 91 | for { 92 | select { 93 | case <-ctx.Done(): 94 | return 95 | case peer := <-s.queue: 96 | go s.remoteSeek(ctx, peer) 97 | } 98 | } 99 | }() 100 | 101 | s.wg.Wait() 102 | 103 | peers := make([]Peer, len(s.results)) 104 | for i, entry := range s.results { 105 | if entry == nil { 106 | peers = peers[:i] 107 | break 108 | } 109 | peers[i] = entry.peer 110 | } 111 | 112 | return peers, ctx.Err() 113 | } 114 | 115 | func (s *seek) localSeek() { 116 | // logf("SEEK local %x (%x)", s.dht.key, s.dst) 117 | defer s.wg.Done() 118 | 119 | peers := s.dht.LookupN(s.dst, s.n) 120 | // logf("SEEK local %x (%d)", s.dht.key, len(peers)) 121 | for _, peer := range peers { 122 | // logf("Attempt add %x", peer.KademliaKey()) 123 | s.AddEntry(&seekEntry{ 124 | distance: keyDistance(s.dst, peer.KademliaKey()), 125 | peer: peer, 126 | }) 127 | } 128 | } 129 | 130 | func (s *seek) remoteSeek(ctx context.Context, peer Peer) { 131 | // logf("SEEK remote %x(%x)", peer.KademliaKey(), s.dst) 132 | 133 | if peer.KademliaKey() == s.dht.key { 134 | s.localSeek() 135 | return 136 | } 137 | 138 | defer s.wg.Done() 139 | 140 | ctx, cancel := context.WithTimeout(ctx, 3*time.Minute) 141 | defer cancel() 142 | 143 | // acquire spot in fast-lane 144 | if !s.smallSema.acquire(ctx) { 145 | return 146 | } 147 | go func() { 148 | defer s.smallSema.release() 149 | smallCtx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) 150 | defer cancel() 151 | <-smallCtx.Done() 152 | }() 153 | 154 | // acquire spot in slow-lane 155 | if !s.largeSema.acquire(ctx) { 156 | return 157 | } 158 | defer s.largeSema.release() 159 | 160 | peers, err := peer.Lookup(ctx, s.dst, s.n) 161 | if err != nil { 162 | s.HandleError(err) 163 | return 164 | } 165 | 166 | for _, peer := range peers { 167 | s.AddEntry(&seekEntry{ 168 | distance: keyDistance(s.dst, peer.KademliaKey()), 169 | peer: peer, 170 | }) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /kademlia/bucket.go: -------------------------------------------------------------------------------- 1 | package kademlia 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | const ( 10 | maxBucketSize = 128 11 | latencyEMAPeriods float64 = 5 12 | latencyEMAAlpha float64 = 2 / (1 + latencyEMAPeriods) 13 | ) 14 | 15 | type bucket struct { 16 | mtx sync.RWMutex 17 | modified bool 18 | peers []*peerInfo 19 | lookupTable *lookupTable 20 | } 21 | 22 | type lookupTable struct { 23 | fastest []*peerInfo // sorted by latency (fastest first) 24 | oldest []*peerInfo // sorted by first seen (oldest first) 25 | peers []*peerInfo // sorted by key 26 | } 27 | 28 | func (b *bucket) Touch(peer Peer, latency time.Duration) { 29 | // add to bucket if not full 30 | // replace with slowest if faster than most 31 | 32 | now := time.Now() 33 | 34 | b.mtx.Lock() 35 | defer b.mtx.Unlock() 36 | 37 | if b.peers == nil { 38 | b.peers = make([]*peerInfo, 0, maxBucketSize) 39 | } 40 | 41 | var ( 42 | key = peer.KademliaKey() 43 | info *peerInfo 44 | slowestPeerIndex int 45 | maxLatency time.Duration 46 | avgLatency time.Duration 47 | ) 48 | 49 | for idx, i := range b.peers { 50 | avgLatency += i.latency 51 | 52 | if i.latency > maxLatency { 53 | maxLatency = i.latency 54 | slowestPeerIndex = idx 55 | } 56 | 57 | if i.key == key { 58 | info = i 59 | } 60 | } 61 | if n := len(b.peers); n > 0 { 62 | avgLatency /= time.Duration(n) 63 | } 64 | 65 | // Update existing record 66 | if info != nil { 67 | if latency > 0 { 68 | info.latency = time.Duration((float64(latency) * latencyEMAAlpha) + ((1 - latencyEMAAlpha) * float64(latency))) 69 | } 70 | info.lastSeen = now 71 | b.modified = true 72 | return 73 | } 74 | 75 | // Add entry; bucket is not full 76 | if len(b.peers) < maxBucketSize { 77 | logf("BUCKET add (more) %x", key) 78 | if latency <= 0 { 79 | latency = 1 * time.Minute 80 | } 81 | info = &peerInfo{ 82 | key: key, 83 | peer: peer, 84 | lastSeen: now, 85 | firstSeen: now, 86 | latency: latency, 87 | } 88 | b.peers = append(b.peers, info) 89 | sort.Sort(lexographicPeers(b.peers)) 90 | b.modified = true 91 | return 92 | } 93 | 94 | // Add entry; faster than most 95 | if latency < avgLatency { 96 | logf("BUCKET add (faster) %x", key) 97 | if latency <= 0 { 98 | latency = 1 * time.Minute 99 | } 100 | info = &peerInfo{ 101 | key: key, 102 | peer: peer, 103 | lastSeen: now, 104 | firstSeen: now, 105 | latency: latency, 106 | } 107 | // replace slowest peer with new peer 108 | b.peers[slowestPeerIndex] = info 109 | sort.Sort(lexographicPeers(b.peers)) 110 | b.modified = true 111 | return 112 | } 113 | } 114 | 115 | func (b *bucket) Remove(peer Peer) { 116 | b.mtx.Lock() 117 | defer b.mtx.Unlock() 118 | 119 | var key = peer.KademliaKey() 120 | 121 | for idx, info := range b.peers { 122 | if info.key == key { 123 | copy(b.peers[idx:], b.peers[idx+1:]) 124 | b.peers = b.peers[:len(b.peers)-1] 125 | b.modified = true 126 | return 127 | } 128 | } 129 | } 130 | 131 | func (b *bucket) GetSize() int { 132 | if b == nil { 133 | return 0 134 | } 135 | 136 | b.mtx.RLock() 137 | s := len(b.peers) 138 | b.mtx.RUnlock() 139 | 140 | return s 141 | } 142 | 143 | func (b *bucket) GetLookupTable() *lookupTable { 144 | if b == nil { 145 | return nil 146 | } 147 | 148 | b.mtx.RLock() 149 | table, modified := b.lookupTable, b.modified 150 | b.mtx.RUnlock() 151 | 152 | if !modified { 153 | return table 154 | } 155 | 156 | b.mtx.Lock() 157 | if b.modified { 158 | table = &lookupTable{ 159 | fastest: make([]*peerInfo, len(b.peers)), 160 | oldest: make([]*peerInfo, len(b.peers)), 161 | peers: make([]*peerInfo, len(b.peers)), 162 | } 163 | 164 | copy(table.fastest, b.peers) 165 | copy(table.oldest, b.peers) 166 | copy(table.peers, b.peers) 167 | 168 | sort.Sort(fastestPeers(table.fastest)) 169 | sort.Sort(oldestPeers(table.oldest)) 170 | 171 | b.lookupTable = table 172 | b.modified = false 173 | } else { 174 | table = b.lookupTable 175 | } 176 | b.mtx.Unlock() 177 | 178 | if table == nil { 179 | table = &lookupTable{} 180 | } 181 | 182 | return table 183 | } 184 | --------------------------------------------------------------------------------