You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

179 lines
3.6 KiB

  1. package kademlia
  2. import (
  3. "errors"
  4. "math/bits"
  5. "net/rpc"
  6. "strconv"
  7. log "github.com/sirupsen/logrus"
  8. )
  9. const (
  10. alpha = 3 // 'alpha', max parallalel calls
  11. B = 20 // 'B', 160 bits, ID length
  12. KBucketSize = 20 // 'K', bucket size
  13. )
  14. type ListedNode struct {
  15. ID ID
  16. Addr string
  17. Port string
  18. }
  19. type Kademlia struct {
  20. // N is this node data
  21. N ListedNode
  22. KBuckets [B][]ListedNode
  23. }
  24. func NewKademliaTable(id ID, addr, port string) *Kademlia {
  25. return &Kademlia{
  26. N: ListedNode{
  27. ID: id,
  28. Addr: addr,
  29. Port: port,
  30. },
  31. }
  32. }
  33. func (kad Kademlia) String() string {
  34. r := "Node ID: " + kad.N.ID.String() + ", KBuckets:\n"
  35. for i, kb := range kad.KBuckets {
  36. if len(kb) > 0 {
  37. r += " KBucket " + strconv.Itoa(i) + "\n"
  38. for _, e := range kb {
  39. r += " " + e.ID.String() + "\n"
  40. }
  41. }
  42. }
  43. return r
  44. }
  45. func (kad Kademlia) FindClosestKBucket(id ID) (int, error) {
  46. kb := kad.KBucket(id)
  47. if len(kad.KBuckets[kb]) != 0 {
  48. return kb, nil
  49. }
  50. for i := 0; kb-i > 0 || kb+i < 8; i++ {
  51. if len(kad.KBuckets[kb+i]) != 0 {
  52. return kb + i, nil
  53. }
  54. if len(kad.KBuckets[kb-i]) != 0 {
  55. return kb - i, nil
  56. }
  57. }
  58. return 0, errors.New("not found")
  59. }
  60. func (kad Kademlia) KBucket(o ID) int {
  61. d := kad.N.ID.Distance(o)
  62. return kBucketByDistance(d[:])
  63. }
  64. func kBucketByDistance(b []byte) int {
  65. z := countZeroes(b)
  66. if z == 0 {
  67. return 0
  68. }
  69. for i := 0; i < 8; i++ {
  70. if z>>uint8(i) == 1 { // check until 1 instead of 0 to avoid one operation
  71. return i
  72. }
  73. }
  74. return 7
  75. }
  76. func countZeroes(b []byte) int {
  77. for i := 0; i < B; i++ {
  78. for a := b[i] ^ 0; a != 0; a &= a - 1 {
  79. return (B * 8) - (i * 8) - (8 - bits.TrailingZeros8(bits.Reverse8(uint8(a))))
  80. }
  81. }
  82. return (B*8 - 1) - (B*8 - 1)
  83. }
  84. func (kad *Kademlia) Update(o ListedNode) {
  85. k := kad.KBucket(o.ID)
  86. kb := kad.KBuckets[k]
  87. if len(kb) >= KBucketSize {
  88. // if n.KBuckets[k] is alrady full, perform ping of the first element
  89. log.Debug("node.KBuckets[k] already full, performing ping to node.KBuckets[0]")
  90. kad.PingOldNode(k, o)
  91. return
  92. }
  93. // check that is not already in the list
  94. exist, pos := existsInListedNodes(kad.KBuckets[k], o)
  95. if exist {
  96. // update position of o to the bottom
  97. kad.KBuckets[k] = moveToBottom(kad.KBuckets[k], pos)
  98. log.Debug("ListedNode (" + o.ID.String() + ") already exists, moved to bottom")
  99. return
  100. }
  101. // not exists, add it to the kBucket
  102. kad.KBuckets[k] = append(kad.KBuckets[k], o)
  103. log.Debug("ListedNode (" + o.ID.String() + ") not exists, added to the bottom")
  104. return
  105. }
  106. func (kad *Kademlia) PingOldNode(k int, o ListedNode) {
  107. // TODO when rpc layer is done
  108. // ping the n.KBuckets[k][0] (using goroutine)
  109. // if no response (timeout), delete it and add 'o'
  110. // n.KBuckets[k][0] = o
  111. }
  112. func (kad *Kademlia) CallPing(o ListedNode) error {
  113. client, err := rpc.DialHTTP("tcp", o.Addr+":"+o.Port)
  114. if err != nil {
  115. return err
  116. }
  117. ln := ListedNode{
  118. ID: kad.N.ID,
  119. Addr: kad.N.Addr,
  120. Port: kad.N.Port,
  121. }
  122. var reply ListedNode
  123. err = client.Call("Node.Ping", ln, &reply)
  124. if err != nil {
  125. return err
  126. }
  127. // TODO use reply as PONG
  128. return nil
  129. }
  130. func (kad *Kademlia) CallPong(o ListedNode) error {
  131. client, err := rpc.DialHTTP("tcp", o.Addr+":"+o.Port)
  132. if err != nil {
  133. return err
  134. }
  135. ln := ListedNode{
  136. ID: kad.N.ID,
  137. Addr: kad.N.Addr,
  138. Port: kad.N.Port,
  139. }
  140. var reply bool
  141. err = client.Call("Node.Pong", ln, &reply)
  142. if err != nil {
  143. return err
  144. }
  145. return nil
  146. }
  147. func existsInListedNodes(lns []ListedNode, ln ListedNode) (bool, int) {
  148. for i, v := range lns {
  149. if v.ID.Equal(ln.ID) {
  150. return true, i
  151. }
  152. }
  153. return false, 0
  154. }
  155. func moveToBottom(kb []ListedNode, k int) []ListedNode {
  156. e := kb[k]
  157. kb = append(kb[:k], kb[k+1:]...)
  158. kb = append(kb[:], e)
  159. return kb
  160. }