Commit 681a47eb authored by Juan Batiz-Benet's avatar Juan Batiz-Benet Committed by Brian Tiger Chow

cleaner KeySpace abstraction.

parent fa2b736b
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
}
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
}
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")
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment