mirror of
https://github.com/arnaucube/go-dht.git
synced 2026-02-06 19:06:44 +01:00
add node structure (cmd, config...)
This commit is contained in:
56
kademlia/id.go
Normal file
56
kademlia/id.go
Normal 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
36
kademlia/id_test.go
Normal 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
99
kademlia/kademlia.go
Normal 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
118
kademlia/kademlia_test.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user