added routing table saving, bitmap operations, lots of tests
This commit is contained in:
parent
b9ee0b0644
commit
14cceda81e
15 changed files with 3858 additions and 244 deletions
190
dht/bitmap.go
190
dht/bitmap.go
|
@ -1,8 +1,10 @@
|
||||||
package dht
|
package dht
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/lbryio/errors.go"
|
"github.com/lbryio/errors.go"
|
||||||
"github.com/lyoshenka/bencode"
|
"github.com/lyoshenka/bencode"
|
||||||
|
@ -14,6 +16,19 @@ func (b Bitmap) RawString() string {
|
||||||
return string(b[:])
|
return string(b[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BString returns the bitmap as a string of 0s and 1s
|
||||||
|
func (b Bitmap) BString() string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for i := 0; i < nodeIDBits; i++ {
|
||||||
|
if b.Get(i) {
|
||||||
|
buf.WriteString("1")
|
||||||
|
} else {
|
||||||
|
buf.WriteString("0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (b Bitmap) Hex() string {
|
func (b Bitmap) Hex() string {
|
||||||
return hex.EncodeToString(b[:])
|
return hex.EncodeToString(b[:])
|
||||||
}
|
}
|
||||||
|
@ -22,6 +37,14 @@ func (b Bitmap) HexShort() string {
|
||||||
return hex.EncodeToString(b[:4])
|
return hex.EncodeToString(b[:4])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b Bitmap) HexSimplified() string {
|
||||||
|
simple := strings.TrimLeft(b.Hex(), "0")
|
||||||
|
if simple == "" {
|
||||||
|
simple = "0"
|
||||||
|
}
|
||||||
|
return simple
|
||||||
|
}
|
||||||
|
|
||||||
func (b Bitmap) Equals(other Bitmap) bool {
|
func (b Bitmap) Equals(other Bitmap) bool {
|
||||||
for k := range b {
|
for k := range b {
|
||||||
if b[k] != other[k] {
|
if b[k] != other[k] {
|
||||||
|
@ -40,6 +63,35 @@ func (b Bitmap) Less(other interface{}) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b Bitmap) LessOrEqual(other interface{}) bool {
|
||||||
|
if bm, ok := other.(Bitmap); ok && b.Equals(bm) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return b.Less(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Bitmap) Greater(other interface{}) bool {
|
||||||
|
for k := range b {
|
||||||
|
if b[k] != other.(Bitmap)[k] {
|
||||||
|
return b[k] > other.(Bitmap)[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Bitmap) GreaterOrEqual(other interface{}) bool {
|
||||||
|
if bm, ok := other.(Bitmap); ok && b.Equals(bm) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return b.Greater(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Bitmap) Copy() Bitmap {
|
||||||
|
var ret Bitmap
|
||||||
|
copy(ret[:], b[:])
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
func (b Bitmap) Xor(other Bitmap) Bitmap {
|
func (b Bitmap) Xor(other Bitmap) Bitmap {
|
||||||
var ret Bitmap
|
var ret Bitmap
|
||||||
for k := range b {
|
for k := range b {
|
||||||
|
@ -48,6 +100,69 @@ func (b Bitmap) Xor(other Bitmap) Bitmap {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b Bitmap) And(other Bitmap) Bitmap {
|
||||||
|
var ret Bitmap
|
||||||
|
for k := range b {
|
||||||
|
ret[k] = b[k] & other[k]
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Bitmap) Or(other Bitmap) Bitmap {
|
||||||
|
var ret Bitmap
|
||||||
|
for k := range b {
|
||||||
|
ret[k] = b[k] | other[k]
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Bitmap) Not() Bitmap {
|
||||||
|
var ret Bitmap
|
||||||
|
for k := range b {
|
||||||
|
ret[k] = ^b[k]
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Bitmap) add(other Bitmap) (Bitmap, bool) {
|
||||||
|
var ret Bitmap
|
||||||
|
carry := false
|
||||||
|
for i := nodeIDBits - 1; i >= 0; i-- {
|
||||||
|
bBit := getBit(b[:], i)
|
||||||
|
oBit := getBit(other[:], i)
|
||||||
|
setBit(ret[:], i, bBit != oBit != carry)
|
||||||
|
carry = (bBit && oBit) || (bBit && carry) || (oBit && carry)
|
||||||
|
}
|
||||||
|
return ret, carry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Bitmap) Add(other Bitmap) Bitmap {
|
||||||
|
ret, carry := b.add(other)
|
||||||
|
if carry {
|
||||||
|
panic("overflow in bitmap addition")
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Bitmap) Sub(other Bitmap) Bitmap {
|
||||||
|
if b.Less(other) {
|
||||||
|
panic("negative bitmaps not supported")
|
||||||
|
}
|
||||||
|
complement, _ := other.Not().add(BitmapFromShortHexP("1"))
|
||||||
|
ret, _ := b.add(complement)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Bitmap) Get(n int) bool {
|
||||||
|
return getBit(b[:], n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Bitmap) Set(n int, one bool) Bitmap {
|
||||||
|
ret := b.Copy()
|
||||||
|
setBit(ret[:], n, one)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// PrefixLen returns the number of leading 0 bits
|
// PrefixLen returns the number of leading 0 bits
|
||||||
func (b Bitmap) PrefixLen() int {
|
func (b Bitmap) PrefixLen() int {
|
||||||
for i := range b {
|
for i := range b {
|
||||||
|
@ -57,20 +172,46 @@ func (b Bitmap) PrefixLen() int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return numBuckets
|
return nodeIDBits
|
||||||
}
|
}
|
||||||
|
|
||||||
// ZeroPrefix returns a copy of b with the first n bits set to 0
|
// Prefix returns a copy of b with the first n bits set to 1 (if `one` is true) or 0 (if `one` is false)
|
||||||
// https://stackoverflow.com/a/23192263/182709
|
// https://stackoverflow.com/a/23192263/182709
|
||||||
func (b Bitmap) ZeroPrefix(n int) Bitmap {
|
func (b Bitmap) Prefix(n int, one bool) Bitmap {
|
||||||
var ret Bitmap
|
ret := b.Copy()
|
||||||
copy(ret[:], b[:])
|
|
||||||
|
|
||||||
Outer:
|
Outer:
|
||||||
for i := range ret {
|
for i := range ret {
|
||||||
for j := 0; j < 8; j++ {
|
for j := 0; j < 8; j++ {
|
||||||
if i*8+j < n {
|
if i*8+j < n {
|
||||||
ret[i] &= ^(1 << uint(7-j))
|
if one {
|
||||||
|
ret[i] |= 1 << uint(7-j)
|
||||||
|
} else {
|
||||||
|
ret[i] &= ^(1 << uint(7-j))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break Outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syffix returns a copy of b with the last n bits set to 1 (if `one` is true) or 0 (if `one` is false)
|
||||||
|
// https://stackoverflow.com/a/23192263/182709
|
||||||
|
func (b Bitmap) Suffix(n int, one bool) Bitmap {
|
||||||
|
ret := b.Copy()
|
||||||
|
|
||||||
|
Outer:
|
||||||
|
for i := len(ret) - 1; i >= 0; i-- {
|
||||||
|
for j := 7; j >= 0; j-- {
|
||||||
|
if i*8+j >= nodeIDBits-n {
|
||||||
|
if one {
|
||||||
|
ret[i] |= 1 << uint(7-j)
|
||||||
|
} else {
|
||||||
|
ret[i] &= ^(1 << uint(7-j))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
break Outer
|
break Outer
|
||||||
}
|
}
|
||||||
|
@ -145,6 +286,18 @@ func BitmapFromHexP(hexStr string) Bitmap {
|
||||||
return bmp
|
return bmp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BitmapFromShortHex(hexStr string) (Bitmap, error) {
|
||||||
|
return BitmapFromHex(strings.Repeat("0", nodeIDLength*2-len(hexStr)) + hexStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BitmapFromShortHexP(hexStr string) Bitmap {
|
||||||
|
bmp, err := BitmapFromShortHex(hexStr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bmp
|
||||||
|
}
|
||||||
|
|
||||||
func RandomBitmapP() Bitmap {
|
func RandomBitmapP() Bitmap {
|
||||||
var id Bitmap
|
var id Bitmap
|
||||||
_, err := rand.Read(id[:])
|
_, err := rand.Read(id[:])
|
||||||
|
@ -153,3 +306,28 @@ func RandomBitmapP() Bitmap {
|
||||||
}
|
}
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RandomBitmapInRangeP(low, high Bitmap) Bitmap {
|
||||||
|
diff := high.Sub(low)
|
||||||
|
r := RandomBitmapP()
|
||||||
|
for r.Greater(diff) {
|
||||||
|
r = r.Sub(diff)
|
||||||
|
}
|
||||||
|
return r.Add(low)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBit(b []byte, n int) bool {
|
||||||
|
i := n / 8
|
||||||
|
j := n % 8
|
||||||
|
return b[i]&(1<<uint(7-j)) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func setBit(b []byte, n int, one bool) {
|
||||||
|
i := n / 8
|
||||||
|
j := n % 8
|
||||||
|
if one {
|
||||||
|
b[i] |= 1 << uint(7-j)
|
||||||
|
} else {
|
||||||
|
b[i] &= ^(1 << uint(7-j))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package dht
|
package dht
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/lyoshenka/bencode"
|
"github.com/lyoshenka/bencode"
|
||||||
|
@ -51,6 +52,84 @@ func TestBitmap(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBitmap_GetBit(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
hex string
|
||||||
|
bit int
|
||||||
|
expected bool
|
||||||
|
panic bool
|
||||||
|
}{
|
||||||
|
//{hex: "0", bit: 385, one: true, expected: "1", panic:true}, // should error
|
||||||
|
//{hex: "0", bit: 384, one: true, expected: "1", panic:true},
|
||||||
|
{bit: 383, expected: false, panic: false},
|
||||||
|
{bit: 382, expected: true, panic: false},
|
||||||
|
{bit: 381, expected: false, panic: false},
|
||||||
|
{bit: 380, expected: true, panic: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
b := BitmapFromShortHexP("a")
|
||||||
|
|
||||||
|
for _, test := range tt {
|
||||||
|
actual := getBit(b[:], test.bit)
|
||||||
|
if test.expected != actual {
|
||||||
|
t.Errorf("getting bit %d of %s: expected %t, got %t", test.bit, b.HexSimplified(), test.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitmap_SetBit(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
hex string
|
||||||
|
bit int
|
||||||
|
one bool
|
||||||
|
expected string
|
||||||
|
panic bool
|
||||||
|
}{
|
||||||
|
{hex: "0", bit: 383, one: true, expected: "1", panic: false},
|
||||||
|
{hex: "0", bit: 382, one: true, expected: "2", panic: false},
|
||||||
|
{hex: "0", bit: 381, one: true, expected: "4", panic: false},
|
||||||
|
{hex: "0", bit: 385, one: true, expected: "1", panic: true},
|
||||||
|
{hex: "0", bit: 384, one: true, expected: "1", panic: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tt {
|
||||||
|
expected := BitmapFromShortHexP(test.expected)
|
||||||
|
actual := BitmapFromShortHexP(test.hex)
|
||||||
|
if test.panic {
|
||||||
|
assertPanic(t, fmt.Sprintf("setting bit %d to %t", test.bit, test.one), func() { setBit(actual[:], test.bit, test.one) })
|
||||||
|
} else {
|
||||||
|
setBit(actual[:], test.bit, test.one)
|
||||||
|
if !expected.Equals(actual) {
|
||||||
|
t.Errorf("setting bit %d to %t: expected %s, got %s", test.bit, test.one, test.expected, actual.HexSimplified())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitmap_FromHexShort(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
short string
|
||||||
|
long string
|
||||||
|
}{
|
||||||
|
{short: "", long: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
|
||||||
|
{short: "0", long: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
|
||||||
|
{short: "00000", long: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
|
||||||
|
{short: "9473745bc", long: "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000009473745bc"},
|
||||||
|
{short: "09473745bc", long: "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000009473745bc"},
|
||||||
|
{short: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||||
|
long: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tt {
|
||||||
|
short := BitmapFromShortHexP(test.short)
|
||||||
|
long := BitmapFromHexP(test.long)
|
||||||
|
if !short.Equals(long) {
|
||||||
|
t.Errorf("short hex %s: expected %s, got %s", test.short, long.Hex(), short.Hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBitmapMarshal(t *testing.T) {
|
func TestBitmapMarshal(t *testing.T) {
|
||||||
b := BitmapFromStringP("123456789012345678901234567890123456789012345678")
|
b := BitmapFromStringP("123456789012345678901234567890123456789012345678")
|
||||||
encoded, err := bencode.EncodeBytes(b)
|
encoded, err := bencode.EncodeBytes(b)
|
||||||
|
@ -120,10 +199,10 @@ func TestBitmap_PrefixLen(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBitmap_ZeroPrefix(t *testing.T) {
|
func TestBitmap_Prefix(t *testing.T) {
|
||||||
original := BitmapFromHexP("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
allOne := BitmapFromHexP("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||||
|
|
||||||
tt := []struct {
|
zerosTT := []struct {
|
||||||
zeros int
|
zeros int
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
|
@ -136,18 +215,162 @@ func TestBitmap_ZeroPrefix(t *testing.T) {
|
||||||
{zeros: 400, expected: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
|
{zeros: 400, expected: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tt {
|
for _, test := range zerosTT {
|
||||||
expected := BitmapFromHexP(test.expected)
|
expected := BitmapFromHexP(test.expected)
|
||||||
actual := original.ZeroPrefix(test.zeros)
|
actual := allOne.Prefix(test.zeros, false)
|
||||||
if !actual.Equals(expected) {
|
if !actual.Equals(expected) {
|
||||||
t.Errorf("%d zeros: got %s; expected %s", test.zeros, actual.Hex(), expected.Hex())
|
t.Errorf("%d zeros: got %s; expected %s", test.zeros, actual.Hex(), expected.Hex())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < nodeIDLength*8; i++ {
|
for i := 0; i < nodeIDLength*8; i++ {
|
||||||
b := original.ZeroPrefix(i)
|
b := allOne.Prefix(i, false)
|
||||||
if b.PrefixLen() != i {
|
if b.PrefixLen() != i {
|
||||||
t.Errorf("got prefix len %d; expected %d for %s", b.PrefixLen(), i, b.Hex())
|
t.Errorf("got prefix len %d; expected %d for %s", b.PrefixLen(), i, b.Hex())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allZero := BitmapFromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||||
|
|
||||||
|
onesTT := []struct {
|
||||||
|
ones int
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{ones: -123, expected: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
|
||||||
|
{ones: 0, expected: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
|
||||||
|
{ones: 1, expected: "800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
|
||||||
|
{ones: 69, expected: "fffffffffffffffff8000000000000000000000000000000000000000000000000000000000000000000000000000000"},
|
||||||
|
{ones: 383, expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},
|
||||||
|
{ones: 384, expected: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
|
||||||
|
{ones: 400, expected: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range onesTT {
|
||||||
|
expected := BitmapFromHexP(test.expected)
|
||||||
|
actual := allZero.Prefix(test.ones, true)
|
||||||
|
if !actual.Equals(expected) {
|
||||||
|
t.Errorf("%d ones: got %s; expected %s", test.ones, actual.Hex(), expected.Hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitmap_Suffix(t *testing.T) {
|
||||||
|
allOne := BitmapFromHexP("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||||
|
|
||||||
|
zerosTT := []struct {
|
||||||
|
zeros int
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{zeros: -123, expected: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
|
||||||
|
{zeros: 0, expected: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
|
||||||
|
{zeros: 1, expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},
|
||||||
|
{zeros: 69, expected: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00000000000000000"},
|
||||||
|
{zeros: 383, expected: "800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
|
||||||
|
{zeros: 384, expected: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
|
||||||
|
{zeros: 400, expected: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range zerosTT {
|
||||||
|
expected := BitmapFromHexP(test.expected)
|
||||||
|
actual := allOne.Suffix(test.zeros, false)
|
||||||
|
if !actual.Equals(expected) {
|
||||||
|
t.Errorf("%d zeros: got %s; expected %s", test.zeros, actual.Hex(), expected.Hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < nodeIDLength*8; i++ {
|
||||||
|
b := allOne.Prefix(i, false)
|
||||||
|
if b.PrefixLen() != i {
|
||||||
|
t.Errorf("got prefix len %d; expected %d for %s", b.PrefixLen(), i, b.Hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allZero := BitmapFromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||||
|
|
||||||
|
onesTT := []struct {
|
||||||
|
ones int
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{ones: -123, expected: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
|
||||||
|
{ones: 0, expected: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
|
||||||
|
{ones: 1, expected: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"},
|
||||||
|
{ones: 69, expected: "0000000000000000000000000000000000000000000000000000000000000000000000000000001fffffffffffffffff"},
|
||||||
|
{ones: 383, expected: "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
|
||||||
|
{ones: 384, expected: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
|
||||||
|
{ones: 400, expected: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range onesTT {
|
||||||
|
expected := BitmapFromHexP(test.expected)
|
||||||
|
actual := allZero.Suffix(test.ones, true)
|
||||||
|
if !actual.Equals(expected) {
|
||||||
|
t.Errorf("%d ones: got %s; expected %s", test.ones, actual.Hex(), expected.Hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitmap_Add(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
a, b, sum string
|
||||||
|
panic bool
|
||||||
|
}{
|
||||||
|
{"0", "0", "0", false},
|
||||||
|
{"0", "1", "1", false},
|
||||||
|
{"1", "0", "1", false},
|
||||||
|
{"1", "1", "2", false},
|
||||||
|
{"8", "4", "c", false},
|
||||||
|
{"1000", "0010", "1010", false},
|
||||||
|
{"1111", "1111", "2222", false},
|
||||||
|
{"ffff", "1", "10000", false},
|
||||||
|
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", false},
|
||||||
|
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "1", "", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tt {
|
||||||
|
a := BitmapFromShortHexP(test.a)
|
||||||
|
b := BitmapFromShortHexP(test.b)
|
||||||
|
expected := BitmapFromShortHexP(test.sum)
|
||||||
|
if test.panic {
|
||||||
|
assertPanic(t, fmt.Sprintf("adding %s and %s", test.a, test.b), func() { a.Add(b) })
|
||||||
|
} else {
|
||||||
|
actual := a.Add(b)
|
||||||
|
if !expected.Equals(actual) {
|
||||||
|
t.Errorf("adding %s and %s; expected %s, got %s", test.a, test.b, test.sum, actual.HexSimplified())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitmap_Sub(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
a, b, sum string
|
||||||
|
panic bool
|
||||||
|
}{
|
||||||
|
{"0", "0", "0", false},
|
||||||
|
{"1", "0", "1", false},
|
||||||
|
{"1", "1", "0", false},
|
||||||
|
{"8", "4", "4", false},
|
||||||
|
{"f", "9", "6", false},
|
||||||
|
{"f", "e", "1", false},
|
||||||
|
{"10", "f", "1", false},
|
||||||
|
{"2222", "1111", "1111", false},
|
||||||
|
{"ffff", "1", "fffe", false},
|
||||||
|
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", false},
|
||||||
|
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0", false},
|
||||||
|
{"0", "1", "", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tt {
|
||||||
|
a := BitmapFromShortHexP(test.a)
|
||||||
|
b := BitmapFromShortHexP(test.b)
|
||||||
|
expected := BitmapFromShortHexP(test.sum)
|
||||||
|
if test.panic {
|
||||||
|
assertPanic(t, fmt.Sprintf("subtracting %s - %s", test.a, test.b), func() { a.Sub(b) })
|
||||||
|
} else {
|
||||||
|
actual := a.Sub(b)
|
||||||
|
if !expected.Equals(actual) {
|
||||||
|
t.Errorf("subtracting %s - %s; expected %s, got %s", test.a, test.b, test.sum, actual.HexSimplified())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package dht
|
package dht
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -14,25 +13,6 @@ const (
|
||||||
bootstrapDefaultRefreshDuration = 15 * time.Minute
|
bootstrapDefaultRefreshDuration = 15 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
type nullStore struct{}
|
|
||||||
|
|
||||||
func (n nullStore) Upsert(id Bitmap, c Contact) {}
|
|
||||||
func (n nullStore) Get(id Bitmap) []Contact { return nil }
|
|
||||||
func (n nullStore) CountStoredHashes() int { return 0 }
|
|
||||||
|
|
||||||
type nullRoutingTable struct{}
|
|
||||||
|
|
||||||
// TODO: the bootstrap logic *could* be implemented just in the routing table, without a custom request handler
|
|
||||||
// TODO: the only tricky part is triggering the ping when Fresh is called, as the rt doesnt have access to the node
|
|
||||||
|
|
||||||
func (n nullRoutingTable) Update(c Contact) {} // this
|
|
||||||
func (n nullRoutingTable) Fresh(c Contact) {} // this
|
|
||||||
func (n nullRoutingTable) Fail(c Contact) {} // this
|
|
||||||
func (n nullRoutingTable) GetClosest(id Bitmap, limit int) []Contact { return nil } // this
|
|
||||||
func (n nullRoutingTable) Count() int { return 0 }
|
|
||||||
func (n nullRoutingTable) GetIDsForRefresh(d time.Duration) []Bitmap { return nil }
|
|
||||||
func (n nullRoutingTable) BucketInfo() string { return "" }
|
|
||||||
|
|
||||||
type BootstrapNode struct {
|
type BootstrapNode struct {
|
||||||
Node
|
Node
|
||||||
|
|
||||||
|
@ -57,8 +37,6 @@ func NewBootstrapNode(id Bitmap, initialPingInterval, rePingInterval time.Durati
|
||||||
nodeKeys: make(map[Bitmap]int),
|
nodeKeys: make(map[Bitmap]int),
|
||||||
}
|
}
|
||||||
|
|
||||||
b.rt = &nullRoutingTable{}
|
|
||||||
b.store = &nullStore{}
|
|
||||||
b.requestHandler = b.handleRequest
|
b.requestHandler = b.handleRequest
|
||||||
|
|
||||||
return b
|
return b
|
||||||
|
@ -98,14 +76,14 @@ func (b *BootstrapNode) upsert(c Contact) {
|
||||||
b.nlock.Lock()
|
b.nlock.Lock()
|
||||||
defer b.nlock.Unlock()
|
defer b.nlock.Unlock()
|
||||||
|
|
||||||
if i, exists := b.nodeKeys[c.id]; exists {
|
if i, exists := b.nodeKeys[c.ID]; exists {
|
||||||
log.Debugf("[%s] bootstrap: touching contact %s", b.id.HexShort(), b.nodes[i].contact.id.HexShort())
|
log.Debugf("[%s] bootstrap: touching contact %s", b.id.HexShort(), b.nodes[i].Contact.ID.HexShort())
|
||||||
b.nodes[i].Touch()
|
b.nodes[i].Touch()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("[%s] bootstrap: adding new contact %s", b.id.HexShort(), c.id.HexShort())
|
log.Debugf("[%s] bootstrap: adding new contact %s", b.id.HexShort(), c.ID.HexShort())
|
||||||
b.nodeKeys[c.id] = len(b.nodes)
|
b.nodeKeys[c.ID] = len(b.nodes)
|
||||||
b.nodes = append(b.nodes, peer{c, time.Now(), 0})
|
b.nodes = append(b.nodes, peer{c, time.Now(), 0})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,14 +92,14 @@ func (b *BootstrapNode) remove(c Contact) {
|
||||||
b.nlock.Lock()
|
b.nlock.Lock()
|
||||||
defer b.nlock.Unlock()
|
defer b.nlock.Unlock()
|
||||||
|
|
||||||
i, exists := b.nodeKeys[c.id]
|
i, exists := b.nodeKeys[c.ID]
|
||||||
if !exists {
|
if !exists {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("[%s] bootstrap: removing contact %s", b.id.HexShort(), c.id.HexShort())
|
log.Debugf("[%s] bootstrap: removing contact %s", b.id.HexShort(), c.ID.HexShort())
|
||||||
b.nodes = append(b.nodes[:i], b.nodes[i+1:]...)
|
b.nodes = append(b.nodes[:i], b.nodes[i+1:]...)
|
||||||
delete(b.nodeKeys, c.id)
|
delete(b.nodeKeys, c.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get returns up to `limit` random contacts from the list
|
// get returns up to `limit` random contacts from the list
|
||||||
|
@ -135,7 +113,7 @@ func (b *BootstrapNode) get(limit int) []Contact {
|
||||||
|
|
||||||
ret := make([]Contact, limit)
|
ret := make([]Contact, limit)
|
||||||
for i, k := range randKeys(len(b.nodes))[:limit] {
|
for i, k := range randKeys(len(b.nodes))[:limit] {
|
||||||
ret[i] = b.nodes[k].contact
|
ret[i] = b.nodes[k].Contact
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
@ -146,8 +124,7 @@ func (b *BootstrapNode) ping(c Contact) {
|
||||||
b.stopWG.Add(1)
|
b.stopWG.Add(1)
|
||||||
defer b.stopWG.Done()
|
defer b.stopWG.Done()
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
resCh, cancel := b.SendCancelable(c, Request{Method: pingMethod})
|
||||||
resCh := b.SendAsync(ctx, c, Request{Method: pingMethod})
|
|
||||||
|
|
||||||
var res *Response
|
var res *Response
|
||||||
|
|
||||||
|
@ -171,7 +148,7 @@ func (b *BootstrapNode) check() {
|
||||||
|
|
||||||
for i := range b.nodes {
|
for i := range b.nodes {
|
||||||
if !b.nodes[i].ActiveInLast(b.checkInterval) {
|
if !b.nodes[i].ActiveInLast(b.checkInterval) {
|
||||||
go b.ping(b.nodes[i].contact)
|
go b.ping(b.nodes[i].Contact)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,7 +173,7 @@ func (b *BootstrapNode) handleRequest(addr *net.UDPAddr, request Request) {
|
||||||
go func() {
|
go func() {
|
||||||
log.Debugf("[%s] bootstrap: queuing %s to ping", b.id.HexShort(), request.NodeID.HexShort())
|
log.Debugf("[%s] bootstrap: queuing %s to ping", b.id.HexShort(), request.NodeID.HexShort())
|
||||||
<-time.After(b.initialPingInterval)
|
<-time.After(b.initialPingInterval)
|
||||||
b.ping(Contact{id: request.NodeID, ip: addr.IP, port: addr.Port})
|
b.ping(Contact{ID: request.NodeID, IP: addr.IP, Port: addr.Port})
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
54
dht/dht.go
54
dht/dht.go
|
@ -1,7 +1,6 @@
|
||||||
package dht
|
package dht
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -25,10 +24,11 @@ const (
|
||||||
|
|
||||||
// TODO: all these constants should be defaults, and should be used to set values in the standard Config. then the code should use values in the config
|
// TODO: all these constants should be defaults, and should be used to set values in the standard Config. then the code should use values in the config
|
||||||
// TODO: alternatively, have a global Config for constants. at least that way tests can modify the values
|
// TODO: alternatively, have a global Config for constants. at least that way tests can modify the values
|
||||||
alpha = 3 // this is the constant alpha in the spec
|
alpha = 3 // this is the constant alpha in the spec
|
||||||
bucketSize = 8 // this is the constant k in the spec
|
bucketSize = 8 // this is the constant k in the spec
|
||||||
nodeIDLength = 48 // bytes. this is the constant B in the spec
|
nodeIDLength = 48 // bytes. this is the constant B in the spec
|
||||||
messageIDLength = 20 // bytes.
|
nodeIDBits = nodeIDLength * 8 // number of bits in node ID
|
||||||
|
messageIDLength = 20 // bytes.
|
||||||
|
|
||||||
udpRetry = 3
|
udpRetry = 3
|
||||||
udpTimeout = 5 * time.Second
|
udpTimeout = 5 * time.Second
|
||||||
|
@ -42,7 +42,6 @@ const (
|
||||||
tRepublish = 24 * time.Hour // the time after which the original publisher must republish a key/value pair
|
tRepublish = 24 * time.Hour // the time after which the original publisher must republish a key/value pair
|
||||||
tNodeRefresh = 15 * time.Minute // the time after which a good node becomes questionable if it has not messaged us
|
tNodeRefresh = 15 * time.Minute // the time after which a good node becomes questionable if it has not messaged us
|
||||||
|
|
||||||
numBuckets = nodeIDLength * 8
|
|
||||||
compactNodeInfoLength = nodeIDLength + 6 // nodeID + 4 for IP + 2 for port
|
compactNodeInfoLength = nodeIDLength + 6 // nodeID + 4 for IP + 2 for port
|
||||||
|
|
||||||
tokenSecretRotationInterval = 5 * time.Minute // how often the token-generating secret is rotated
|
tokenSecretRotationInterval = 5 * time.Minute // how often the token-generating secret is rotated
|
||||||
|
@ -102,7 +101,7 @@ func New(config *Config) (*DHT, error) {
|
||||||
d := &DHT{
|
d := &DHT{
|
||||||
conf: config,
|
conf: config,
|
||||||
contact: contact,
|
contact: contact,
|
||||||
node: NewNode(contact.id),
|
node: NewNode(contact.ID),
|
||||||
stop: stopOnce.New(),
|
stop: stopOnce.New(),
|
||||||
stopWG: &sync.WaitGroup{},
|
stopWG: &sync.WaitGroup{},
|
||||||
joined: make(chan struct{}),
|
joined: make(chan struct{}),
|
||||||
|
@ -181,7 +180,7 @@ func (dht *DHT) Ping(addr string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpNode := Contact{id: RandomBitmapP(), ip: raddr.IP, port: raddr.Port}
|
tmpNode := Contact{ID: RandomBitmapP(), IP: raddr.IP, Port: raddr.Port}
|
||||||
res := dht.node.Send(tmpNode, Request{Method: pingMethod})
|
res := dht.node.Send(tmpNode, Request{Method: pingMethod})
|
||||||
if res == nil {
|
if res == nil {
|
||||||
return errors.Err("no response from node %s", addr)
|
return errors.Err("no response from node %s", addr)
|
||||||
|
@ -214,19 +213,25 @@ func (dht *DHT) Announce(hash Bitmap) error {
|
||||||
|
|
||||||
// TODO: if this node is closer than farthest peer, store locally and pop farthest peer
|
// TODO: if this node is closer than farthest peer, store locally and pop farthest peer
|
||||||
|
|
||||||
for _, node := range res.Contacts {
|
wg := &sync.WaitGroup{}
|
||||||
go dht.storeOnNode(hash, node)
|
for _, c := range res.Contacts {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(c Contact) {
|
||||||
|
dht.storeOnNode(hash, c)
|
||||||
|
wg.Done()
|
||||||
|
}(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dht *DHT) storeOnNode(hash Bitmap, node Contact) {
|
func (dht *DHT) storeOnNode(hash Bitmap, c Contact) {
|
||||||
dht.stopWG.Add(1)
|
dht.stopWG.Add(1)
|
||||||
defer dht.stopWG.Done()
|
defer dht.stopWG.Done()
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
resCh, cancel := dht.node.SendCancelable(c, Request{
|
||||||
resCh := dht.node.SendAsync(ctx, node, Request{
|
|
||||||
Method: findValueMethod,
|
Method: findValueMethod,
|
||||||
Arg: &hash,
|
Arg: &hash,
|
||||||
})
|
})
|
||||||
|
@ -244,15 +249,14 @@ func (dht *DHT) storeOnNode(hash Bitmap, node Contact) {
|
||||||
return // request timed out
|
return // request timed out
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel = context.WithCancel(context.Background())
|
resCh, cancel = dht.node.SendCancelable(c, Request{
|
||||||
resCh = dht.node.SendAsync(ctx, node, Request{
|
|
||||||
Method: storeMethod,
|
Method: storeMethod,
|
||||||
StoreArgs: &storeArgs{
|
StoreArgs: &storeArgs{
|
||||||
BlobHash: hash,
|
BlobHash: hash,
|
||||||
Value: storeArgsValue{
|
Value: storeArgsValue{
|
||||||
Token: res.Token,
|
Token: res.Token,
|
||||||
LbryID: dht.contact.id,
|
LbryID: dht.contact.ID,
|
||||||
Port: dht.contact.port,
|
Port: dht.contact.Port,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -276,18 +280,12 @@ func (dht *DHT) PrintState() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func printNodeList(list []Contact) {
|
|
||||||
for i, n := range list {
|
|
||||||
log.Printf("%d) %s", i, n.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getContact(nodeID, addr string) (Contact, error) {
|
func getContact(nodeID, addr string) (Contact, error) {
|
||||||
var c Contact
|
var c Contact
|
||||||
if nodeID == "" {
|
if nodeID == "" {
|
||||||
c.id = RandomBitmapP()
|
c.ID = RandomBitmapP()
|
||||||
} else {
|
} else {
|
||||||
c.id = BitmapFromHexP(nodeID)
|
c.ID = BitmapFromHexP(nodeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, port, err := net.SplitHostPort(addr)
|
ip, port, err := net.SplitHostPort(addr)
|
||||||
|
@ -299,12 +297,12 @@ func getContact(nodeID, addr string) (Contact, error) {
|
||||||
return c, errors.Err("address does not contain a port")
|
return c, errors.Err("address does not contain a port")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ip = net.ParseIP(ip)
|
c.IP = net.ParseIP(ip)
|
||||||
if c.ip == nil {
|
if c.IP == nil {
|
||||||
return c, errors.Err("invalid ip")
|
return c, errors.Err("invalid ip")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.port, err = cast.ToIntE(port)
|
c.Port, err = cast.ToIntE(port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c, errors.Err(err)
|
return c, errors.Err(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,13 +36,13 @@ func TestNodeFinder_FindNodes(t *testing.T) {
|
||||||
foundTwo := false
|
foundTwo := false
|
||||||
|
|
||||||
for _, n := range foundNodes {
|
for _, n := range foundNodes {
|
||||||
if n.id.Equals(bs.id) {
|
if n.ID.Equals(bs.id) {
|
||||||
foundBootstrap = true
|
foundBootstrap = true
|
||||||
}
|
}
|
||||||
if n.id.Equals(dhts[0].node.id) {
|
if n.ID.Equals(dhts[0].node.id) {
|
||||||
foundOne = true
|
foundOne = true
|
||||||
}
|
}
|
||||||
if n.id.Equals(dhts[1].node.id) {
|
if n.ID.Equals(dhts[1].node.id) {
|
||||||
foundTwo = true
|
foundTwo = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ func TestNodeFinder_FindValue(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
blobHashToFind := RandomBitmapP()
|
blobHashToFind := RandomBitmapP()
|
||||||
nodeToFind := Contact{id: RandomBitmapP(), ip: net.IPv4(1, 2, 3, 4), port: 5678}
|
nodeToFind := Contact{ID: RandomBitmapP(), IP: net.IPv4(1, 2, 3, 4), Port: 5678}
|
||||||
dhts[0].node.store.Upsert(blobHashToFind, nodeToFind)
|
dhts[0].node.store.Upsert(blobHashToFind, nodeToFind)
|
||||||
|
|
||||||
nf := newContactFinder(dhts[2].node, blobHashToFind, true)
|
nf := newContactFinder(dhts[2].node, blobHashToFind, true)
|
||||||
|
@ -101,8 +101,8 @@ func TestNodeFinder_FindValue(t *testing.T) {
|
||||||
t.Fatalf("expected one node, found %d", len(foundNodes))
|
t.Fatalf("expected one node, found %d", len(foundNodes))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !foundNodes[0].id.Equals(nodeToFind.id) {
|
if !foundNodes[0].ID.Equals(nodeToFind.ID) {
|
||||||
t.Fatalf("found node id %s, expected %s", foundNodes[0].id.Hex(), nodeToFind.id.Hex())
|
t.Fatalf("found node id %s, expected %s", foundNodes[0].ID.Hex(), nodeToFind.ID.Hex())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ func TestDHT_LargeDHT(t *testing.T) {
|
||||||
c := d2.node.rt.GetClosest(d.node.id, 1)
|
c := d2.node.rt.GetClosest(d.node.id, 1)
|
||||||
if len(c) > 1 {
|
if len(c) > 1 {
|
||||||
t.Error("rt returned more than one node when only one requested")
|
t.Error("rt returned more than one node when only one requested")
|
||||||
} else if len(c) == 1 && c[0].id.Equals(d.node.id) {
|
} else if len(c) == 1 && c[0].ID.Equals(d.node.id) {
|
||||||
rtCounts[d.node.id]++
|
rtCounts[d.node.id]++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
3060
dht/fixtures/TestRoutingTable_Save.golden
Normal file
3060
dht/fixtures/TestRoutingTable_Save.golden
Normal file
File diff suppressed because it is too large
Load diff
|
@ -240,7 +240,7 @@ func (r Response) ArgsDebug() string {
|
||||||
|
|
||||||
str += "|"
|
str += "|"
|
||||||
for _, c := range r.Contacts {
|
for _, c := range r.Contacts {
|
||||||
str += c.Addr().String() + ":" + c.id.HexShort() + ","
|
str += c.Addr().String() + ":" + c.ID.HexShort() + ","
|
||||||
}
|
}
|
||||||
str = strings.TrimRight(str, ",") + "|"
|
str = strings.TrimRight(str, ",") + "|"
|
||||||
|
|
||||||
|
|
|
@ -78,8 +78,8 @@ func TestBencodeFindNodesResponse(t *testing.T) {
|
||||||
ID: newMessageID(),
|
ID: newMessageID(),
|
||||||
NodeID: RandomBitmapP(),
|
NodeID: RandomBitmapP(),
|
||||||
Contacts: []Contact{
|
Contacts: []Contact{
|
||||||
{id: RandomBitmapP(), ip: net.IPv4(1, 2, 3, 4).To4(), port: 5678},
|
{ID: RandomBitmapP(), IP: net.IPv4(1, 2, 3, 4).To4(), Port: 5678},
|
||||||
{id: RandomBitmapP(), ip: net.IPv4(4, 3, 2, 1).To4(), port: 8765},
|
{ID: RandomBitmapP(), IP: net.IPv4(4, 3, 2, 1).To4(), Port: 8765},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ func TestBencodeFindValueResponse(t *testing.T) {
|
||||||
FindValueKey: RandomBitmapP().RawString(),
|
FindValueKey: RandomBitmapP().RawString(),
|
||||||
Token: "arst",
|
Token: "arst",
|
||||||
Contacts: []Contact{
|
Contacts: []Contact{
|
||||||
{id: RandomBitmapP(), ip: net.IPv4(1, 2, 3, 4).To4(), port: 5678},
|
{ID: RandomBitmapP(), IP: net.IPv4(1, 2, 3, 4).To4(), Port: 5678},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
dht/node.go
20
dht/node.go
|
@ -48,9 +48,9 @@ type Node struct {
|
||||||
transactions map[messageID]*transaction
|
transactions map[messageID]*transaction
|
||||||
|
|
||||||
// routing table
|
// routing table
|
||||||
rt RoutingTable
|
rt *routingTable
|
||||||
// data store
|
// data store
|
||||||
store Store
|
store *contactStore
|
||||||
|
|
||||||
// overrides for request handlers
|
// overrides for request handlers
|
||||||
requestHandler RequestHandlerFunc
|
requestHandler RequestHandlerFunc
|
||||||
|
@ -238,7 +238,7 @@ func (n *Node) handleRequest(addr *net.UDPAddr, request Request) {
|
||||||
// TODO: we should be sending the IP in the request, not just using the sender's IP
|
// TODO: we should be sending the IP in the request, not just using the sender's IP
|
||||||
// TODO: should we be using StoreArgs.NodeID or StoreArgs.Value.LbryID ???
|
// TODO: should we be using StoreArgs.NodeID or StoreArgs.Value.LbryID ???
|
||||||
if n.tokens.Verify(request.StoreArgs.Value.Token, request.NodeID, addr) {
|
if n.tokens.Verify(request.StoreArgs.Value.Token, request.NodeID, addr) {
|
||||||
n.store.Upsert(request.StoreArgs.BlobHash, Contact{id: request.StoreArgs.NodeID, ip: addr.IP, port: request.StoreArgs.Value.Port})
|
n.store.Upsert(request.StoreArgs.BlobHash, Contact{ID: request.StoreArgs.NodeID, IP: addr.IP, Port: request.StoreArgs.Value.Port})
|
||||||
n.sendMessage(addr, Response{ID: request.ID, NodeID: n.id, Data: storeSuccessResponse})
|
n.sendMessage(addr, Response{ID: request.ID, NodeID: n.id, Data: storeSuccessResponse})
|
||||||
} else {
|
} else {
|
||||||
n.sendMessage(addr, Error{ID: request.ID, NodeID: n.id, ExceptionType: "invalid-token"})
|
n.sendMessage(addr, Error{ID: request.ID, NodeID: n.id, ExceptionType: "invalid-token"})
|
||||||
|
@ -280,7 +280,7 @@ func (n *Node) handleRequest(addr *net.UDPAddr, request Request) {
|
||||||
// the routing table must only contain "good" nodes, which are nodes that reply to our requests
|
// the routing table must only contain "good" nodes, which are nodes that reply to our requests
|
||||||
// if a node is already good (aka in the table), its fine to refresh it
|
// if a node is already good (aka in the table), its fine to refresh it
|
||||||
// http://www.bittorrent.org/beps/bep_0005.html#routing-table
|
// http://www.bittorrent.org/beps/bep_0005.html#routing-table
|
||||||
n.rt.Fresh(Contact{id: request.NodeID, ip: addr.IP, port: addr.Port})
|
n.rt.Fresh(Contact{ID: request.NodeID, IP: addr.IP, Port: addr.Port})
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleResponse handles responses received from udp.
|
// handleResponse handles responses received from udp.
|
||||||
|
@ -290,13 +290,13 @@ func (n *Node) handleResponse(addr *net.UDPAddr, response Response) {
|
||||||
tx.res <- response
|
tx.res <- response
|
||||||
}
|
}
|
||||||
|
|
||||||
n.rt.Update(Contact{id: response.NodeID, ip: addr.IP, port: addr.Port})
|
n.rt.Update(Contact{ID: response.NodeID, IP: addr.IP, Port: addr.Port})
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleError handles errors received from udp.
|
// handleError handles errors received from udp.
|
||||||
func (n *Node) handleError(addr *net.UDPAddr, e Error) {
|
func (n *Node) handleError(addr *net.UDPAddr, e Error) {
|
||||||
spew.Dump(e)
|
spew.Dump(e)
|
||||||
n.rt.Fresh(Contact{id: e.NodeID, ip: addr.IP, port: addr.Port})
|
n.rt.Fresh(Contact{ID: e.NodeID, IP: addr.IP, Port: addr.Port})
|
||||||
}
|
}
|
||||||
|
|
||||||
// send sends data to a udp address
|
// send sends data to a udp address
|
||||||
|
@ -361,7 +361,7 @@ func (n *Node) txFind(id messageID, addr *net.UDPAddr) *transaction {
|
||||||
// SendAsync sends a transaction and returns a channel that will eventually contain the transaction response
|
// SendAsync sends a transaction and returns a channel that will eventually contain the transaction response
|
||||||
// The response channel is closed when the transaction is completed or times out.
|
// The response channel is closed when the transaction is completed or times out.
|
||||||
func (n *Node) SendAsync(ctx context.Context, contact Contact, req Request) <-chan *Response {
|
func (n *Node) SendAsync(ctx context.Context, contact Contact, req Request) <-chan *Response {
|
||||||
if contact.id.Equals(n.id) {
|
if contact.ID.Equals(n.id) {
|
||||||
log.Error("sending query to self")
|
log.Error("sending query to self")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -413,6 +413,12 @@ func (n *Node) Send(contact Contact, req Request) *Response {
|
||||||
return <-n.SendAsync(context.Background(), contact, req)
|
return <-n.SendAsync(context.Background(), contact, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendCancelable sends the transaction asynchronously and allows the transaction to be canceled
|
||||||
|
func (n *Node) SendCancelable(contact Contact, req Request) (<-chan *Response, context.CancelFunc) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
return n.SendAsync(ctx, contact, req), cancel
|
||||||
|
}
|
||||||
|
|
||||||
// Count returns the number of transactions in the manager
|
// Count returns the number of transactions in the manager
|
||||||
func (n *Node) CountActiveTransactions() int {
|
func (n *Node) CountActiveTransactions() int {
|
||||||
n.txLock.Lock()
|
n.txLock.Lock()
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package dht
|
package dht
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -113,7 +112,7 @@ func (cf *contactFinder) iterationWorker(num int) {
|
||||||
} else {
|
} else {
|
||||||
contact := *maybeContact
|
contact := *maybeContact
|
||||||
|
|
||||||
if contact.id.Equals(cf.node.id) {
|
if contact.ID.Equals(cf.node.id) {
|
||||||
continue // cannot contact self
|
continue // cannot contact self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,13 +123,12 @@ func (cf *contactFinder) iterationWorker(num int) {
|
||||||
req.Method = findNodeMethod
|
req.Method = findNodeMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("[%s] worker %d: contacting %s", cf.node.id.HexShort(), num, contact.id.HexShort())
|
log.Debugf("[%s] worker %d: contacting %s", cf.node.id.HexShort(), num, contact.ID.HexShort())
|
||||||
|
|
||||||
cf.incrementOutstanding()
|
cf.incrementOutstanding()
|
||||||
|
|
||||||
var res *Response
|
var res *Response
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
resCh, cancel := cf.node.SendCancelable(contact, req)
|
||||||
resCh := cf.node.SendAsync(ctx, contact, req)
|
|
||||||
select {
|
select {
|
||||||
case res = <-resCh:
|
case res = <-resCh:
|
||||||
case <-cf.done.Chan():
|
case <-cf.done.Chan():
|
||||||
|
@ -141,7 +139,7 @@ func (cf *contactFinder) iterationWorker(num int) {
|
||||||
|
|
||||||
if res == nil {
|
if res == nil {
|
||||||
// nothing to do, response timed out
|
// nothing to do, response timed out
|
||||||
log.Debugf("[%s] worker %d: search canceled or timed out waiting for %s", cf.node.id.HexShort(), num, contact.id.HexShort())
|
log.Debugf("[%s] worker %d: search canceled or timed out waiting for %s", cf.node.id.HexShort(), num, contact.ID.HexShort())
|
||||||
} else if cf.findValue && res.FindValueKey != "" {
|
} else if cf.findValue && res.FindValueKey != "" {
|
||||||
log.Debugf("[%s] worker %d: got value", cf.node.id.HexShort(), num)
|
log.Debugf("[%s] worker %d: got value", cf.node.id.HexShort(), num)
|
||||||
cf.findValueMutex.Lock()
|
cf.findValueMutex.Lock()
|
||||||
|
@ -171,9 +169,9 @@ func (cf *contactFinder) appendNewToShortlist(contacts []Contact) {
|
||||||
defer cf.shortlistMutex.Unlock()
|
defer cf.shortlistMutex.Unlock()
|
||||||
|
|
||||||
for _, c := range contacts {
|
for _, c := range contacts {
|
||||||
if _, ok := cf.shortlistAdded[c.id]; !ok {
|
if _, ok := cf.shortlistAdded[c.ID]; !ok {
|
||||||
cf.shortlist = append(cf.shortlist, c)
|
cf.shortlist = append(cf.shortlist, c)
|
||||||
cf.shortlistAdded[c.id] = true
|
cf.shortlistAdded[c.ID] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +197,7 @@ func (cf *contactFinder) insertIntoActiveList(contact Contact) {
|
||||||
|
|
||||||
inserted := false
|
inserted := false
|
||||||
for i, n := range cf.activeContacts {
|
for i, n := range cf.activeContacts {
|
||||||
if contact.id.Xor(cf.target).Less(n.id.Xor(cf.target)) {
|
if contact.ID.Xor(cf.target).Less(n.ID.Xor(cf.target)) {
|
||||||
cf.activeContacts = append(cf.activeContacts[:i], append([]Contact{contact}, cf.activeContacts[i:]...)...)
|
cf.activeContacts = append(cf.activeContacts[:i], append([]Contact{contact}, cf.activeContacts[i:]...)...)
|
||||||
inserted = true
|
inserted = true
|
||||||
break
|
break
|
||||||
|
@ -232,7 +230,7 @@ func (cf *contactFinder) isSearchFinished() bool {
|
||||||
cf.activeContactsMutex.Lock()
|
cf.activeContactsMutex.Lock()
|
||||||
defer cf.activeContactsMutex.Unlock()
|
defer cf.activeContactsMutex.Unlock()
|
||||||
|
|
||||||
if len(cf.activeContacts) >= bucketSize && cf.activeContacts[bucketSize-1].id.Xor(cf.target).Less(cf.shortlist[0].id.Xor(cf.target)) {
|
if len(cf.activeContacts) >= bucketSize && cf.activeContacts[bucketSize-1].ID.Xor(cf.target).Less(cf.shortlist[0].ID.Xor(cf.target)) {
|
||||||
// we have at least K active contacts, and we don't have any closer contacts to ping
|
// we have at least K active contacts, and we don't have any closer contacts to ping
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -263,7 +261,7 @@ func sortInPlace(contacts []Contact, target Bitmap) {
|
||||||
toSort := make([]sortedContact, len(contacts))
|
toSort := make([]sortedContact, len(contacts))
|
||||||
|
|
||||||
for i, n := range contacts {
|
for i, n := range contacts {
|
||||||
toSort[i] = sortedContact{n, n.id.Xor(target)}
|
toSort[i] = sortedContact{n, n.ID.Xor(target)}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(byXorDistance(toSort))
|
sort.Sort(byXorDistance(toSort))
|
||||||
|
|
|
@ -198,7 +198,7 @@ func TestStore(t *testing.T) {
|
||||||
if len(items) != 1 {
|
if len(items) != 1 {
|
||||||
t.Error("list created in store, but nothing in list")
|
t.Error("list created in store, but nothing in list")
|
||||||
}
|
}
|
||||||
if !items[0].id.Equals(testNodeID) {
|
if !items[0].ID.Equals(testNodeID) {
|
||||||
t.Error("wrong value stored")
|
t.Error("wrong value stored")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,7 +223,7 @@ func TestFindNode(t *testing.T) {
|
||||||
nodesToInsert := 3
|
nodesToInsert := 3
|
||||||
var nodes []Contact
|
var nodes []Contact
|
||||||
for i := 0; i < nodesToInsert; i++ {
|
for i := 0; i < nodesToInsert; i++ {
|
||||||
n := Contact{id: RandomBitmapP(), ip: net.ParseIP("127.0.0.1"), port: 10000 + i}
|
n := Contact{ID: RandomBitmapP(), IP: net.ParseIP("127.0.0.1"), Port: 10000 + i}
|
||||||
nodes = append(nodes, n)
|
nodes = append(nodes, n)
|
||||||
dht.node.rt.Update(n)
|
dht.node.rt.Update(n)
|
||||||
}
|
}
|
||||||
|
@ -292,7 +292,7 @@ func TestFindValueExisting(t *testing.T) {
|
||||||
nodesToInsert := 3
|
nodesToInsert := 3
|
||||||
var nodes []Contact
|
var nodes []Contact
|
||||||
for i := 0; i < nodesToInsert; i++ {
|
for i := 0; i < nodesToInsert; i++ {
|
||||||
n := Contact{id: RandomBitmapP(), ip: net.ParseIP("127.0.0.1"), port: 10000 + i}
|
n := Contact{ID: RandomBitmapP(), IP: net.ParseIP("127.0.0.1"), Port: 10000 + i}
|
||||||
nodes = append(nodes, n)
|
nodes = append(nodes, n)
|
||||||
dht.node.rt.Update(n)
|
dht.node.rt.Update(n)
|
||||||
}
|
}
|
||||||
|
@ -302,7 +302,7 @@ func TestFindValueExisting(t *testing.T) {
|
||||||
messageID := newMessageID()
|
messageID := newMessageID()
|
||||||
valueToFind := RandomBitmapP()
|
valueToFind := RandomBitmapP()
|
||||||
|
|
||||||
nodeToFind := Contact{id: RandomBitmapP(), ip: net.ParseIP("1.2.3.4"), port: 1286}
|
nodeToFind := Contact{ID: RandomBitmapP(), IP: net.ParseIP("1.2.3.4"), Port: 1286}
|
||||||
dht.node.store.Upsert(valueToFind, nodeToFind)
|
dht.node.store.Upsert(valueToFind, nodeToFind)
|
||||||
dht.node.store.Upsert(valueToFind, nodeToFind)
|
dht.node.store.Upsert(valueToFind, nodeToFind)
|
||||||
dht.node.store.Upsert(valueToFind, nodeToFind)
|
dht.node.store.Upsert(valueToFind, nodeToFind)
|
||||||
|
@ -378,7 +378,7 @@ func TestFindValueFallbackToFindNode(t *testing.T) {
|
||||||
nodesToInsert := 3
|
nodesToInsert := 3
|
||||||
var nodes []Contact
|
var nodes []Contact
|
||||||
for i := 0; i < nodesToInsert; i++ {
|
for i := 0; i < nodesToInsert; i++ {
|
||||||
n := Contact{id: RandomBitmapP(), ip: net.ParseIP("127.0.0.1"), port: 10000 + i}
|
n := Contact{ID: RandomBitmapP(), IP: net.ParseIP("127.0.0.1"), Port: 10000 + i}
|
||||||
nodes = append(nodes, n)
|
nodes = append(nodes, n)
|
||||||
dht.node.rt.Update(n)
|
dht.node.rt.Update(n)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@ package dht
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"container/list"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -16,32 +17,32 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Contact struct {
|
type Contact struct {
|
||||||
id Bitmap
|
ID Bitmap
|
||||||
ip net.IP
|
IP net.IP
|
||||||
port int
|
Port int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Contact) Addr() *net.UDPAddr {
|
func (c Contact) Addr() *net.UDPAddr {
|
||||||
return &net.UDPAddr{IP: c.ip, Port: c.port}
|
return &net.UDPAddr{IP: c.IP, Port: c.Port}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Contact) String() string {
|
func (c Contact) String() string {
|
||||||
return c.id.HexShort() + "@" + c.Addr().String()
|
return c.ID.HexShort() + "@" + c.Addr().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Contact) MarshalCompact() ([]byte, error) {
|
func (c Contact) MarshalCompact() ([]byte, error) {
|
||||||
if c.ip.To4() == nil {
|
if c.IP.To4() == nil {
|
||||||
return nil, errors.Err("ip not set")
|
return nil, errors.Err("ip not set")
|
||||||
}
|
}
|
||||||
if c.port < 0 || c.port > 65535 {
|
if c.Port < 0 || c.Port > 65535 {
|
||||||
return nil, errors.Err("invalid port")
|
return nil, errors.Err("invalid port")
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
buf.Write(c.ip.To4())
|
buf.Write(c.IP.To4())
|
||||||
buf.WriteByte(byte(c.port >> 8))
|
buf.WriteByte(byte(c.Port >> 8))
|
||||||
buf.WriteByte(byte(c.port))
|
buf.WriteByte(byte(c.Port))
|
||||||
buf.Write(c.id[:])
|
buf.Write(c.ID[:])
|
||||||
|
|
||||||
if buf.Len() != compactNodeInfoLength {
|
if buf.Len() != compactNodeInfoLength {
|
||||||
return nil, errors.Err("i dont know how this happened")
|
return nil, errors.Err("i dont know how this happened")
|
||||||
|
@ -54,14 +55,14 @@ func (c *Contact) UnmarshalCompact(b []byte) error {
|
||||||
if len(b) != compactNodeInfoLength {
|
if len(b) != compactNodeInfoLength {
|
||||||
return errors.Err("invalid compact length")
|
return errors.Err("invalid compact length")
|
||||||
}
|
}
|
||||||
c.ip = net.IPv4(b[0], b[1], b[2], b[3]).To4()
|
c.IP = net.IPv4(b[0], b[1], b[2], b[3]).To4()
|
||||||
c.port = int(uint16(b[5]) | uint16(b[4])<<8)
|
c.Port = int(uint16(b[5]) | uint16(b[4])<<8)
|
||||||
c.id = BitmapFromBytesP(b[6:])
|
c.ID = BitmapFromBytesP(b[6:])
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Contact) MarshalBencode() ([]byte, error) {
|
func (c Contact) MarshalBencode() ([]byte, error) {
|
||||||
return bencode.EncodeBytes([]interface{}{c.id, c.ip.String(), c.port})
|
return bencode.EncodeBytes([]interface{}{c.ID, c.IP.String(), c.Port})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Contact) UnmarshalBencode(b []byte) error {
|
func (c *Contact) UnmarshalBencode(b []byte) error {
|
||||||
|
@ -75,7 +76,7 @@ func (c *Contact) UnmarshalBencode(b []byte) error {
|
||||||
return errors.Err("contact must have 3 elements; got %d", len(raw))
|
return errors.Err("contact must have 3 elements; got %d", len(raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = bencode.DecodeBytes(raw[0], &c.id)
|
err = bencode.DecodeBytes(raw[0], &c.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -85,12 +86,12 @@ func (c *Contact) UnmarshalBencode(b []byte) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.ip = net.ParseIP(ipStr).To4()
|
c.IP = net.ParseIP(ipStr).To4()
|
||||||
if c.ip == nil {
|
if c.IP == nil {
|
||||||
return errors.Err("invalid IP")
|
return errors.Err("invalid IP")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = bencode.DecodeBytes(raw[2], &c.port)
|
err = bencode.DecodeBytes(raw[2], &c.Port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -113,52 +114,38 @@ func (a byXorDistance) Less(i, j int) bool {
|
||||||
|
|
||||||
// peer is a contact with extra freshness information
|
// peer is a contact with extra freshness information
|
||||||
type peer struct {
|
type peer struct {
|
||||||
contact Contact
|
Contact Contact
|
||||||
lastActivity time.Time
|
LastActivity time.Time
|
||||||
numFailures int
|
NumFailures int
|
||||||
//<lastPublished>,
|
//<lastPublished>,
|
||||||
//<originallyPublished>
|
//<originallyPublished>
|
||||||
// <originalPublisherID>
|
// <originalPublisherID>
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *peer) Touch() {
|
func (p *peer) Touch() {
|
||||||
p.lastActivity = time.Now()
|
p.LastActivity = time.Now()
|
||||||
p.numFailures = 0
|
p.NumFailures = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActiveSince returns whether a peer has responded in the last `d` duration
|
// ActiveSince returns whether a peer has responded in the last `d` duration
|
||||||
// this is used to check if the peer is "good", meaning that we believe the peer will respond to our requests
|
// this is used to check if the peer is "good", meaning that we believe the peer will respond to our requests
|
||||||
func (p *peer) ActiveInLast(d time.Duration) bool {
|
func (p *peer) ActiveInLast(d time.Duration) bool {
|
||||||
return time.Now().Sub(p.lastActivity) > d
|
return time.Now().Sub(p.LastActivity) > d
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsBad returns whether a peer is "bad", meaning that it has failed to respond to multiple pings in a row
|
// IsBad returns whether a peer is "bad", meaning that it has failed to respond to multiple pings in a row
|
||||||
func (p *peer) IsBad(maxFalures int) bool {
|
func (p *peer) IsBad(maxFalures int) bool {
|
||||||
return p.numFailures >= maxFalures
|
return p.NumFailures >= maxFalures
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fail marks a peer as having failed to respond. It returns whether or not the peer should be removed from the routing table
|
// Fail marks a peer as having failed to respond. It returns whether or not the peer should be removed from the routing table
|
||||||
func (p *peer) Fail() {
|
func (p *peer) Fail() {
|
||||||
p.numFailures++
|
p.NumFailures++
|
||||||
}
|
|
||||||
|
|
||||||
// toPeer converts a generic *list.Element into a *peer
|
|
||||||
// this (along with newPeer) keeps all conversions between *list.Element and peer in one place
|
|
||||||
func toPeer(el *list.Element) *peer {
|
|
||||||
return el.Value.(*peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newPeer creates a new peer from a contact
|
|
||||||
// this (along with toPeer) keeps all conversions between *list.Element and peer in one place
|
|
||||||
func newPeer(c Contact) peer {
|
|
||||||
return peer{
|
|
||||||
contact: c,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type bucket struct {
|
type bucket struct {
|
||||||
lock *sync.RWMutex
|
lock *sync.RWMutex
|
||||||
peers *list.List
|
peers []peer
|
||||||
lastUpdate time.Time
|
lastUpdate time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,16 +153,16 @@ type bucket struct {
|
||||||
func (b bucket) Len() int {
|
func (b bucket) Len() int {
|
||||||
b.lock.RLock()
|
b.lock.RLock()
|
||||||
defer b.lock.RUnlock()
|
defer b.lock.RUnlock()
|
||||||
return b.peers.Len()
|
return len(b.peers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contacts returns a slice of the bucket's contacts
|
// Contacts returns a slice of the bucket's contacts
|
||||||
func (b bucket) Contacts() []Contact {
|
func (b bucket) Contacts() []Contact {
|
||||||
b.lock.RLock()
|
b.lock.RLock()
|
||||||
defer b.lock.RUnlock()
|
defer b.lock.RUnlock()
|
||||||
contacts := make([]Contact, b.peers.Len())
|
contacts := make([]Contact, len(b.peers))
|
||||||
for i, curr := 0, b.peers.Front(); curr != nil; i, curr = i+1, curr.Next() {
|
for i := range b.peers {
|
||||||
contacts[i] = toPeer(curr).contact
|
contacts[i] = b.peers[i].Contact
|
||||||
}
|
}
|
||||||
return contacts
|
return contacts
|
||||||
}
|
}
|
||||||
|
@ -185,21 +172,21 @@ func (b *bucket) UpdateContact(c Contact, insertIfNew bool) {
|
||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
defer b.lock.Unlock()
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
element := find(c.id, b.peers)
|
peerIndex := find(c.ID, b.peers)
|
||||||
if element != nil {
|
if peerIndex >= 0 {
|
||||||
b.lastUpdate = time.Now()
|
b.lastUpdate = time.Now()
|
||||||
toPeer(element).Touch()
|
b.peers[peerIndex].Touch()
|
||||||
b.peers.MoveToBack(element)
|
moveToBack(b.peers, peerIndex)
|
||||||
|
|
||||||
} else if insertIfNew {
|
} else if insertIfNew {
|
||||||
hasRoom := true
|
hasRoom := true
|
||||||
|
|
||||||
if b.peers.Len() >= bucketSize {
|
if len(b.peers) >= bucketSize {
|
||||||
hasRoom = false
|
hasRoom = false
|
||||||
for curr := b.peers.Front(); curr != nil; curr = curr.Next() {
|
for i := range b.peers {
|
||||||
if toPeer(curr).IsBad(maxPeerFails) {
|
if b.peers[i].IsBad(maxPeerFails) {
|
||||||
// TODO: Ping contact first. Only remove if it does not respond
|
// TODO: Ping contact first. Only remove if it does not respond
|
||||||
b.peers.Remove(curr)
|
b.peers = append(b.peers[:i], b.peers[i+1:]...)
|
||||||
hasRoom = true
|
hasRoom = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -208,9 +195,9 @@ func (b *bucket) UpdateContact(c Contact, insertIfNew bool) {
|
||||||
|
|
||||||
if hasRoom {
|
if hasRoom {
|
||||||
b.lastUpdate = time.Now()
|
b.lastUpdate = time.Now()
|
||||||
peer := newPeer(c)
|
peer := peer{Contact: c}
|
||||||
peer.Touch()
|
peer.Touch()
|
||||||
b.peers.PushBack(&peer)
|
b.peers = append(b.peers, peer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,21 +206,21 @@ func (b *bucket) UpdateContact(c Contact, insertIfNew bool) {
|
||||||
func (b *bucket) FailContact(id Bitmap) {
|
func (b *bucket) FailContact(id Bitmap) {
|
||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
defer b.lock.Unlock()
|
defer b.lock.Unlock()
|
||||||
element := find(id, b.peers)
|
i := find(id, b.peers)
|
||||||
if element != nil {
|
if i >= 0 {
|
||||||
// BEP5 says not to remove the contact until the bucket is full and you try to insert
|
// BEP5 says not to remove the contact until the bucket is full and you try to insert
|
||||||
toPeer(element).Fail()
|
b.peers[i].Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// find returns the contact in the bucket, or nil if the bucket does not contain the contact
|
// find returns the contact in the bucket, or nil if the bucket does not contain the contact
|
||||||
func find(id Bitmap, peers *list.List) *list.Element {
|
func find(id Bitmap, peers []peer) int {
|
||||||
for curr := peers.Front(); curr != nil; curr = curr.Next() {
|
for i := range peers {
|
||||||
if toPeer(curr).contact.id.Equals(id) {
|
if peers[i].Contact.ID.Equals(id) {
|
||||||
return curr
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// NeedsRefresh returns true if bucket has not been updated in the last `refreshInterval`, false otherwise
|
// NeedsRefresh returns true if bucket has not been updated in the last `refreshInterval`, false otherwise
|
||||||
|
@ -243,41 +230,31 @@ func (b *bucket) NeedsRefresh(refreshInterval time.Duration) bool {
|
||||||
return time.Now().Sub(b.lastUpdate) > refreshInterval
|
return time.Now().Sub(b.lastUpdate) > refreshInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoutingTable interface {
|
type routingTable struct {
|
||||||
Update(Contact)
|
|
||||||
Fresh(Contact)
|
|
||||||
Fail(Contact)
|
|
||||||
GetClosest(Bitmap, int) []Contact
|
|
||||||
Count() int
|
|
||||||
GetIDsForRefresh(time.Duration) []Bitmap
|
|
||||||
BucketInfo() string // for debugging
|
|
||||||
}
|
|
||||||
|
|
||||||
type routingTableImpl struct {
|
|
||||||
id Bitmap
|
id Bitmap
|
||||||
buckets [numBuckets]bucket
|
buckets [nodeIDBits]bucket
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRoutingTable(id Bitmap) *routingTableImpl {
|
func newRoutingTable(id Bitmap) *routingTable {
|
||||||
var rt routingTableImpl
|
var rt routingTable
|
||||||
rt.id = id
|
rt.id = id
|
||||||
for i := range rt.buckets {
|
for i := range rt.buckets {
|
||||||
rt.buckets[i] = bucket{
|
rt.buckets[i] = bucket{
|
||||||
peers: list.New(),
|
peers: make([]peer, 0, bucketSize),
|
||||||
lock: &sync.RWMutex{},
|
lock: &sync.RWMutex{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &rt
|
return &rt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt *routingTableImpl) BucketInfo() string {
|
func (rt *routingTable) BucketInfo() string {
|
||||||
var bucketInfo []string
|
var bucketInfo []string
|
||||||
for i, b := range rt.buckets {
|
for i, b := range rt.buckets {
|
||||||
if b.Len() > 0 {
|
if b.Len() > 0 {
|
||||||
contacts := b.Contacts()
|
contacts := b.Contacts()
|
||||||
s := make([]string, len(contacts))
|
s := make([]string, len(contacts))
|
||||||
for j, c := range contacts {
|
for j, c := range contacts {
|
||||||
s[j] = c.id.HexShort()
|
s[j] = c.ID.HexShort()
|
||||||
}
|
}
|
||||||
bucketInfo = append(bucketInfo, fmt.Sprintf("Bucket %d: (%d) %s", i, len(contacts), strings.Join(s, ", ")))
|
bucketInfo = append(bucketInfo, fmt.Sprintf("Bucket %d: (%d) %s", i, len(contacts), strings.Join(s, ", ")))
|
||||||
}
|
}
|
||||||
|
@ -289,23 +266,23 @@ func (rt *routingTableImpl) BucketInfo() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update inserts or refreshes a contact
|
// Update inserts or refreshes a contact
|
||||||
func (rt *routingTableImpl) Update(c Contact) {
|
func (rt *routingTable) Update(c Contact) {
|
||||||
rt.bucketFor(c.id).UpdateContact(c, true)
|
rt.bucketFor(c.ID).UpdateContact(c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fresh refreshes a contact if its already in the routing table
|
// Fresh refreshes a contact if its already in the routing table
|
||||||
func (rt *routingTableImpl) Fresh(c Contact) {
|
func (rt *routingTable) Fresh(c Contact) {
|
||||||
rt.bucketFor(c.id).UpdateContact(c, false)
|
rt.bucketFor(c.ID).UpdateContact(c, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FailContact marks a contact as having failed, and removes it if it failed too many times
|
// FailContact marks a contact as having failed, and removes it if it failed too many times
|
||||||
func (rt *routingTableImpl) Fail(c Contact) {
|
func (rt *routingTable) Fail(c Contact) {
|
||||||
rt.bucketFor(c.id).FailContact(c.id)
|
rt.bucketFor(c.ID).FailContact(c.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetClosest returns the closest `limit` contacts from the routing table
|
// GetClosest returns the closest `limit` contacts from the routing table
|
||||||
// It marks each bucket it accesses as having been accessed
|
// It marks each bucket it accesses as having been accessed
|
||||||
func (rt *routingTableImpl) GetClosest(target Bitmap, limit int) []Contact {
|
func (rt *routingTable) GetClosest(target Bitmap, limit int) []Contact {
|
||||||
var toSort []sortedContact
|
var toSort []sortedContact
|
||||||
var bucketNum int
|
var bucketNum int
|
||||||
|
|
||||||
|
@ -317,11 +294,11 @@ func (rt *routingTableImpl) GetClosest(target Bitmap, limit int) []Contact {
|
||||||
|
|
||||||
toSort = appendContacts(toSort, rt.buckets[bucketNum], target)
|
toSort = appendContacts(toSort, rt.buckets[bucketNum], target)
|
||||||
|
|
||||||
for i := 1; (bucketNum-i >= 0 || bucketNum+i < numBuckets) && len(toSort) < limit; i++ {
|
for i := 1; (bucketNum-i >= 0 || bucketNum+i < nodeIDBits) && len(toSort) < limit; i++ {
|
||||||
if bucketNum-i >= 0 {
|
if bucketNum-i >= 0 {
|
||||||
toSort = appendContacts(toSort, rt.buckets[bucketNum-i], target)
|
toSort = appendContacts(toSort, rt.buckets[bucketNum-i], target)
|
||||||
}
|
}
|
||||||
if bucketNum+i < numBuckets {
|
if bucketNum+i < nodeIDBits {
|
||||||
toSort = appendContacts(toSort, rt.buckets[bucketNum+i], target)
|
toSort = appendContacts(toSort, rt.buckets[bucketNum+i], target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -341,13 +318,13 @@ func (rt *routingTableImpl) GetClosest(target Bitmap, limit int) []Contact {
|
||||||
|
|
||||||
func appendContacts(contacts []sortedContact, b bucket, target Bitmap) []sortedContact {
|
func appendContacts(contacts []sortedContact, b bucket, target Bitmap) []sortedContact {
|
||||||
for _, contact := range b.Contacts() {
|
for _, contact := range b.Contacts() {
|
||||||
contacts = append(contacts, sortedContact{contact, contact.id.Xor(target)})
|
contacts = append(contacts, sortedContact{contact, contact.ID.Xor(target)})
|
||||||
}
|
}
|
||||||
return contacts
|
return contacts
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count returns the number of contacts in the routing table
|
// Count returns the number of contacts in the routing table
|
||||||
func (rt *routingTableImpl) Count() int {
|
func (rt *routingTable) Count() int {
|
||||||
count := 0
|
count := 0
|
||||||
for _, bucket := range rt.buckets {
|
for _, bucket := range rt.buckets {
|
||||||
count = bucket.Len()
|
count = bucket.Len()
|
||||||
|
@ -355,27 +332,99 @@ func (rt *routingTableImpl) Count() int {
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt *routingTableImpl) bucketNumFor(target Bitmap) int {
|
type Range struct {
|
||||||
|
start Bitmap
|
||||||
|
end Bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
// BucketRanges returns a slice of ranges, where the `start` of each range is the smallest id that can
|
||||||
|
// go in that bucket, and the `end` is the largest id
|
||||||
|
func (rt *routingTable) BucketRanges() []Range {
|
||||||
|
ranges := make([]Range, len(rt.buckets))
|
||||||
|
for i := range rt.buckets {
|
||||||
|
ranges[i] = Range{
|
||||||
|
rt.id.Suffix(i, false).Set(nodeIDBits-1-i, !rt.id.Get(nodeIDBits-1-i)),
|
||||||
|
rt.id.Suffix(i, true).Set(nodeIDBits-1-i, !rt.id.Get(nodeIDBits-1-i)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ranges
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *routingTable) bucketNumFor(target Bitmap) int {
|
||||||
if rt.id.Equals(target) {
|
if rt.id.Equals(target) {
|
||||||
panic("routing table does not have a bucket for its own id")
|
panic("routing table does not have a bucket for its own id")
|
||||||
}
|
}
|
||||||
return numBuckets - 1 - target.Xor(rt.id).PrefixLen()
|
return nodeIDBits - 1 - target.Xor(rt.id).PrefixLen()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt *routingTableImpl) bucketFor(target Bitmap) *bucket {
|
func (rt *routingTable) bucketFor(target Bitmap) *bucket {
|
||||||
return &rt.buckets[rt.bucketNumFor(target)]
|
return &rt.buckets[rt.bucketNumFor(target)]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt *routingTableImpl) GetIDsForRefresh(refreshInterval time.Duration) []Bitmap {
|
func (rt *routingTable) GetIDsForRefresh(refreshInterval time.Duration) []Bitmap {
|
||||||
var bitmaps []Bitmap
|
var bitmaps []Bitmap
|
||||||
for i, bucket := range rt.buckets {
|
for i, bucket := range rt.buckets {
|
||||||
if bucket.NeedsRefresh(refreshInterval) {
|
if bucket.NeedsRefresh(refreshInterval) {
|
||||||
bitmaps = append(bitmaps, RandomBitmapP().ZeroPrefix(i))
|
bitmaps = append(bitmaps, RandomBitmapP().Prefix(i, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bitmaps
|
return bitmaps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rtContactSep = "-"
|
||||||
|
|
||||||
|
type rtSave struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Contacts []string `json:"contacts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *routingTable) MarshalJSON() ([]byte, error) {
|
||||||
|
var data rtSave
|
||||||
|
data.ID = rt.id.Hex()
|
||||||
|
for _, b := range rt.buckets {
|
||||||
|
for _, c := range b.Contacts() {
|
||||||
|
data.Contacts = append(data.Contacts, strings.Join([]string{c.ID.Hex(), c.IP.String(), strconv.Itoa(c.Port)}, rtContactSep))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json.Marshal(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *routingTable) UnmarshalJSON(b []byte) error {
|
||||||
|
var data rtSave
|
||||||
|
err := json.Unmarshal(b, &data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rt.id, err = BitmapFromHex(data.ID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Prefix("decoding ID", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range data.Contacts {
|
||||||
|
parts := strings.Split(s, rtContactSep)
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return errors.Err("decoding contact %s: wrong number of parts", s)
|
||||||
|
}
|
||||||
|
var c Contact
|
||||||
|
c.ID, err = BitmapFromHex(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return errors.Err("decoding contact %s: invalid ID: %s", s, err)
|
||||||
|
}
|
||||||
|
c.IP = net.ParseIP(parts[1])
|
||||||
|
if c.IP == nil {
|
||||||
|
return errors.Err("decoding contact %s: invalid IP", s)
|
||||||
|
}
|
||||||
|
c.Port, err = strconv.Atoi(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return errors.Err("decoding contact %s: invalid port: %s", s, err)
|
||||||
|
}
|
||||||
|
rt.Update(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// RoutingTableRefresh refreshes any buckets that need to be refreshed
|
// RoutingTableRefresh refreshes any buckets that need to be refreshed
|
||||||
// It returns a channel that will be closed when the refresh is done
|
// It returns a channel that will be closed when the refresh is done
|
||||||
func RoutingTableRefresh(n *Node, refreshInterval time.Duration, cancel <-chan struct{}) <-chan struct{} {
|
func RoutingTableRefresh(n *Node, refreshInterval time.Duration, cancel <-chan struct{}) <-chan struct{} {
|
||||||
|
@ -411,3 +460,14 @@ func RoutingTableRefresh(n *Node, refreshInterval time.Duration, cancel <-chan s
|
||||||
|
|
||||||
return done
|
return done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func moveToBack(peers []peer, index int) {
|
||||||
|
if index < 0 || len(peers) <= index+1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p := peers[index]
|
||||||
|
for i := index; i < len(peers)-1; i++ {
|
||||||
|
peers[i] = peers[i+1]
|
||||||
|
}
|
||||||
|
peers[len(peers)-1] = p
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
package dht
|
package dht
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sebdah/goldie"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRoutingTable_bucketFor(t *testing.T) {
|
func TestRoutingTable_bucketFor(t *testing.T) {
|
||||||
|
@ -31,7 +36,7 @@ func TestRoutingTable_bucketFor(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRoutingTable(t *testing.T) {
|
func TestRoutingTable_GetClosest(t *testing.T) {
|
||||||
n1 := BitmapFromHexP("FFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
n1 := BitmapFromHexP("FFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||||
n2 := BitmapFromHexP("FFFFFFF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
n2 := BitmapFromHexP("FFFFFFF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||||
n3 := BitmapFromHexP("111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
n3 := BitmapFromHexP("111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||||
|
@ -44,7 +49,7 @@ func TestRoutingTable(t *testing.T) {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !contacts[0].id.Equals(n3) {
|
if !contacts[0].ID.Equals(n3) {
|
||||||
t.Error(contacts[0])
|
t.Error(contacts[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,19 +58,19 @@ func TestRoutingTable(t *testing.T) {
|
||||||
t.Error(len(contacts))
|
t.Error(len(contacts))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !contacts[0].id.Equals(n2) {
|
if !contacts[0].ID.Equals(n2) {
|
||||||
t.Error(contacts[0])
|
t.Error(contacts[0])
|
||||||
}
|
}
|
||||||
if !contacts[1].id.Equals(n3) {
|
if !contacts[1].ID.Equals(n3) {
|
||||||
t.Error(contacts[1])
|
t.Error(contacts[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompactEncoding(t *testing.T) {
|
func TestCompactEncoding(t *testing.T) {
|
||||||
c := Contact{
|
c := Contact{
|
||||||
id: BitmapFromHexP("1c8aff71b99462464d9eeac639595ab99664be3482cb91a29d87467515c7d9158fe72aa1f1582dab07d8f8b5db277f41"),
|
ID: BitmapFromHexP("1c8aff71b99462464d9eeac639595ab99664be3482cb91a29d87467515c7d9158fe72aa1f1582dab07d8f8b5db277f41"),
|
||||||
ip: net.ParseIP("1.2.3.4"),
|
IP: net.ParseIP("1.2.3.4"),
|
||||||
port: int(55<<8 + 66),
|
Port: int(55<<8 + 66),
|
||||||
}
|
}
|
||||||
|
|
||||||
var compact []byte
|
var compact []byte
|
||||||
|
@ -78,11 +83,117 @@ func TestCompactEncoding(t *testing.T) {
|
||||||
t.Fatalf("got length of %d; expected %d", len(compact), compactNodeInfoLength)
|
t.Fatalf("got length of %d; expected %d", len(compact), compactNodeInfoLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(compact, append([]byte{1, 2, 3, 4, 55, 66}, c.id[:]...)) {
|
if !reflect.DeepEqual(compact, append([]byte{1, 2, 3, 4, 55, 66}, c.ID[:]...)) {
|
||||||
t.Errorf("compact bytes not encoded correctly")
|
t.Errorf("compact bytes not encoded correctly")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRoutingTableRefresh(t *testing.T) {
|
func TestRoutingTable_Refresh(t *testing.T) {
|
||||||
t.Skip("TODO: test routing table refreshing")
|
t.Skip("TODO: test routing table refreshing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRoutingTable_MoveToBack(t *testing.T) {
|
||||||
|
tt := map[string]struct {
|
||||||
|
data []peer
|
||||||
|
index int
|
||||||
|
expected []peer
|
||||||
|
}{
|
||||||
|
"simpleMove": {
|
||||||
|
data: []peer{{NumFailures: 0}, {NumFailures: 1}, {NumFailures: 2}, {NumFailures: 3}},
|
||||||
|
index: 1,
|
||||||
|
expected: []peer{{NumFailures: 0}, {NumFailures: 2}, {NumFailures: 3}, {NumFailures: 1}},
|
||||||
|
},
|
||||||
|
"moveFirst": {
|
||||||
|
data: []peer{{NumFailures: 0}, {NumFailures: 1}, {NumFailures: 2}, {NumFailures: 3}},
|
||||||
|
index: 0,
|
||||||
|
expected: []peer{{NumFailures: 1}, {NumFailures: 2}, {NumFailures: 3}, {NumFailures: 0}},
|
||||||
|
},
|
||||||
|
"moveLast": {
|
||||||
|
data: []peer{{NumFailures: 0}, {NumFailures: 1}, {NumFailures: 2}, {NumFailures: 3}},
|
||||||
|
index: 3,
|
||||||
|
expected: []peer{{NumFailures: 0}, {NumFailures: 1}, {NumFailures: 2}, {NumFailures: 3}},
|
||||||
|
},
|
||||||
|
"largeIndex": {
|
||||||
|
data: []peer{{NumFailures: 0}, {NumFailures: 1}, {NumFailures: 2}, {NumFailures: 3}},
|
||||||
|
index: 27,
|
||||||
|
expected: []peer{{NumFailures: 0}, {NumFailures: 1}, {NumFailures: 2}, {NumFailures: 3}},
|
||||||
|
},
|
||||||
|
"negativeIndex": {
|
||||||
|
data: []peer{{NumFailures: 0}, {NumFailures: 1}, {NumFailures: 2}, {NumFailures: 3}},
|
||||||
|
index: -12,
|
||||||
|
expected: []peer{{NumFailures: 0}, {NumFailures: 1}, {NumFailures: 2}, {NumFailures: 3}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, test := range tt {
|
||||||
|
moveToBack(test.data, test.index)
|
||||||
|
expected := make([]string, len(test.expected))
|
||||||
|
actual := make([]string, len(test.data))
|
||||||
|
for i := range actual {
|
||||||
|
actual[i] = strconv.Itoa(test.data[i].NumFailures)
|
||||||
|
expected[i] = strconv.Itoa(test.expected[i].NumFailures)
|
||||||
|
}
|
||||||
|
|
||||||
|
expJoin := strings.Join(expected, ",")
|
||||||
|
actJoin := strings.Join(actual, ",")
|
||||||
|
|
||||||
|
if actJoin != expJoin {
|
||||||
|
t.Errorf("%s failed: got %s; expected %s", name, actJoin, expJoin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoutingTable_BucketRanges(t *testing.T) {
|
||||||
|
id := BitmapFromHexP("1c8aff71b99462464d9eeac639595ab99664be3482cb91a29d87467515c7d9158fe72aa1f1582dab07d8f8b5db277f41")
|
||||||
|
ranges := newRoutingTable(id).BucketRanges()
|
||||||
|
if !ranges[0].start.Equals(ranges[0].end) {
|
||||||
|
t.Error("first bucket should only fit exactly one id")
|
||||||
|
}
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
randID := RandomBitmapP()
|
||||||
|
found := -1
|
||||||
|
for i, r := range ranges {
|
||||||
|
if r.start.LessOrEqual(randID) && r.end.GreaterOrEqual(randID) {
|
||||||
|
if found >= 0 {
|
||||||
|
t.Errorf("%s appears in buckets %d and %d", randID.Hex(), found, i)
|
||||||
|
} else {
|
||||||
|
found = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found < 0 {
|
||||||
|
t.Errorf("%s did not appear in any bucket", randID.Hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoutingTable_Save(t *testing.T) {
|
||||||
|
id := BitmapFromHexP("1c8aff71b99462464d9eeac639595ab99664be3482cb91a29d87467515c7d9158fe72aa1f1582dab07d8f8b5db277f41")
|
||||||
|
rt := newRoutingTable(id)
|
||||||
|
|
||||||
|
ranges := rt.BucketRanges()
|
||||||
|
|
||||||
|
for i, r := range ranges {
|
||||||
|
for j := 0; j < bucketSize; j++ {
|
||||||
|
toAdd := r.start.Add(BitmapFromShortHexP(strconv.Itoa(j)))
|
||||||
|
if toAdd.LessOrEqual(r.end) {
|
||||||
|
rt.Update(Contact{
|
||||||
|
ID: r.start.Add(BitmapFromShortHexP(strconv.Itoa(j))),
|
||||||
|
IP: net.ParseIP("1.2.3." + strconv.Itoa(j)),
|
||||||
|
Port: 1 + i*bucketSize + j,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.MarshalIndent(rt, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
goldie.Assert(t, t.Name(), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoutingTable_Load(t *testing.T) {
|
||||||
|
t.Skip("TODO")
|
||||||
|
}
|
||||||
|
|
24
dht/store.go
24
dht/store.go
|
@ -2,13 +2,7 @@ package dht
|
||||||
|
|
||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
type Store interface {
|
type contactStore struct {
|
||||||
Upsert(Bitmap, Contact)
|
|
||||||
Get(Bitmap) []Contact
|
|
||||||
CountStoredHashes() int
|
|
||||||
}
|
|
||||||
|
|
||||||
type storeImpl struct {
|
|
||||||
// map of blob hashes to (map of node IDs to bools)
|
// map of blob hashes to (map of node IDs to bools)
|
||||||
hashes map[Bitmap]map[Bitmap]bool
|
hashes map[Bitmap]map[Bitmap]bool
|
||||||
// stores the peers themselves, so they can be updated in one place
|
// stores the peers themselves, so they can be updated in one place
|
||||||
|
@ -16,25 +10,25 @@ type storeImpl struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStore() *storeImpl {
|
func newStore() *contactStore {
|
||||||
return &storeImpl{
|
return &contactStore{
|
||||||
hashes: make(map[Bitmap]map[Bitmap]bool),
|
hashes: make(map[Bitmap]map[Bitmap]bool),
|
||||||
contacts: make(map[Bitmap]Contact),
|
contacts: make(map[Bitmap]Contact),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *storeImpl) Upsert(blobHash Bitmap, contact Contact) {
|
func (s *contactStore) Upsert(blobHash Bitmap, contact Contact) {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
if _, ok := s.hashes[blobHash]; !ok {
|
if _, ok := s.hashes[blobHash]; !ok {
|
||||||
s.hashes[blobHash] = make(map[Bitmap]bool)
|
s.hashes[blobHash] = make(map[Bitmap]bool)
|
||||||
}
|
}
|
||||||
s.hashes[blobHash][contact.id] = true
|
s.hashes[blobHash][contact.ID] = true
|
||||||
s.contacts[contact.id] = contact
|
s.contacts[contact.ID] = contact
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *storeImpl) Get(blobHash Bitmap) []Contact {
|
func (s *contactStore) Get(blobHash Bitmap) []Contact {
|
||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
|
@ -51,11 +45,11 @@ func (s *storeImpl) Get(blobHash Bitmap) []Contact {
|
||||||
return contacts
|
return contacts
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *storeImpl) RemoveTODO(contact Contact) {
|
func (s *contactStore) RemoveTODO(contact Contact) {
|
||||||
// TODO: remove peer from everywhere
|
// TODO: remove peer from everywhere
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *storeImpl) CountStoredHashes() int {
|
func (s *contactStore) CountStoredHashes() int {
|
||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
return len(s.hashes)
|
return len(s.hashes)
|
||||||
|
|
|
@ -215,7 +215,7 @@ func verifyContacts(t *testing.T, contacts []interface{}, nodes []Contact) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, n := range nodes {
|
for _, n := range nodes {
|
||||||
if n.id.RawString() == id {
|
if n.ID.RawString() == id {
|
||||||
currNode = n
|
currNode = n
|
||||||
currNodeFound = true
|
currNodeFound = true
|
||||||
foundNodes[id] = true
|
foundNodes[id] = true
|
||||||
|
@ -231,15 +231,15 @@ func verifyContacts(t *testing.T, contacts []interface{}, nodes []Contact) {
|
||||||
ip, ok := contact[1].(string)
|
ip, ok := contact[1].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Error("contact IP is not a string")
|
t.Error("contact IP is not a string")
|
||||||
} else if !currNode.ip.Equal(net.ParseIP(ip)) {
|
} else if !currNode.IP.Equal(net.ParseIP(ip)) {
|
||||||
t.Errorf("contact IP mismatch. got %s; expected %s", ip, currNode.ip.String())
|
t.Errorf("contact IP mismatch. got %s; expected %s", ip, currNode.IP.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
port, ok := contact[2].(int64)
|
port, ok := contact[2].(int64)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Error("contact port is not an int")
|
t.Error("contact port is not an int")
|
||||||
} else if int(port) != currNode.port {
|
} else if int(port) != currNode.Port {
|
||||||
t.Errorf("contact port mismatch. got %d; expected %d", port, currNode.port)
|
t.Errorf("contact port mismatch. got %d; expected %d", port, currNode.Port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,29 +269,38 @@ func verifyCompactContacts(t *testing.T, contacts []interface{}, nodes []Contact
|
||||||
var currNode Contact
|
var currNode Contact
|
||||||
currNodeFound := false
|
currNodeFound := false
|
||||||
|
|
||||||
if _, ok := foundNodes[contact.id.Hex()]; ok {
|
if _, ok := foundNodes[contact.ID.Hex()]; ok {
|
||||||
t.Errorf("contact %s appears multiple times", contact.id.Hex())
|
t.Errorf("contact %s appears multiple times", contact.ID.Hex())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, n := range nodes {
|
for _, n := range nodes {
|
||||||
if n.id.Equals(contact.id) {
|
if n.ID.Equals(contact.ID) {
|
||||||
currNode = n
|
currNode = n
|
||||||
currNodeFound = true
|
currNodeFound = true
|
||||||
foundNodes[contact.id.Hex()] = true
|
foundNodes[contact.ID.Hex()] = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !currNodeFound {
|
if !currNodeFound {
|
||||||
t.Errorf("unexpected contact %s", contact.id.Hex())
|
t.Errorf("unexpected contact %s", contact.ID.Hex())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !currNode.ip.Equal(contact.ip) {
|
if !currNode.IP.Equal(contact.IP) {
|
||||||
t.Errorf("contact IP mismatch. got %s; expected %s", contact.ip.String(), currNode.ip.String())
|
t.Errorf("contact IP mismatch. got %s; expected %s", contact.IP.String(), currNode.IP.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if contact.port != currNode.port {
|
if contact.Port != currNode.Port {
|
||||||
t.Errorf("contact port mismatch. got %d; expected %d", contact.port, currNode.port)
|
t.Errorf("contact port mismatch. got %d; expected %d", contact.Port, currNode.Port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertPanic(t *testing.T, text string, f func()) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r == nil {
|
||||||
|
t.Errorf("%s: did not panic as expected", text)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue