@ -17,44 +17,63 @@
package enode
package enode
import (
import (
"bytes"
"crypto/ecdsa"
"crypto/ecdsa"
"math/big"
"net"
"net"
"reflect"
"reflect"
"strings"
"strings"
"testing"
"testing"
"testing/quick"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/enr"
)
)
var parseNodeTests = [ ] struct {
var parseNodeTests = [ ] struct {
rawurl string
input string
wantError string
wantError string
wantResult * Node
wantResult * Node
} {
} {
// Records
{
input : "enr:-IS4QGrdq0ugARp5T2BZ41TrZOqLc_oKvZoPuZP5--anqWE_J-Tucc1xgkOL7qXl0puJgT7qc2KSvcupc4NCb0nr4tdjgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQM6UUF2Rm-oFe1IH_rQkRCi00T2ybeMHRSvw1HDpRvjPYN1ZHCCdl8" ,
wantResult : func ( ) * Node {
testKey , _ := crypto . HexToECDSA ( "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" )
var r enr . Record
r . Set ( enr . IP { 127 , 0 , 0 , 1 } )
r . Set ( enr . UDP ( 30303 ) )
r . SetSeq ( 99 )
SignV4 ( & r , testKey )
n , _ := New ( ValidSchemes , & r )
return n
} ( ) ,
} ,
// Invalid Records
{
{
rawurl : "http://foobar" ,
input : "enr: ",
wantError : ` invalid URL scheme, want "enode" ` ,
wantError : "EOF" , // could be nicer
} ,
} ,
{
{
rawurl : "enode://01010101@123.124.125.126:3" ,
input : "enr:x ",
wantError : ` invalid node ID (wrong length, want 128 hex chars) ` ,
wantError : "illegal base64 data at input byte 0" ,
} ,
} ,
// Complete nodes with IP address.
{
{
rawurl : "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3" ,
input : "enr:-EmGZm9vYmFyY4JpZIJ2NIJpcIR_AAABiXNlY3AyNTZrMaEDOlFBdkZvqBXtSB_60JEQotNE9sm3jB0Ur8NRw6Ub4z2DdWRwgnZf" ,
wantError : enr . ErrInvalidSig . Error ( ) ,
} ,
// Complete node URLs with IP address and ports
{
input : "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3" ,
wantError : ` invalid IP address ` ,
wantError : ` invalid IP address ` ,
} ,
} ,
{
{
rawurl : "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo" ,
input : "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo" ,
wantError : ` invalid port ` ,
wantError : ` invalid port ` ,
} ,
} ,
{
{
rawurl : "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo" ,
input : "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo" ,
wantError : ` invalid discport in query ` ,
wantError : ` invalid discport in query ` ,
} ,
} ,
{
{
rawurl : "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150" ,
input : "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150" ,
wantResult : NewV4 (
wantResult : NewV4 (
hexPubkey ( "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" ) ,
hexPubkey ( "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" ) ,
net . IP { 0x7f , 0x0 , 0x0 , 0x1 } ,
net . IP { 0x7f , 0x0 , 0x0 , 0x1 } ,
@ -63,7 +82,7 @@ var parseNodeTests = []struct {
) ,
) ,
} ,
} ,
{
{
rawurl : "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150" ,
input : "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150" ,
wantResult : NewV4 (
wantResult : NewV4 (
hexPubkey ( "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" ) ,
hexPubkey ( "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" ) ,
net . ParseIP ( "::" ) ,
net . ParseIP ( "::" ) ,
@ -72,7 +91,7 @@ var parseNodeTests = []struct {
) ,
) ,
} ,
} ,
{
{
rawurl : "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150" ,
input : "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150" ,
wantResult : NewV4 (
wantResult : NewV4 (
hexPubkey ( "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" ) ,
hexPubkey ( "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" ) ,
net . ParseIP ( "2001:db8:3c4d:15::abcd:ef12" ) ,
net . ParseIP ( "2001:db8:3c4d:15::abcd:ef12" ) ,
@ -81,7 +100,7 @@ var parseNodeTests = []struct {
) ,
) ,
} ,
} ,
{
{
rawurl : "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334" ,
input : "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334" ,
wantResult : NewV4 (
wantResult : NewV4 (
hexPubkey ( "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" ) ,
hexPubkey ( "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" ) ,
net . IP { 0x7f , 0x0 , 0x0 , 0x1 } ,
net . IP { 0x7f , 0x0 , 0x0 , 0x1 } ,
@ -89,34 +108,42 @@ var parseNodeTests = []struct {
22334 ,
22334 ,
) ,
) ,
} ,
} ,
// Incomplete nodes with no address.
// Incomplete node URL s with no address
{
{
rawurl : "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" ,
input : "enode:// 1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" ,
wantResult : NewV4 (
wantResult : NewV4 (
hexPubkey ( "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" ) ,
hexPubkey ( "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" ) ,
nil , 0 , 0 ,
nil , 0 , 0 ,
) ,
) ,
} ,
} ,
// Invalid URLs
{
{
rawurl : "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" ,
input : "" ,
wantResult : NewV4 (
wantError : errMissingPrefix . Error ( ) ,
hexPubkey ( "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" ) ,
nil , 0 , 0 ,
) ,
} ,
} ,
// Invalid URLs
{
{
rawurl : "01010101 ",
input : "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" ,
wantError : ` invalid node ID (wrong length, want 128 hex chars) ` ,
wantError : errMissingPrefix . Error ( ) ,
} ,
} ,
{
{
rawurl : "enode:// 01010101",
input : " 01010101",
wantError : ` invalid node ID (wrong length, want 128 hex chars) ` ,
wantError : errMissingPrefix . Error ( ) ,
} ,
} ,
{
{
// This test checks that errors from url.Parse are handled.
input : "enode://01010101@123.124.125.126:3" ,
rawurl : "://foo" ,
wantError : ` invalid public key (wrong length, want 128 hex chars) ` ,
wantError : ` parse ://foo: missing protocol scheme ` ,
} ,
{
input : "enode://01010101" ,
wantError : ` invalid public key (wrong length, want 128 hex chars) ` ,
} ,
{
input : "http://foobar" ,
wantError : errMissingPrefix . Error ( ) ,
} ,
{
input : "://foo" ,
wantError : errMissingPrefix . Error ( ) ,
} ,
} ,
}
}
@ -130,22 +157,22 @@ func hexPubkey(h string) *ecdsa.PublicKey {
func TestParseNode ( t * testing . T ) {
func TestParseNode ( t * testing . T ) {
for _ , test := range parseNodeTests {
for _ , test := range parseNodeTests {
n , err := ParseV4 ( test . rawurl )
n , err := Parse ( ValidSchemes , test . input )
if test . wantError != "" {
if test . wantError != "" {
if err == nil {
if err == nil {
t . Errorf ( "test %q:\n got nil error, expected %#q" , test . rawurl , test . wantError )
t . Errorf ( "test %q:\n got nil error, expected %#q" , test . input , test . wantError )
continue
continue
} else if err . Error ( ) != test . wantError {
} else if err . Error ( ) != test . wantError {
t . Errorf ( "test %q:\n got error %#q, expected %#q" , test . rawurl , err . Error ( ) , test . wantError )
t . Errorf ( "test %q:\n got error %#q, expected %#q" , test . input , err . Error ( ) , test . wantError )
continue
continue
}
}
} else {
} else {
if err != nil {
if err != nil {
t . Errorf ( "test %q:\n unexpected error: %v" , test . rawurl , err )
t . Errorf ( "test %q:\n unexpected error: %v" , test . input , err )
continue
continue
}
}
if ! reflect . DeepEqual ( n , test . wantResult ) {
if ! reflect . DeepEqual ( n , test . wantResult ) {
t . Errorf ( "test %q:\n result mismatch:\ngot: %#v\nwant: %#v" , test . rawurl , n , test . wantResult )
t . Errorf ( "test %q:\n result mismatch:\ngot: %#v\nwant: %#v" , test . input , n , test . wantResult )
}
}
}
}
}
}
@ -153,91 +180,11 @@ func TestParseNode(t *testing.T) {
func TestNodeString ( t * testing . T ) {
func TestNodeString ( t * testing . T ) {
for i , test := range parseNodeTests {
for i , test := range parseNodeTests {
if test . wantError == "" && strings . HasPrefix ( test . rawurl , "enode://" ) {
if test . wantError == "" && strings . HasPrefix ( test . input , "enode://" ) {
str := test . wantResult . String ( )
str := test . wantResult . String ( )
if str != test . rawurl {
if str != test . input {
t . Errorf ( "test %d: Node.String() mismatch:\ngot: %s\nwant: %s" , i , str , test . rawurl )
t . Errorf ( "test %d: Node.String() mismatch:\ngot: %s\nwant: %s" , i , str , test . input )
}
}
}
}
}
}
}
}
func TestHexID ( t * testing . T ) {
ref := ID { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 128 , 106 , 217 , 182 , 31 , 165 , 174 , 1 , 67 , 7 , 235 , 220 , 150 , 66 , 83 , 173 , 205 , 159 , 44 , 10 , 57 , 42 , 161 , 26 , 188 }
id1 := HexID ( "0x00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc" )
id2 := HexID ( "00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc" )
if id1 != ref {
t . Errorf ( "wrong id1\ngot %v\nwant %v" , id1 [ : ] , ref [ : ] )
}
if id2 != ref {
t . Errorf ( "wrong id2\ngot %v\nwant %v" , id2 [ : ] , ref [ : ] )
}
}
func TestID_textEncoding ( t * testing . T ) {
ref := ID {
0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 , 0x09 , 0x10 ,
0x11 , 0x12 , 0x13 , 0x14 , 0x15 , 0x16 , 0x17 , 0x18 , 0x19 , 0x20 ,
0x21 , 0x22 , 0x23 , 0x24 , 0x25 , 0x26 , 0x27 , 0x28 , 0x29 , 0x30 ,
0x31 , 0x32 ,
}
hex := "0102030405060708091011121314151617181920212223242526272829303132"
text , err := ref . MarshalText ( )
if err != nil {
t . Fatal ( err )
}
if ! bytes . Equal ( text , [ ] byte ( hex ) ) {
t . Fatalf ( "text encoding did not match\nexpected: %s\ngot: %s" , hex , text )
}
id := new ( ID )
if err := id . UnmarshalText ( text ) ; err != nil {
t . Fatal ( err )
}
if * id != ref {
t . Fatalf ( "text decoding did not match\nexpected: %s\ngot: %s" , ref , id )
}
}
func TestNodeID_distcmp ( t * testing . T ) {
distcmpBig := func ( target , a , b ID ) int {
tbig := new ( big . Int ) . SetBytes ( target [ : ] )
abig := new ( big . Int ) . SetBytes ( a [ : ] )
bbig := new ( big . Int ) . SetBytes ( b [ : ] )
return new ( big . Int ) . Xor ( tbig , abig ) . Cmp ( new ( big . Int ) . Xor ( tbig , bbig ) )
}
if err := quick . CheckEqual ( DistCmp , distcmpBig , nil ) ; err != nil {
t . Error ( err )
}
}
// The random tests is likely to miss the case where a and b are equal,
// this test checks it explicitly.
func TestNodeID_distcmpEqual ( t * testing . T ) {
base := ID { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 }
x := ID { 15 , 14 , 13 , 12 , 11 , 10 , 9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 , 0 }
if DistCmp ( base , x , x ) != 0 {
t . Errorf ( "DistCmp(base, x, x) != 0" )
}
}
func TestNodeID_logdist ( t * testing . T ) {
logdistBig := func ( a , b ID ) int {
abig , bbig := new ( big . Int ) . SetBytes ( a [ : ] ) , new ( big . Int ) . SetBytes ( b [ : ] )
return new ( big . Int ) . Xor ( abig , bbig ) . BitLen ( )
}
if err := quick . CheckEqual ( LogDist , logdistBig , nil ) ; err != nil {
t . Error ( err )
}
}
// The random tests is likely to miss the case where a and b are equal,
// this test checks it explicitly.
func TestNodeID_logdistEqual ( t * testing . T ) {
x := ID { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 }
if LogDist ( x , x ) != 0 {
t . Errorf ( "LogDist(x, x) != 0" )
}
}