package kademlia
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/bits"
|
|
"net/rpc"
|
|
"strconv"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
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
|
|
Port string
|
|
}
|
|
|
|
type Kademlia struct {
|
|
// N is this node data
|
|
N ListedNode
|
|
KBuckets [B][]ListedNode
|
|
}
|
|
|
|
func NewKademliaTable(id ID, addr, port string) *Kademlia {
|
|
return &Kademlia{
|
|
N: ListedNode{
|
|
ID: id,
|
|
Addr: addr,
|
|
Port: port,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (kad Kademlia) String() string {
|
|
r := "Node ID: " + kad.N.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) GetClosestKBucket(id ID) (int, error) {
|
|
kb := kad.KBucket(id)
|
|
if kb == 0 {
|
|
// is this node
|
|
return kb, nil
|
|
}
|
|
if len(kad.KBuckets[kb]) != 0 {
|
|
return kb, nil
|
|
}
|
|
for i := 0; kb-i > 0 || kb+i < 8; i++ {
|
|
if len(kad.KBuckets[kb+i]) != 0 {
|
|
return kb + i, nil
|
|
}
|
|
if len(kad.KBuckets[kb-i]) != 0 {
|
|
return kb - i, nil
|
|
}
|
|
}
|
|
return 0, errors.New("not found")
|
|
}
|
|
|
|
func (kad Kademlia) KBucket(o ID) int {
|
|
d := kad.N.ID.Distance(o)
|
|
return kBucketByDistance(d[:])
|
|
|
|
}
|
|
|
|
func kBucketByDistance(b []byte) int {
|
|
z := countZeroes(b)
|
|
if z == 0 {
|
|
return 0
|
|
}
|
|
for i := 0; i < 8; i++ {
|
|
if z>>uint8(i) == 1 { // check until 1 instead of 0 to avoid one operation
|
|
return i
|
|
}
|
|
}
|
|
return 7
|
|
}
|
|
|
|
func countZeroes(b []byte) int {
|
|
for i := 0; i < B; i++ {
|
|
for a := b[i] ^ 0; a != 0; a &= a - 1 {
|
|
return (B * 8) - (i * 8) - (8 - 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
|
|
log.Debug("node.KBuckets[k] already full, performing ping to node.KBuckets[0]")
|
|
kad.PingOldNode(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)
|
|
log.Debug("ListedNode (" + o.ID.String() + ") already exists, moved to bottom")
|
|
return
|
|
}
|
|
// not exists, add it to the kBucket
|
|
kad.KBuckets[k] = append(kad.KBuckets[k], o)
|
|
log.Debug("ListedNode (" + o.ID.String() + ") not exists, added to the bottom")
|
|
return
|
|
}
|
|
|
|
func (kad *Kademlia) PingOldNode(k int, o ListedNode) {
|
|
// TODO
|
|
// ping the n.KBuckets[k][0] (using goroutine)
|
|
// if no response (timeout), delete it and add 'o'
|
|
// n.KBuckets[k][0] = o
|
|
}
|
|
|
|
func (kad *Kademlia) CallPing(o ListedNode) error {
|
|
client, err := rpc.DialHTTP("tcp", o.Addr+":"+o.Port)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ln := ListedNode{
|
|
ID: kad.N.ID,
|
|
Addr: kad.N.Addr,
|
|
Port: kad.N.Port,
|
|
}
|
|
var reply ListedNode
|
|
err = client.Call("Node.Ping", ln, &reply)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// TODO use reply as PONG
|
|
return nil
|
|
}
|
|
|
|
func (kad *Kademlia) CallPong(o ListedNode) error {
|
|
client, err := rpc.DialHTTP("tcp", o.Addr+":"+o.Port)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ln := ListedNode{
|
|
ID: kad.N.ID,
|
|
Addr: kad.N.Addr,
|
|
Port: kad.N.Port,
|
|
}
|
|
var reply bool
|
|
err = client.Call("Node.Pong", ln, &reply)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
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
|
|
}
|
|
func removeFromListedNodes(lns []ListedNode, k int) []ListedNode {
|
|
lns = append(lns[:k], lns[k+1:]...)
|
|
return lns
|
|
}
|
|
|
|
func (kad Kademlia) CallFindNode(id ID, o ListedNode) ([]ListedNode, error) {
|
|
client, err := rpc.DialHTTP("tcp", o.Addr+":"+o.Port)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var lns []ListedNode
|
|
err = client.Call("Node.FindNode", id, &lns)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return lns, nil
|
|
}
|
|
|
|
func (kad Kademlia) NodeLookup(id ID) ([]ListedNode, error) {
|
|
log.Debug("[NodeLookup] get closest KBucket for ", id)
|
|
// find closest kbucket for this current node
|
|
k, err := kad.GetClosestKBucket(id)
|
|
if err != nil {
|
|
return []ListedNode{}, fmt.Errorf("No KBuckets")
|
|
}
|
|
log.Debug("[NodeLookup] closest KBucket", k)
|
|
|
|
var closest ListedNode
|
|
var newClosest ListedNode
|
|
closest = kad.N
|
|
var lns []ListedNode
|
|
lns = kad.KBuckets[k]
|
|
for !closest.ID.Equal(newClosest.ID) { // TODO
|
|
// call each ListedNode from the kbucket, asking for their list of closest kbucket
|
|
var newLns []ListedNode
|
|
closest = newClosest
|
|
for k, ln := range lns {
|
|
// TODO TMP for the moment is synchronous
|
|
fmt.Println("calling node", ln)
|
|
flns, err := kad.CallFindNode(id, ln)
|
|
if err != nil {
|
|
log.Debug(err)
|
|
}
|
|
removeFromListedNodes(lns, k)
|
|
// newLns = append(newLns, flns...)
|
|
for _, fln := range flns {
|
|
exist, _ := existsInListedNodes(newLns, fln)
|
|
if !exist {
|
|
fmt.Println("ln", fln.ID, " not exists")
|
|
newLns = append(newLns, fln)
|
|
}
|
|
}
|
|
}
|
|
lns = newLns
|
|
newClosest = updateClosest(id, lns, closest)
|
|
}
|
|
return lns, nil
|
|
}
|
|
|
|
func updateClosest(id ID, lns []ListedNode, closest ListedNode) ListedNode {
|
|
d := id.Distance(closest.ID)
|
|
closestZ := countZeroes(d[:])
|
|
for _, ln := range lns {
|
|
d := id.Distance(ln.ID)
|
|
z := countZeroes(d[:])
|
|
if z < closestZ {
|
|
closest = ln
|
|
closestZ = z
|
|
}
|
|
}
|
|
return closest
|
|
}
|