github.com/zignig/go-ipfs@v0.0.0-20141111235910-c9e5fdf55a52/routing/kbucket/table.go (about)

     1  // package kbucket implements a kademlia 'k-bucket' routing table.
     2  package kbucket
     3  
     4  import (
     5  	"container/list"
     6  	"fmt"
     7  	"sort"
     8  	"sync"
     9  	"time"
    10  
    11  	peer "github.com/jbenet/go-ipfs/peer"
    12  	u "github.com/jbenet/go-ipfs/util"
    13  )
    14  
    15  var log = u.Logger("table")
    16  
    17  // RoutingTable defines the routing table.
    18  type RoutingTable struct {
    19  
    20  	// ID of the local peer
    21  	local ID
    22  
    23  	// Blanket lock, refine later for better performance
    24  	tabLock sync.RWMutex
    25  
    26  	// Maximum acceptable latency for peers in this cluster
    27  	maxLatency time.Duration
    28  
    29  	// kBuckets define all the fingers to other nodes.
    30  	Buckets    []*Bucket
    31  	bucketsize int
    32  }
    33  
    34  // NewRoutingTable creates a new routing table with a given bucketsize, local ID, and latency tolerance.
    35  func NewRoutingTable(bucketsize int, localID ID, latency time.Duration) *RoutingTable {
    36  	rt := new(RoutingTable)
    37  	rt.Buckets = []*Bucket{newBucket()}
    38  	rt.bucketsize = bucketsize
    39  	rt.local = localID
    40  	rt.maxLatency = latency
    41  	return rt
    42  }
    43  
    44  // Update adds or moves the given peer to the front of its respective bucket
    45  // If a peer gets removed from a bucket, it is returned
    46  func (rt *RoutingTable) Update(p peer.Peer) peer.Peer {
    47  	rt.tabLock.Lock()
    48  	defer rt.tabLock.Unlock()
    49  	peerID := ConvertPeerID(p.ID())
    50  	cpl := commonPrefixLen(peerID, rt.local)
    51  
    52  	bucketID := cpl
    53  	if bucketID >= len(rt.Buckets) {
    54  		bucketID = len(rt.Buckets) - 1
    55  	}
    56  
    57  	bucket := rt.Buckets[bucketID]
    58  	e := bucket.find(p.ID())
    59  	if e == nil {
    60  		// New peer, add to bucket
    61  		if p.GetLatency() > rt.maxLatency {
    62  			// Connection doesnt meet requirements, skip!
    63  			return nil
    64  		}
    65  		bucket.pushFront(p)
    66  
    67  		// Are we past the max bucket size?
    68  		if bucket.len() > rt.bucketsize {
    69  			// If this bucket is the rightmost bucket, and its full
    70  			// we need to split it and create a new bucket
    71  			if bucketID == len(rt.Buckets)-1 {
    72  				return rt.nextBucket()
    73  			} else {
    74  				// If the bucket cant split kick out least active node
    75  				return bucket.popBack()
    76  			}
    77  		}
    78  		return nil
    79  	}
    80  	// If the peer is already in the table, move it to the front.
    81  	// This signifies that it it "more active" and the less active nodes
    82  	// Will as a result tend towards the back of the list
    83  	bucket.moveToFront(e)
    84  	return nil
    85  }
    86  
    87  func (rt *RoutingTable) nextBucket() peer.Peer {
    88  	bucket := rt.Buckets[len(rt.Buckets)-1]
    89  	newBucket := bucket.Split(len(rt.Buckets)-1, rt.local)
    90  	rt.Buckets = append(rt.Buckets, newBucket)
    91  	if newBucket.len() > rt.bucketsize {
    92  		return rt.nextBucket()
    93  	}
    94  
    95  	// If all elements were on left side of split...
    96  	if bucket.len() > rt.bucketsize {
    97  		return bucket.popBack()
    98  	}
    99  	return nil
   100  }
   101  
   102  // A helper struct to sort peers by their distance to the local node
   103  type peerDistance struct {
   104  	p        peer.Peer
   105  	distance ID
   106  }
   107  
   108  // peerSorterArr implements sort.Interface to sort peers by xor distance
   109  type peerSorterArr []*peerDistance
   110  
   111  func (p peerSorterArr) Len() int      { return len(p) }
   112  func (p peerSorterArr) Swap(a, b int) { p[a], p[b] = p[b], p[a] }
   113  func (p peerSorterArr) Less(a, b int) bool {
   114  	return p[a].distance.less(p[b].distance)
   115  }
   116  
   117  //
   118  
   119  func copyPeersFromList(target ID, peerArr peerSorterArr, peerList *list.List) peerSorterArr {
   120  	for e := peerList.Front(); e != nil; e = e.Next() {
   121  		p := e.Value.(peer.Peer)
   122  		pID := ConvertPeerID(p.ID())
   123  		pd := peerDistance{
   124  			p:        p,
   125  			distance: xor(target, pID),
   126  		}
   127  		peerArr = append(peerArr, &pd)
   128  		if e == nil {
   129  			log.Debug("list element was nil")
   130  			return peerArr
   131  		}
   132  	}
   133  	return peerArr
   134  }
   135  
   136  // Find a specific peer by ID or return nil
   137  func (rt *RoutingTable) Find(id peer.ID) peer.Peer {
   138  	srch := rt.NearestPeers(ConvertPeerID(id), 1)
   139  	if len(srch) == 0 || !srch[0].ID().Equal(id) {
   140  		return nil
   141  	}
   142  	return srch[0]
   143  }
   144  
   145  // NearestPeer returns a single peer that is nearest to the given ID
   146  func (rt *RoutingTable) NearestPeer(id ID) peer.Peer {
   147  	peers := rt.NearestPeers(id, 1)
   148  	if len(peers) > 0 {
   149  		return peers[0]
   150  	}
   151  
   152  	log.Errorf("NearestPeer: Returning nil, table size = %d", rt.Size())
   153  	return nil
   154  }
   155  
   156  // NearestPeers returns a list of the 'count' closest peers to the given ID
   157  func (rt *RoutingTable) NearestPeers(id ID, count int) []peer.Peer {
   158  	rt.tabLock.RLock()
   159  	defer rt.tabLock.RUnlock()
   160  	cpl := commonPrefixLen(id, rt.local)
   161  
   162  	// Get bucket at cpl index or last bucket
   163  	var bucket *Bucket
   164  	if cpl >= len(rt.Buckets) {
   165  		cpl = len(rt.Buckets) - 1
   166  	}
   167  	bucket = rt.Buckets[cpl]
   168  
   169  	var peerArr peerSorterArr
   170  	if bucket.len() == 0 {
   171  		// In the case of an unusual split, one bucket may be empty.
   172  		// if this happens, search both surrounding buckets for nearest peer
   173  		if cpl > 0 {
   174  			plist := rt.Buckets[cpl-1].list
   175  			peerArr = copyPeersFromList(id, peerArr, plist)
   176  		}
   177  
   178  		if cpl < len(rt.Buckets)-1 {
   179  			plist := rt.Buckets[cpl+1].list
   180  			peerArr = copyPeersFromList(id, peerArr, plist)
   181  		}
   182  	} else {
   183  		peerArr = copyPeersFromList(id, peerArr, bucket.list)
   184  	}
   185  
   186  	// Sort by distance to local peer
   187  	sort.Sort(peerArr)
   188  
   189  	var out []peer.Peer
   190  	for i := 0; i < count && i < peerArr.Len(); i++ {
   191  		out = append(out, peerArr[i].p)
   192  	}
   193  
   194  	return out
   195  }
   196  
   197  // Size returns the total number of peers in the routing table
   198  func (rt *RoutingTable) Size() int {
   199  	var tot int
   200  	for _, buck := range rt.Buckets {
   201  		tot += buck.len()
   202  	}
   203  	return tot
   204  }
   205  
   206  // ListPeers takes a RoutingTable and returns a list of all peers from all buckets in the table.
   207  // NOTE: This is potentially unsafe... use at your own risk
   208  func (rt *RoutingTable) ListPeers() []peer.Peer {
   209  	var peers []peer.Peer
   210  	for _, buck := range rt.Buckets {
   211  		for e := buck.getIter(); e != nil; e = e.Next() {
   212  			peers = append(peers, e.Value.(peer.Peer))
   213  		}
   214  	}
   215  	return peers
   216  }
   217  
   218  // Print prints a descriptive statement about the provided RoutingTable
   219  func (rt *RoutingTable) Print() {
   220  	fmt.Printf("Routing Table, bs = %d, Max latency = %d\n", rt.bucketsize, rt.maxLatency)
   221  	rt.tabLock.RLock()
   222  	peers := rt.ListPeers()
   223  	for i, p := range peers {
   224  		fmt.Printf("%d) %s %s\n", i, p.ID().Pretty(), p.GetLatency().String())
   225  	}
   226  }