Added better prediction of waku RLP key type and value

This commit is contained in:
Samuel Hawksby-Robinson 2020-04-14 22:52:34 +01:00 committed by Andrea Maria Piana
parent a50e35b72d
commit 900bad769a
2 changed files with 106 additions and 24 deletions

View File

@ -1,8 +1,11 @@
package waku package waku
import ( import (
"bytes"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"github.com/davecgh/go-spew/spew"
"io" "io"
"math" "math"
"reflect" "reflect"
@ -202,28 +205,36 @@ loop:
} }
func (o statusOptions) decodeKey(s *rlp.Stream) (statusOptionKey, statusOptionKeyType, error) { func (o statusOptions) decodeKey(s *rlp.Stream) (statusOptionKey, statusOptionKeyType, error) {
var key statusOptionKey // Problem: A string will be encoded to bytes, and bytes can be decoded into a uint.
// This means that an encoded string that is attempted to be decoded into a uint will succeed and return a valid uint.
// This is bad because wildly inaccurate keys can be returned. See below examples:
// - string("0"); encodes to byte(48); decodes to uint(48).
// - string("111"); encodes to []byte(131, 49, 49, 49); decode to uint(3223857).
// This means an expected index of 0 will be returned as 48. An expected index of 111 will be returned as 3223857
// If statusOptionKey (uint) can be decoded return it // Solution: We need to first test if the RLP stream can be decoded into a string.
// Ignore the first error and attempt string decoding // If a stream can be decoded into a string, attempt to decode the string into a uint.
if err := s.Decode(&key); err == nil { // If decoding the string into a uint is successful return the value.
return key, sOKTU, nil // If decoding the string failed, attempt to decode as a uint. Return the result or error from this final step.
}
// Attempt decoding into a string // decode into bytes, detect if bytes can be parsed as a string and from a string to a uint
var sKey string var bKey []byte
if err := s.Decode(&sKey); err != nil { if err := s.Decode(&bKey); err != nil {
return key, 0, err return 0, 0, err
} }
// Parse string into uint // Parse string into uint
uKey, err := strconv.ParseUint(sKey, 10, 64) uKey, err := strconv.ParseUint(string(bKey), 10, 64)
if err != nil { if err == nil {
return key, 0, err return statusOptionKey(uKey), sOKTS, err
} }
key = statusOptionKey(uKey) // If statusOptionKey (uint) can be decoded return it
return key, sOKTS, nil buf := bytes.NewBuffer(bKey)
uintKey, c := binary.ReadUvarint(buf)
spew.Dump(uintKey, c)
return statusOptionKey(uintKey), sOKTU, nil
} }
// setKeyType sets a statusOptions' keyType if it hasn't previously been set // setKeyType sets a statusOptions' keyType if it hasn't previously been set

View File

@ -1,6 +1,8 @@
package waku package waku
import ( import (
"fmt"
"github.com/davecgh/go-spew/spew"
"math" "math"
"testing" "testing"
@ -36,18 +38,87 @@ func TestEncodeDecodeRLP(t *testing.T) {
require.EqualValues(t, opts, optsDecoded) require.EqualValues(t, opts, optsDecoded)
} }
func TestBackwardCompatibility(t *testing.T) { // TODO remove once key type issue is resolved.
alist := []interface{}{ func TestKeyTypes(t *testing.T) {
[]interface{}{"0", math.Float64bits(2.05)}, uKeys := []uint{
0, 1, 2, 49, 50, 256, 257, 1000, 6000,
} }
data, err := rlp.EncodeToBytes(alist)
require.NoError(t, err)
var optsDecoded statusOptions for i, uKey := range uKeys {
err = rlp.DecodeBytes(data, &optsDecoded) fmt.Printf("test %d, for key '%d'", i+1, uKey)
require.NoError(t, err)
encodeable := []interface{}{
[]interface{}{uKey, true},
}
data, err := rlp.EncodeToBytes(encodeable)
spew.Dump(data, err)
var optsDecoded statusOptions
err = rlp.DecodeBytes(data, &optsDecoded)
spew.Dump(optsDecoded, err)
println("\n----------------\n")
}
}
func TestBackwardCompatibility(t *testing.T) {
pow := math.Float64bits(2.05) pow := math.Float64bits(2.05)
require.EqualValues(t, statusOptions{PoWRequirement: &pow}, optsDecoded) lne := true
cs := []struct {
Input []interface{}
Expected statusOptions
}{
{
[]interface{}{
[]interface{}{"0", pow},
},
statusOptions{PoWRequirement: &pow, keyType: sOKTS},
},
{
[]interface{}{
[]interface{}{"2", true},
},
statusOptions{LightNodeEnabled: &lne, keyType: sOKTS},
},
{
[]interface{}{
[]interface{}{uint(2), true},
},
statusOptions{LightNodeEnabled: &lne, keyType: sOKTU},
},
{
[]interface{}{
[]interface{}{uint(0), pow},
},
statusOptions{PoWRequirement: &pow, keyType: sOKTU},
},
{
[]interface{}{
[]interface{}{"1000", true},
},
statusOptions{keyType: sOKTS},
},
{
[]interface{}{
[]interface{}{uint(1000), true},
},
statusOptions{keyType: sOKTU},
},
}
for i, c := range cs {
failMsg := fmt.Sprintf("test '%d'", i+1)
data, err := rlp.EncodeToBytes(c.Input)
require.NoError(t, err, failMsg)
var optsDecoded statusOptions
err = rlp.DecodeBytes(data, &optsDecoded)
require.NoError(t, err, failMsg)
require.EqualValues(t, c.Expected, optsDecoded, failMsg)
}
} }
func TestForwardCompatibility(t *testing.T) { func TestForwardCompatibility(t *testing.T) {