diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c53fa7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tmp.go diff --git a/id.go b/id.go index 09d14f7..d7aaf2c 100644 --- a/id.go +++ b/id.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "crypto/rand" "encoding/hex" ) @@ -32,6 +33,10 @@ func IDFromString(s string) (ID, error) { return id, nil } +func (idA ID) Equal(idB ID) bool { + return bytes.Equal(idA[:], idB[:]) +} + // Cmp returns true if idA > idB func (idA ID) Cmp(idB ID) bool { for i := 0; i < len(idA); i++ { diff --git a/id_test.go b/id_test.go index 8993272..e7abfe8 100644 --- a/id_test.go +++ b/id_test.go @@ -7,8 +7,12 @@ import ( ) func TestNewID(t *testing.T) { - _, err := NewID() - assert.Nil(t, err) + // get some IDs + // for i := 0; i < 10; i++ { + // id, err := NewID() + // assert.Nil(t, err) + // fmt.Println(id) + // } idA, err := IDFromString("0fd85ddddf15aeec2d5d8b01b013dbca030a18d7") assert.Nil(t, err) diff --git a/kademlia.go b/kademlia.go new file mode 100644 index 0000000..91fe140 --- /dev/null +++ b/kademlia.go @@ -0,0 +1,99 @@ +package main + +import ( + "math/bits" + "strconv" +) + +const ( + alpha = 3 // 'alpha', max parallalel calls + B = 20 // 'B', 160 bits, ID length + KBucketSize = 20 // 'K', bucket size +) + +type ListedNode struct { + ID ID + Addr string +} + +type Node struct { + ID ID + KBuckets [B * 8][]ListedNode +} + +func (n Node) String() string { + r := "Node ID: " + n.ID.String() + ", KBuckets:\n" + for i, kb := range n.KBuckets { + if len(kb) > 0 { + r += " KBucket " + strconv.Itoa(i) + "\n" + for _, e := range kb { + r += " " + e.ID.String() + "\n" + } + } + } + return r +} + +func NewNode() (Node, error) { + // TODO if node already has id, import it + id, err := NewID() + if err != nil { + return Node{}, err + } + + var n Node + n.ID = id + return n, nil +} + +func LoadNode(idStr string) (Node, error) { + id, err := IDFromString("0fd85ddddf15aeec2d5d8b01b013dbca030a18d7") + if err != nil { + return Node{}, err + } + var n Node + n.ID = id + return n, nil +} + +func (n Node) KBucket(o ID) int { + d := n.ID.Distance(o) + return kBucketByDistance(d[:]) + +} + +func kBucketByDistance(b []byte) int { + for i := 0; i < B; i++ { + for a := b[i] ^ 0; a != 0; a &= a - 1 { + return (B-1-i)*8 + (7 - bits.TrailingZeros8(bits.Reverse8(uint8(a)))) + } + } + return (B*8 - 1) - (B*8 - 1) +} + +func (n *Node) Update(o ListedNode) { + k := n.KBucket(o.ID) + kb := n.KBuckets[k] + if len(kb) >= KBucketSize { + // TODO ping the kb[0], and if no response, delete it, and add the current o (ID) + return + } + // check that is not already in the list + exist, _ := existsInListedNodes(n.KBuckets[k], o) + if exist { + // update position to the bottom + return + } + // not exists, add it to the kBucket + n.KBuckets[k] = append(n.KBuckets[k], o) + return +} + +func existsInListedNodes(lns []ListedNode, ln ListedNode) (bool, int) { + for i, v := range lns { + if v.ID.Equal(ln.ID) { + return true, i + } + } + return false, 0 +} diff --git a/kademlia_test.go b/kademlia_test.go new file mode 100644 index 0000000..7a2c2a2 --- /dev/null +++ b/kademlia_test.go @@ -0,0 +1,97 @@ +package main + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +var debug = true + +func TestCountZeros(t *testing.T) { + zeroes := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + assert.Equal(t, 0, kBucketByDistance(zeroes)) + + b := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} + assert.Equal(t, 20, len(b)) + assert.Equal(t, 159, kBucketByDistance(b)) + b[19] = 0x00 + assert.Equal(t, 159, kBucketByDistance(b)) + + b[0] = 0x0f + assert.Equal(t, 159-4, kBucketByDistance(b)) + + b[0] = 0x0c + assert.Equal(t, 159-4, kBucketByDistance(b)) + + b[0] = 0x00 + b[1] = 0x00 + b[2] = 0x0f + assert.Equal(t, 159-20, kBucketByDistance(b)) + + b[2] = 0x07 + assert.Equal(t, 159-21, kBucketByDistance(b)) + + b[2] = 0x03 + assert.Equal(t, 159-22, kBucketByDistance(b)) +} + +func TestNodeKBucket(t *testing.T) { + node, err := LoadNode("0fd85ddddf15aeec2d5d8b01b013dbca030a18d7") + assert.Nil(t, err) + + d := node.KBucket(node.ID) + assert.Equal(t, 0, d) // same node should have distance 0 + + idB, err := IDFromString("c48d8b53dbefb609ed4e94d386dd5b22efcb2c5b") + assert.Nil(t, err) + + d = node.KBucket(idB) + assert.Equal(t, 159, d) +} + +func TestUpdate(t *testing.T) { + nodeA, err := LoadNode("0fd85ddddf15aeec2d5d8b01b013dbca030a18d7") + assert.Nil(t, err) + + lnIDs := []string{ + "c48d8b53dbefb609ed4e94d386dd5b22efcb2c5b", + "420bfebd44fc62615253224328f40f29c9b225fa", + "6272bb67b1661abfa07d1d32cd9b810e531d42cd", + "07db608db033384895e48098a1bbe25266387463", + "c19c3285ab9ada4b420050ae1a204640b2bed508", + "f8971d5da24517c8cc5a316fb0658de8906c4155", + "04122a5f2dec42284147b1847ec1bc41ecd78626", + "407a90870d7b482a271446c85fda940ce78a4c7a", + "5ebe4539e7a33721a8623f7ebfab62600aa503e7", + "fc44a42879ef3a74d6bd8303bc3e4e205a92acf9", + "fc44a42879ef3a74d6bd8303bc3e4e205a92acf9", + "10c86f96ebaa1685a46a6417e6faa2ef34a68976", + "243c81515196a5b0e2d4675e73f0da3eead12793", + "0fd85ddddf15aeec2d5d8b01b013dbca030a18d7", + "0fd85ddddf15aeec2d5d8b01b013dbca030a18d5", + "0fd85ddddf15aeec2d5d8b01b013dbca030a18d0", + "0fd85ddddf15aeec2d5d8b01b013dbca030a1800", + "0750931c40a52a2facd220a02851f7d34f95e1fa", + "ebfba615ac50bcd0f5c2420741d032e885abf576", + } + for i := 0; i < len(lnIDs); i++ { + idI, err := IDFromString(lnIDs[i]) + assert.Nil(t, err) + lnI := ListedNode{ + ID: idI, + Addr: "", + } + nodeA.Update(lnI) + } + + if debug { + fmt.Println(nodeA) + } + + assert.Equal(t, len(nodeA.KBuckets[0]), 1) + assert.Equal(t, len(nodeA.KBuckets[1]), 1) + assert.Equal(t, len(nodeA.KBuckets[158]), 4) + assert.Equal(t, len(nodeA.KBuckets[159]), 5) +}