add node structure (cmd, config...)

This commit is contained in:
arnaucube
2019-12-07 17:27:30 +01:00
parent 5a4d536f0d
commit a3d1d8db78
11 changed files with 419 additions and 54 deletions

56
kademlia/id.go Normal file
View File

@@ -0,0 +1,56 @@
package kademlia
import (
"bytes"
"crypto/rand"
"encoding/hex"
)
type ID [B]byte
func NewID() (ID, error) {
b := make([]byte, 32)
_, err := rand.Read(b)
if err != nil {
return ID{}, err
}
var id ID
copy(id[:], b[:B])
return id, nil
}
func (id ID) String() string {
return hex.EncodeToString(id[:])
}
func IDFromString(s string) (ID, error) {
b, err := hex.DecodeString(s)
if err != nil {
return ID{}, err
}
var id ID
copy(id[:], b[:B])
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++ {
if idA[i] != idB[i] {
return idA[i] > idB[i]
}
}
return false
}
func (idA ID) Distance(idB ID) ID {
var d ID
for i := 0; i < B; i++ {
d[i] = idA[i] ^ idB[i]
}
return d
}

36
kademlia/id_test.go Normal file
View File

@@ -0,0 +1,36 @@
package kademlia
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewID(t *testing.T) {
// 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)
assert.Equal(t, "0fd85ddddf15aeec2d5d8b01b013dbca030a18d7", idA.String())
}
func TestIDCmp(t *testing.T) {
idA, err := IDFromString("0fd85ddddf15aeec2d5d8b01b013dbca030a18d7")
assert.Nil(t, err)
idB, err := IDFromString("c48d8b53dbefb609ed4e94d386dd5b22efcb2c5b")
assert.Nil(t, err)
assert.True(t, !idA.Cmp(idB))
}
func TestIDDistance(t *testing.T) {
idA, err := IDFromString("0fd85ddddf15aeec2d5d8b01b013dbca030a18d7")
assert.Nil(t, err)
idB, err := IDFromString("c48d8b53dbefb609ed4e94d386dd5b22efcb2c5b")
assert.Nil(t, err)
assert.Equal(t, "cb55d68e04fa18e5c0131fd236ce80e8ecc1348c", idA.Distance(idB).String())
}

99
kademlia/kademlia.go Normal file
View File

@@ -0,0 +1,99 @@
package kademlia
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 Kademlia struct {
ID ID
KBuckets [B * 8][]ListedNode
}
func NewKademliaTable(id ID) *Kademlia {
return &Kademlia{
ID: id,
}
}
func (kad Kademlia) String() string {
r := "Node ID: " + kad.ID.String() + ", KBuckets:\n"
for i, kb := range kad.KBuckets {
if len(kb) > 0 {
r += " KBucket " + strconv.Itoa(i) + "\n"
for _, e := range kb {
r += " " + e.ID.String() + "\n"
}
}
}
return r
}
func (kad Kademlia) KBucket(o ID) int {
d := kad.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 (kad *Kademlia) Update(o ListedNode) {
k := kad.KBucket(o.ID)
kb := kad.KBuckets[k]
if len(kb) >= KBucketSize {
// if n.KBuckets[k] is alrady full, perform ping of the first element
kad.Ping(k, o)
return
}
// check that is not already in the list
exist, pos := existsInListedNodes(kad.KBuckets[k], o)
if exist {
// update position of o to the bottom
kad.KBuckets[k] = moveToBottom(kad.KBuckets[k], pos)
return
}
// not exists, add it to the kBucket
kad.KBuckets[k] = append(kad.KBuckets[k], o)
return
}
func (kad *Kademlia) Ping(k int, o ListedNode) {
// TODO when rpc layer is done
// ping the n.KBuckets[k][0] (using goroutine)
// if no response (timeout), delete it and add 'o'
// n.KBuckets[k][0] = o
}
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
}
func moveToBottom(kb []ListedNode, k int) []ListedNode {
e := kb[k]
kb = append(kb[:k], kb[k+1:]...)
kb = append(kb[:], e)
return kb
}

118
kademlia/kademlia_test.go Normal file
View File

@@ -0,0 +1,118 @@
package kademlia
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
var debug = false
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 TestKBucket(t *testing.T) {
idA, err := IDFromString("0fd85ddddf15aeec2d5d8b01b013dbca030a18d7")
assert.Nil(t, err)
kademlia := NewKademliaTable(idA)
d := kademlia.KBucket(kademlia.ID)
assert.Equal(t, 0, d) // same node should have distance 0
idB, err := IDFromString("c48d8b53dbefb609ed4e94d386dd5b22efcb2c5b")
assert.Nil(t, err)
d = kademlia.KBucket(idB)
assert.Equal(t, 159, d)
}
func prepareTestListedNodes() []ListedNode {
lnIDs := []string{
"c48d8b53dbefb609ed4e94d386dd5b22efcb2c5b",
"420bfebd44fc62615253224328f40f29c9b225fa",
"6272bb67b1661abfa07d1d32cd9b810e531d42cd",
"07db608db033384895e48098a1bbe25266387463",
"c19c3285ab9ada4b420050ae1a204640b2bed508",
"f8971d5da24517c8cc5a316fb0658de8906c4155",
"04122a5f2dec42284147b1847ec1bc41ecd78626",
"407a90870d7b482a271446c85fda940ce78a4c7a",
"5ebe4539e7a33721a8623f7ebfab62600aa503e7",
"fc44a42879ef3a74d6bd8303bc3e4e205a92acf9",
"fc44a42879ef3a74d6bd8303bc3e4e205a92acf9",
"10c86f96ebaa1685a46a6417e6faa2ef34a68976",
"243c81515196a5b0e2d4675e73f0da3eead12793",
"0fd85ddddf15aeec2d5d8b01b013dbca030a18d7",
"0fd85ddddf15aeec2d5d8b01b013dbca030a18d5",
"0fd85ddddf15aeec2d5d8b01b013dbca030a18d0",
"0fd85ddddf15aeec2d5d8b01b013dbca030a1800",
"0750931c40a52a2facd220a02851f7d34f95e1fa",
"ebfba615ac50bcd0f5c2420741d032e885abf576",
}
var lns []ListedNode
for i := 0; i < len(lnIDs); i++ {
idI, err := IDFromString(lnIDs[i])
if err != nil {
panic(err)
}
lnI := ListedNode{
ID: idI,
Addr: "",
}
lns = append(lns, lnI)
}
return lns
}
func TestMoveToBottom(t *testing.T) {
lns := prepareTestListedNodes()
movedElem := lns[3]
assert.NotEqual(t, movedElem, lns[len(lns)-1])
lns = moveToBottom(lns, 3)
assert.Equal(t, movedElem, lns[len(lns)-1])
}
func TestUpdate(t *testing.T) {
idA, err := IDFromString("0fd85ddddf15aeec2d5d8b01b013dbca030a18d7")
assert.Nil(t, err)
kademlia := NewKademliaTable(idA)
lns := prepareTestListedNodes()
for _, lnI := range lns {
kademlia.Update(lnI)
}
if debug {
fmt.Println(kademlia)
}
assert.Equal(t, len(kademlia.KBuckets[0]), 1)
assert.Equal(t, len(kademlia.KBuckets[1]), 1)
assert.Equal(t, len(kademlia.KBuckets[158]), 4)
assert.Equal(t, len(kademlia.KBuckets[159]), 5)
}