table.go 3.21 KB
Newer Older
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
1 2 3
package dht

import (
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
4
	"container/list"
5 6
	"sort"

7
	peer "github.com/jbenet/go-ipfs/peer"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
8 9 10 11 12
)

// RoutingTable defines the routing table.
type RoutingTable struct {

13 14 15
	// ID of the local peer
	local ID

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
16
	// kBuckets define all the fingers to other nodes.
17 18 19 20
	Buckets []*Bucket
	bucketsize int
}

21 22 23 24 25 26
func NewRoutingTable(bucketsize int, local_id ID) *RoutingTable {
	rt := new(RoutingTable)
	rt.Buckets = []*Bucket{new(Bucket)}
	rt.bucketsize = bucketsize
	rt.local = local_id
	return rt
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
}

// Update adds or moves the given peer to the front of its respective bucket
// If a peer gets removed from a bucket, it is returned
func (rt *RoutingTable) Update(p *peer.Peer) *peer.Peer {
	peer_id := convertPeerID(p.ID)
	cpl := xor(peer_id, rt.local).commonPrefixLen()

	b_id := cpl
	if b_id >= len(rt.Buckets) {
		b_id = len(rt.Buckets) - 1
	}

	bucket := rt.Buckets[b_id]
	e := bucket.Find(p.ID)
	if e == nil {
		// New peer, add to bucket
		bucket.PushFront(p)

		// Are we past the max bucket size?
		if bucket.Len() > rt.bucketsize {
			if b_id == len(rt.Buckets) - 1 {
				new_bucket := bucket.Split(b_id, rt.local)
				rt.Buckets = append(rt.Buckets, new_bucket)
51 52 53 54
				if new_bucket.Len() > rt.bucketsize {
					// This is another very rare and annoying case
					panic("Case not handled.")
				}
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79

				// If all elements were on left side of split...
				if bucket.Len() > rt.bucketsize {
					return bucket.PopBack()
				}
			} else {
				// If the bucket cant split kick out least active node
				return bucket.PopBack()
			}
		}
		return nil
	} else {
		// If the peer is already in the table, move it to the front.
		// This signifies that it it "more active" and the less active nodes
		// Will as a result tend towards the back of the list
		bucket.MoveToFront(e)
		return nil
	}
}

// A helper struct to sort peers by their distance to the local node
type peerDistance struct {
	p *peer.Peer
	distance ID
}
80 81

// peerSorterArr implements sort.Interface to sort peers by xor distance
82 83 84 85
type peerSorterArr []*peerDistance
func (p peerSorterArr) Len() int {return len(p)}
func (p peerSorterArr) Swap(a, b int) {p[a],p[b] = p[b],p[a]}
func (p peerSorterArr) Less(a, b int) bool {
86
	return p[a].distance.Less(p[b].distance)
87 88 89
}
//

90
// Returns a single peer that is nearest to the given ID
91 92 93
func (rt *RoutingTable) NearestPeer(id ID) *peer.Peer {
	peers := rt.NearestPeers(id, 1)
	return peers[0]
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
94 95
}

96
// Returns a list of the 'count' closest peers to the given ID
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer {
	cpl := xor(id, rt.local).commonPrefixLen()

	// Get bucket at cpl index or last bucket
	var bucket *Bucket
	if cpl >= len(rt.Buckets) {
		bucket = rt.Buckets[len(rt.Buckets) - 1]
	} else {
		bucket = rt.Buckets[cpl]
	}

	if bucket.Len() == 0 {
		// This can happen, very rarely.
		panic("Case not yet implemented.")
	}

	var peerArr peerSorterArr
	plist := (*list.List)(bucket)
	for e := plist.Front();e != nil; e = e.Next() {
		p := e.Value.(*peer.Peer)
		p_id := convertPeerID(p.ID)
		pd := peerDistance{
			p: p,
			distance: xor(rt.local, p_id),
		}
		peerArr = append(peerArr, &pd)
	}

125
	// Sort by distance to local peer
126 127 128 129 130 131 132 133
	sort.Sort(peerArr)

	var out []*peer.Peer
	for i := 0; i < count && i < peerArr.Len(); i++ {
		out = append(out, peerArr[i].p)
	}

	return out
134
}