From bade1aa277b8531ccead82384930adab8eb841f0 Mon Sep 17 00:00:00 2001
From: Jeromy <jeromyj@gmail.com>
Date: Sun, 3 Aug 2014 15:04:24 -0700
Subject: [PATCH] tests for kbucket and some code cleanup

---
 routing/dht/bucket.go     |  63 +++++++++++++++++
 routing/dht/table.go      | 143 +++++---------------------------------
 routing/dht/table_test.go |  92 ++++++++++++++++++++++++
 routing/dht/util.go       |  78 +++++++++++++++++++++
 4 files changed, 249 insertions(+), 127 deletions(-)
 create mode 100644 routing/dht/bucket.go
 create mode 100644 routing/dht/table_test.go
 create mode 100644 routing/dht/util.go

diff --git a/routing/dht/bucket.go b/routing/dht/bucket.go
new file mode 100644
index 00000000..120ed29a
--- /dev/null
+++ b/routing/dht/bucket.go
@@ -0,0 +1,63 @@
+package dht
+
+import (
+	"container/list"
+
+	peer "github.com/jbenet/go-ipfs/peer"
+)
+// Bucket holds a list of peers.
+type Bucket list.List
+
+func (b *Bucket) Find(id peer.ID) *list.Element {
+	bucket_list := (*list.List)(b)
+	for e := bucket_list.Front(); e != nil; e = e.Next() {
+		if e.Value.(*peer.Peer).ID.Equal(id) {
+			return e
+		}
+	}
+	return nil
+}
+
+func (b *Bucket) MoveToFront(e *list.Element) {
+	bucket_list := (*list.List)(b)
+	bucket_list.MoveToFront(e)
+}
+
+func (b *Bucket) PushFront(p *peer.Peer) {
+	bucket_list := (*list.List)(b)
+	bucket_list.PushFront(p)
+}
+
+func (b *Bucket) PopBack() *peer.Peer {
+	bucket_list := (*list.List)(b)
+	last := bucket_list.Back()
+	bucket_list.Remove(last)
+	return last.Value.(*peer.Peer)
+}
+
+func (b *Bucket) Len() int {
+	bucket_list := (*list.List)(b)
+	return bucket_list.Len()
+}
+
+// Splits a buckets peers into two buckets, the methods receiver will have
+// peers with CPL equal to cpl, the returned bucket will have peers with CPL
+// greater than cpl (returned bucket has closer peers)
+func (b *Bucket) Split(cpl int, target ID) *Bucket {
+	bucket_list := (*list.List)(b)
+	out := list.New()
+	e := bucket_list.Front()
+	for e != nil {
+		peer_id := convertPeerID(e.Value.(*peer.Peer).ID)
+		peer_cpl := xor(peer_id, target).commonPrefixLen()
+		if peer_cpl > cpl {
+			cur := e
+			out.PushBack(e.Value)
+			e = e.Next()
+			bucket_list.Remove(cur)
+			continue
+		}
+		e = e.Next()
+	}
+	return (*Bucket)(out)
+}
diff --git a/routing/dht/table.go b/routing/dht/table.go
index b8c20f8a..c1eed534 100644
--- a/routing/dht/table.go
+++ b/routing/dht/table.go
@@ -1,76 +1,12 @@
 package dht
 
 import (
-	"bytes"
 	"container/list"
 	"sort"
 
-	"crypto/sha256"
-
 	peer "github.com/jbenet/go-ipfs/peer"
-	u "github.com/jbenet/go-ipfs/util"
 )
 
-// ID for IpfsDHT should be a byte slice, to allow for simpler operations
-// (xor). DHT ids are based on the peer.IDs.
-//
-// NOTE: peer.IDs are biased because they are multihashes (first bytes
-// biased). Thus, may need to re-hash keys (uniform dist). TODO(jbenet)
-type ID []byte
-
-// Bucket holds a list of peers.
-type Bucket list.List
-
-func (b *Bucket) Find(id peer.ID) *list.Element {
-	bucket_list := (*list.List)(b)
-	for e := bucket_list.Front(); e != nil; e = e.Next() {
-		if e.Value.(*peer.Peer).ID.Equal(id) {
-			return e
-		}
-	}
-	return nil
-}
-
-func (b *Bucket) MoveToFront(e *list.Element) {
-	bucket_list := (*list.List)(b)
-	bucket_list.MoveToFront(e)
-}
-
-func (b *Bucket) PushFront(p *peer.Peer) {
-	bucket_list := (*list.List)(b)
-	bucket_list.PushFront(p)
-}
-
-func (b *Bucket) PopBack() *peer.Peer {
-	bucket_list := (*list.List)(b)
-	last := bucket_list.Back()
-	bucket_list.Remove(last)
-	return last.Value.(*peer.Peer)
-}
-
-func (b *Bucket) Len() int {
-	bucket_list := (*list.List)(b)
-	return bucket_list.Len()
-}
-
-func (b *Bucket) Split(cpl int, target ID) *Bucket {
-	bucket_list := (*list.List)(b)
-	out := list.New()
-	e := bucket_list.Front()
-	for e != nil {
-		peer_id := convertPeerID(e.Value.(*peer.Peer).ID)
-		peer_cpl := xor(peer_id, target).commonPrefixLen()
-		if peer_cpl > cpl {
-			cur := e
-			out.PushBack(e.Value)
-			e = e.Next()
-			bucket_list.Remove(cur)
-			continue
-		}
-	}
-	return (*Bucket)(out)
-}
-
 // RoutingTable defines the routing table.
 type RoutingTable struct {
 
@@ -82,14 +18,12 @@ type RoutingTable struct {
 	bucketsize int
 }
 
-func convertPeerID(id peer.ID) ID {
-	hash := sha256.Sum256(id)
-	return hash[:]
-}
-
-func convertKey(id u.Key) ID {
-	hash := sha256.Sum256([]byte(id))
-	return hash[:]
+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
 }
 
 // Update adds or moves the given peer to the front of its respective bucket
@@ -114,6 +48,10 @@ func (rt *RoutingTable) Update(p *peer.Peer) *peer.Peer {
 			if b_id == len(rt.Buckets) - 1 {
 				new_bucket := bucket.Split(b_id, rt.local)
 				rt.Buckets = append(rt.Buckets, new_bucket)
+				if new_bucket.Len() > rt.bucketsize {
+					// This is another very rare and annoying case
+					panic("Case not handled.")
+				}
 
 				// If all elements were on left side of split...
 				if bucket.Len() > rt.bucketsize {
@@ -139,20 +77,23 @@ type peerDistance struct {
 	p *peer.Peer
 	distance ID
 }
+
+// peerSorterArr implements sort.Interface to sort peers by xor distance
 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 {
-	return p[a].distance.Less(p[b])
+	return p[a].distance.Less(p[b].distance)
 }
 //
 
+// Returns a single peer that is nearest to the given ID
 func (rt *RoutingTable) NearestPeer(id ID) *peer.Peer {
 	peers := rt.NearestPeers(id, 1)
 	return peers[0]
 }
 
-//TODO: make this accept an ID, requires method of converting keys to IDs
+// Returns a list of the 'count' closest peers to the given ID
 func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer {
 	cpl := xor(id, rt.local).commonPrefixLen()
 
@@ -170,7 +111,6 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer {
 	}
 
 	var peerArr peerSorterArr
-
 	plist := (*list.List)(bucket)
 	for e := plist.Front();e != nil; e = e.Next() {
 		p := e.Value.(*peer.Peer)
@@ -182,6 +122,7 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer {
 		peerArr = append(peerArr, &pd)
 	}
 
+	// Sort by distance to local peer
 	sort.Sort(peerArr)
 
 	var out []*peer.Peer
@@ -191,55 +132,3 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer {
 
 	return out
 }
-
-func (id ID) Equal(other ID) bool {
-	return bytes.Equal(id, other)
-}
-
-func (id ID) Less(other interface{}) bool {
-	a, b := equalizeSizes(id, other.(ID))
-	for i := 0; i < len(a); i++ {
-		if a[i] != b[i] {
-			return a[i] < b[i]
-		}
-	}
-	return len(a) < len(b)
-}
-
-func (id ID) commonPrefixLen() int {
-	for i := 0; i < len(id); i++ {
-		for j := 0; j < 8; j++ {
-			if (id[i]>>uint8(7-j))&0x1 != 0 {
-				return i*8 + j
-			}
-		}
-	}
-	return len(id)*8 - 1
-}
-
-func xor(a, b ID) ID {
-	a, b = equalizeSizes(a, b)
-
-	c := make(ID, len(a))
-	for i := 0; i < len(a); i++ {
-		c[i] = a[i] ^ b[i]
-	}
-	return c
-}
-
-func equalizeSizes(a, b ID) (ID, ID) {
-	la := len(a)
-	lb := len(b)
-
-	if la < lb {
-		na := make([]byte, lb)
-		copy(na, a)
-		a = na
-	} else if lb < la {
-		nb := make([]byte, la)
-		copy(nb, b)
-		b = nb
-	}
-
-	return a, b
-}
diff --git a/routing/dht/table_test.go b/routing/dht/table_test.go
new file mode 100644
index 00000000..cb52bd1a
--- /dev/null
+++ b/routing/dht/table_test.go
@@ -0,0 +1,92 @@
+package dht
+
+import (
+	crand "crypto/rand"
+	"crypto/sha256"
+	"math/rand"
+	"container/list"
+	"testing"
+
+	peer "github.com/jbenet/go-ipfs/peer"
+)
+
+func _randPeer() *peer.Peer {
+	p := new(peer.Peer)
+	p.ID = make(peer.ID, 16)
+	crand.Read(p.ID)
+	return p
+}
+
+func _randID() ID {
+	buf := make([]byte, 16)
+	crand.Read(buf)
+
+	hash := sha256.Sum256(buf)
+	return ID(hash[:])
+}
+
+// Test basic features of the bucket struct
+func TestBucket(t *testing.T) {
+	b := new(Bucket)
+
+	peers := make([]*peer.Peer, 100)
+	for i := 0; i < 100; i++ {
+		peers[i] = _randPeer()
+		b.PushFront(peers[i])
+	}
+
+	local := _randPeer()
+	local_id := convertPeerID(local.ID)
+
+	i := rand.Intn(len(peers))
+	e := b.Find(peers[i].ID)
+	if e == nil {
+		t.Errorf("Failed to find peer: %v", peers[i])
+	}
+
+	spl := b.Split(0, convertPeerID(local.ID))
+	llist := (*list.List)(b)
+	for e := llist.Front(); e != nil; e = e.Next() {
+		p := convertPeerID(e.Value.(*peer.Peer).ID)
+		cpl := xor(p, local_id).commonPrefixLen()
+		if cpl > 0 {
+			t.Fatalf("Split failed. found id with cpl > 0 in 0 bucket")
+		}
+	}
+
+	rlist := (*list.List)(spl)
+	for e := rlist.Front(); e != nil; e = e.Next() {
+		p := convertPeerID(e.Value.(*peer.Peer).ID)
+		cpl := xor(p, local_id).commonPrefixLen()
+		if cpl == 0 {
+			t.Fatalf("Split failed. found id with cpl == 0 in non 0 bucket")
+		}
+	}
+}
+
+// Right now, this just makes sure that it doesnt hang or crash
+func TestTableUpdate(t *testing.T) {
+	local := _randPeer()
+	rt := NewRoutingTable(10, convertPeerID(local.ID))
+
+	peers := make([]*peer.Peer, 100)
+	for i := 0; i < 100; i++ {
+		peers[i] = _randPeer()
+	}
+
+	// Testing Update
+	for i := 0; i < 10000; i++ {
+		p := rt.Update(peers[rand.Intn(len(peers))])
+		if p != nil {
+			t.Log("evicted peer.")
+		}
+	}
+
+	for i := 0; i < 100; i++ {
+		id := _randID()
+		ret := rt.NearestPeers(id, 5)
+		if len(ret) == 0 {
+			t.Fatal("Failed to find node near ID.")
+		}
+	}
+}
diff --git a/routing/dht/util.go b/routing/dht/util.go
new file mode 100644
index 00000000..eed8d930
--- /dev/null
+++ b/routing/dht/util.go
@@ -0,0 +1,78 @@
+package dht
+
+import (
+	"bytes"
+	"crypto/sha256"
+
+	peer "github.com/jbenet/go-ipfs/peer"
+	u "github.com/jbenet/go-ipfs/util"
+)
+
+// ID for IpfsDHT should be a byte slice, to allow for simpler operations
+// (xor). DHT ids are based on the peer.IDs.
+//
+// NOTE: peer.IDs are biased because they are multihashes (first bytes
+// biased). Thus, may need to re-hash keys (uniform dist). TODO(jbenet)
+type ID []byte
+
+func (id ID) Equal(other ID) bool {
+	return bytes.Equal(id, other)
+}
+
+func (id ID) Less(other interface{}) bool {
+	a, b := equalizeSizes(id, other.(ID))
+	for i := 0; i < len(a); i++ {
+		if a[i] != b[i] {
+			return a[i] < b[i]
+		}
+	}
+	return len(a) < len(b)
+}
+
+func (id ID) commonPrefixLen() int {
+	for i := 0; i < len(id); i++ {
+		for j := 0; j < 8; j++ {
+			if (id[i]>>uint8(7-j))&0x1 != 0 {
+				return i*8 + j
+			}
+		}
+	}
+	return len(id)*8 - 1
+}
+
+func xor(a, b ID) ID {
+	a, b = equalizeSizes(a, b)
+
+	c := make(ID, len(a))
+	for i := 0; i < len(a); i++ {
+		c[i] = a[i] ^ b[i]
+	}
+	return c
+}
+
+func equalizeSizes(a, b ID) (ID, ID) {
+	la := len(a)
+	lb := len(b)
+
+	if la < lb {
+		na := make([]byte, lb)
+		copy(na, a)
+		a = na
+	} else if lb < la {
+		nb := make([]byte, la)
+		copy(nb, b)
+		b = nb
+	}
+
+	return a, b
+}
+
+func convertPeerID(id peer.ID) ID {
+	hash := sha256.Sum256(id)
+	return hash[:]
+}
+
+func convertKey(id u.Key) ID {
+	hash := sha256.Sum256([]byte(id))
+	return hash[:]
+}
-- 
GitLab