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