From b838cc061902f8298a947687a59f307e61f331a7 Mon Sep 17 00:00:00 2001
From: Juan Batiz-Benet <juan@benet.ai>
Date: Wed, 17 Sep 2014 01:39:08 -0700
Subject: [PATCH] cleaner KeySpace abstraction.

---
 routing/dht/keyspace/keyspace.go |  95 +++++++++++++++++++++
 routing/dht/keyspace/xor.go      |  74 ++++++++++++++++
 routing/dht/keyspace/xor_test.go | 139 +++++++++++++++++++++++++++++++
 3 files changed, 308 insertions(+)
 create mode 100644 routing/dht/keyspace/keyspace.go
 create mode 100644 routing/dht/keyspace/xor.go
 create mode 100644 routing/dht/keyspace/xor_test.go

diff --git a/routing/dht/keyspace/keyspace.go b/routing/dht/keyspace/keyspace.go
new file mode 100644
index 00000000..3d3260b2
--- /dev/null
+++ b/routing/dht/keyspace/keyspace.go
@@ -0,0 +1,95 @@
+package keyspace
+
+import (
+	"sort"
+
+	"math/big"
+)
+
+// Key represents an identifier in a KeySpace. It holds a reference to the
+// associated KeySpace, as well references to both the Original identifier,
+// as well as the new, KeySpace Adjusted one.
+type Key struct {
+
+	// Space is the KeySpace this Key is related to.
+	Space KeySpace
+
+	// Original is the original value of the identifier
+	Original []byte
+
+	// Adjusted is the new value of the identifier, in the KeySpace.
+	Adjusted []byte
+}
+
+// Equal returns whether this key is equal to another.
+func (k1 Key) Equal(k2 Key) bool {
+	if k1.Space != k2.Space {
+		panic("k1 and k2 not in same key space.")
+	}
+	return k1.Space.Equal(k1, k2)
+}
+
+// Less returns whether this key comes before another.
+func (k1 Key) Less(k2 Key) bool {
+	if k1.Space != k2.Space {
+		panic("k1 and k2 not in same key space.")
+	}
+	return k1.Space.Less(k1, k2)
+}
+
+// Distance returns this key's distance to another
+func (k1 Key) Distance(k2 Key) *big.Int {
+	if k1.Space != k2.Space {
+		panic("k1 and k2 not in same key space.")
+	}
+	return k1.Space.Distance(k1, k2)
+}
+
+// KeySpace is an object used to do math on identifiers. Each keyspace has its
+// own properties and rules. See XorKeySpace.
+type KeySpace interface {
+
+	// Key converts an identifier into a Key in this space.
+	Key([]byte) Key
+
+	// Equal returns whether keys are equal in this key space
+	Equal(Key, Key) bool
+
+	// Distance returns the distance metric in this key space
+	Distance(Key, Key) *big.Int
+
+	// Less returns whether the first key is smaller than the second.
+	Less(Key, Key) bool
+}
+
+// byDistanceToCenter is a type used to sort Keys by proximity to a center.
+type byDistanceToCenter struct {
+	Center Key
+	Keys   []Key
+}
+
+func (s byDistanceToCenter) Len() int {
+	return len(s.Keys)
+}
+
+func (s byDistanceToCenter) Swap(i, j int) {
+	s.Keys[i], s.Keys[j] = s.Keys[j], s.Keys[i]
+}
+
+func (s byDistanceToCenter) Less(i, j int) bool {
+	a := s.Center.Distance(s.Keys[i])
+	b := s.Center.Distance(s.Keys[j])
+	return a.Cmp(b) == -1
+}
+
+// SortByDistance takes a KeySpace, a center Key, and a list of Keys toSort.
+// It returns a new list, where the Keys toSort have been sorted by their
+// distance to the center Key.
+func SortByDistance(sp KeySpace, center Key, toSort []Key) []Key {
+	bdtc := &byDistanceToCenter{
+		Center: center,
+		Keys:   toSort[:], // copy
+	}
+	sort.Sort(bdtc)
+	return bdtc.Keys
+}
diff --git a/routing/dht/keyspace/xor.go b/routing/dht/keyspace/xor.go
new file mode 100644
index 00000000..8cbef30e
--- /dev/null
+++ b/routing/dht/keyspace/xor.go
@@ -0,0 +1,74 @@
+package keyspace
+
+import (
+	"bytes"
+	"crypto/sha256"
+	"math/big"
+)
+
+// XORKeySpace is a KeySpace which:
+// - normalizes identifiers using a cryptographic hash (sha256)
+// - measures distance by XORing keys together
+var XORKeySpace = &xorKeySpace{}
+var _ KeySpace = XORKeySpace // ensure it conforms
+
+type xorKeySpace struct{}
+
+// Key converts an identifier into a Key in this space.
+func (s *xorKeySpace) Key(id []byte) Key {
+	hash := sha256.Sum256(id)
+	key := hash[:]
+	return Key{
+		Space:    s,
+		Original: id,
+		Adjusted: key,
+	}
+}
+
+// Equal returns whether keys are equal in this key space
+func (s *xorKeySpace) Equal(k1, k2 Key) bool {
+	return bytes.Equal(k1.Adjusted, k2.Adjusted)
+}
+
+// Distance returns the distance metric in this key space
+func (s *xorKeySpace) Distance(k1, k2 Key) *big.Int {
+	// XOR the keys
+	k3 := XOR(k1.Adjusted, k2.Adjusted)
+
+	// interpret it as an integer
+	dist := big.NewInt(0).SetBytes(k3)
+	return dist
+}
+
+// Less returns whether the first key is smaller than the second.
+func (s *xorKeySpace) Less(k1, k2 Key) bool {
+	a := k1.Adjusted
+	b := k2.Adjusted
+	for i := 0; i < len(a); i++ {
+		if a[i] != b[i] {
+			return a[i] < b[i]
+		}
+	}
+	return true
+}
+
+// XOR takes two byte slices, XORs them together, returns the resulting slice.
+func XOR(a, b []byte) []byte {
+	c := make([]byte, len(a))
+	for i := 0; i < len(a); i++ {
+		c[i] = a[i] ^ b[i]
+	}
+	return c
+}
+
+// ZeroPrefixLen returns the number of consecutive zeroes in a byte slice.
+func ZeroPrefixLen(id []byte) 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
+}
diff --git a/routing/dht/keyspace/xor_test.go b/routing/dht/keyspace/xor_test.go
new file mode 100644
index 00000000..58987ad1
--- /dev/null
+++ b/routing/dht/keyspace/xor_test.go
@@ -0,0 +1,139 @@
+package keyspace
+
+import (
+	"bytes"
+	"math/big"
+	"testing"
+)
+
+func TestXOR(t *testing.T) {
+	cases := [][3][]byte{
+		[3][]byte{
+			[]byte{0xFF, 0xFF, 0xFF},
+			[]byte{0xFF, 0xFF, 0xFF},
+			[]byte{0x00, 0x00, 0x00},
+		},
+		[3][]byte{
+			[]byte{0x00, 0xFF, 0x00},
+			[]byte{0xFF, 0xFF, 0xFF},
+			[]byte{0xFF, 0x00, 0xFF},
+		},
+		[3][]byte{
+			[]byte{0x55, 0x55, 0x55},
+			[]byte{0x55, 0xFF, 0xAA},
+			[]byte{0x00, 0xAA, 0xFF},
+		},
+	}
+
+	for _, c := range cases {
+		r := XOR(c[0], c[1])
+		if !bytes.Equal(r, c[2]) {
+			t.Error("XOR failed")
+		}
+	}
+}
+
+func TestPrefixLen(t *testing.T) {
+	cases := [][]byte{
+		[]byte{0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00},
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x00, 0x58, 0xFF, 0x80, 0x00, 0x00, 0xF0},
+	}
+	lens := []int{24, 56, 9}
+
+	for i, c := range cases {
+		r := ZeroPrefixLen(c)
+		if r != lens[i] {
+			t.Errorf("ZeroPrefixLen failed: %v != %v", r, lens[i])
+		}
+	}
+
+}
+
+func TestXorKeySpace(t *testing.T) {
+
+	ids := [][]byte{
+		[]byte{0xFF, 0xFF, 0xFF, 0xFF},
+		[]byte{0x00, 0x00, 0x00, 0x00},
+		[]byte{0xFF, 0xFF, 0xFF, 0xF0},
+	}
+
+	ks := [][2]Key{
+		[2]Key{XORKeySpace.Key(ids[0]), XORKeySpace.Key(ids[0])},
+		[2]Key{XORKeySpace.Key(ids[1]), XORKeySpace.Key(ids[1])},
+		[2]Key{XORKeySpace.Key(ids[2]), XORKeySpace.Key(ids[2])},
+	}
+
+	for i, set := range ks {
+		if !set[0].Equal(set[1]) {
+			t.Errorf("Key not eq. %v != %v", set[0], set[1])
+		}
+
+		if !bytes.Equal(set[0].Adjusted, set[1].Adjusted) {
+			t.Errorf("Key gen failed. %v != %v", set[0].Adjusted, set[1].Adjusted)
+		}
+
+		if !bytes.Equal(set[0].Original, ids[i]) {
+			t.Errorf("ptrs to original. %v != %v", set[0].Original, ids[i])
+		}
+
+		if len(set[0].Adjusted) != 32 {
+			t.Errorf("key length incorrect. 32 != %d", len(set[0].Adjusted))
+		}
+	}
+
+	for i := 1; i < len(ks); i++ {
+		if ks[i][0].Less(ks[i-1][0]) == ks[i-1][0].Less(ks[i][0]) {
+			t.Errorf("less should be different.")
+		}
+
+		if ks[i][0].Distance(ks[i-1][0]).Cmp(ks[i-1][0].Distance(ks[i][0])) != 0 {
+			t.Errorf("distance should be the same.")
+		}
+
+		if ks[i][0].Equal(ks[i-1][0]) {
+			t.Errorf("Keys should not be eq. %v != %v", ks[i][0], ks[i-1][0])
+		}
+	}
+}
+
+func TestCenterSorting(t *testing.T) {
+
+	adjs := [][]byte{
+		[]byte{173, 149, 19, 27, 192, 183, 153, 192, 177, 175, 71, 127, 177, 79, 207, 38, 166, 169, 247, 96, 121, 228, 139, 240, 144, 172, 183, 232, 54, 123, 253, 14},
+		[]byte{223, 63, 97, 152, 4, 169, 47, 219, 64, 87, 25, 45, 196, 61, 215, 72, 234, 119, 138, 220, 82, 188, 73, 140, 232, 5, 36, 192, 20, 184, 17, 25},
+		[]byte{73, 176, 221, 176, 149, 143, 22, 42, 129, 124, 213, 114, 232, 95, 189, 154, 18, 3, 122, 132, 32, 199, 53, 185, 58, 157, 117, 78, 52, 146, 157, 127},
+		[]byte{73, 176, 221, 176, 149, 143, 22, 42, 129, 124, 213, 114, 232, 95, 189, 154, 18, 3, 122, 132, 32, 199, 53, 185, 58, 157, 117, 78, 52, 146, 157, 127},
+		[]byte{73, 176, 221, 176, 149, 143, 22, 42, 129, 124, 213, 114, 232, 95, 189, 154, 18, 3, 122, 132, 32, 199, 53, 185, 58, 157, 117, 78, 52, 146, 157, 126},
+		[]byte{73, 0, 221, 176, 149, 143, 22, 42, 129, 124, 213, 114, 232, 95, 189, 154, 18, 3, 122, 132, 32, 199, 53, 185, 58, 157, 117, 78, 52, 146, 157, 127},
+	}
+
+	keys := make([]Key, len(adjs))
+	for i, a := range adjs {
+		keys[i] = Key{Space: XORKeySpace, Adjusted: a}
+	}
+
+	cmp := func(a int, b *big.Int) int {
+		return big.NewInt(int64(a)).Cmp(b)
+	}
+
+	if 0 != cmp(0, keys[2].Distance(keys[3])) {
+		t.Errorf("distance calculation wrong: %v", keys[2].Distance(keys[3]))
+	}
+
+	if 0 != cmp(1, keys[2].Distance(keys[4])) {
+		t.Errorf("distance calculation wrong: %v", keys[2].Distance(keys[4]))
+	}
+
+	d1 := keys[2].Distance(keys[5])
+	d2 := XOR(keys[2].Adjusted, keys[5].Adjusted)
+	d2 = d2[len(keys[2].Adjusted)-len(d1.Bytes()):] // skip empty space for big
+	if !bytes.Equal(d1.Bytes(), d2) {
+		t.Errorf("bytes should be the same. %v == %v", d1.Bytes(), d2)
+	}
+
+	if -1 != cmp(2<<32, keys[2].Distance(keys[5])) {
+		t.Errorf("2<<32 should be smaller")
+	}
+
+}
-- 
GitLab