aboutsummaryrefslogtreecommitdiffstats
path: root/rlp/decode_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'rlp/decode_test.go')
-rw-r--r--rlp/decode_test.go287
1 files changed, 190 insertions, 97 deletions
diff --git a/rlp/decode_test.go b/rlp/decode_test.go
index 73a31c67f..d07520bd0 100644
--- a/rlp/decode_test.go
+++ b/rlp/decode_test.go
@@ -21,22 +21,18 @@ func TestStreamKind(t *testing.T) {
{"7F", Byte, 0},
{"80", String, 0},
{"B7", String, 55},
- {"B800", String, 0},
{"B90400", String, 1024},
- {"BA000400", String, 1024},
- {"BB00000400", String, 1024},
{"BFFFFFFFFFFFFFFFFF", String, ^uint64(0)},
{"C0", List, 0},
{"C8", List, 8},
{"F7", List, 55},
- {"F800", List, 0},
- {"F804", List, 4},
{"F90400", List, 1024},
{"FFFFFFFFFFFFFFFFFF", List, ^uint64(0)},
}
for i, test := range tests {
- s := NewStream(bytes.NewReader(unhex(test.input)))
+ // using plainReader to inhibit input limit errors.
+ s := NewStream(newPlainReader(unhex(test.input)), 0)
kind, len, err := s.Kind()
if err != nil {
t.Errorf("test %d: Kind returned error: %v", i, err)
@@ -70,29 +66,85 @@ func TestNewListStream(t *testing.T) {
}
func TestStreamErrors(t *testing.T) {
+ withoutInputLimit := func(b []byte) *Stream {
+ return NewStream(newPlainReader(b), 0)
+ }
+ withCustomInputLimit := func(limit uint64) func([]byte) *Stream {
+ return func(b []byte) *Stream {
+ return NewStream(bytes.NewReader(b), limit)
+ }
+ }
+
type calls []string
tests := []struct {
string
calls
- error
+ newStream func([]byte) *Stream // uses bytes.Reader if nil
+ error error
}{
- {"", calls{"Kind"}, io.EOF},
- {"", calls{"List"}, io.EOF},
- {"", calls{"Uint"}, io.EOF},
- {"C0", calls{"Bytes"}, ErrExpectedString},
- {"C0", calls{"Uint"}, ErrExpectedString},
- {"81", calls{"Bytes"}, io.ErrUnexpectedEOF},
- {"81", calls{"Uint"}, io.ErrUnexpectedEOF},
- {"BFFFFFFFFFFFFFFF", calls{"Bytes"}, io.ErrUnexpectedEOF},
- {"89000000000000000001", calls{"Uint"}, errUintOverflow},
- {"00", calls{"List"}, ErrExpectedList},
- {"80", calls{"List"}, ErrExpectedList},
- {"C0", calls{"List", "Uint"}, EOL},
- {"C801", calls{"List", "Uint", "Uint"}, io.ErrUnexpectedEOF},
- {"C8C9", calls{"List", "Kind"}, ErrElemTooLarge},
- {"C3C2010201", calls{"List", "List", "Uint", "Uint", "ListEnd", "Uint"}, EOL},
- {"00", calls{"ListEnd"}, errNotInList},
- {"C40102", calls{"List", "Uint", "ListEnd"}, errNotAtEOL},
+ {"C0", calls{"Bytes"}, nil, ErrExpectedString},
+ {"C0", calls{"Uint"}, nil, ErrExpectedString},
+ {"89000000000000000001", calls{"Uint"}, nil, errUintOverflow},
+ {"00", calls{"List"}, nil, ErrExpectedList},
+ {"80", calls{"List"}, nil, ErrExpectedList},
+ {"C0", calls{"List", "Uint"}, nil, EOL},
+ {"C8C9010101010101010101", calls{"List", "Kind"}, nil, ErrElemTooLarge},
+ {"C3C2010201", calls{"List", "List", "Uint", "Uint", "ListEnd", "Uint"}, nil, EOL},
+ {"00", calls{"ListEnd"}, nil, errNotInList},
+ {"C401020304", calls{"List", "Uint", "ListEnd"}, nil, errNotAtEOL},
+
+ // Non-canonical integers (e.g. leading zero bytes).
+ {"00", calls{"Uint"}, nil, ErrCanonInt},
+ {"820002", calls{"Uint"}, nil, ErrCanonInt},
+ {"8133", calls{"Uint"}, nil, ErrCanonSize},
+ {"8156", calls{"Uint"}, nil, nil},
+
+ // Size tags must use the smallest possible encoding.
+ // Leading zero bytes in the size tag are also rejected.
+ {"8100", calls{"Uint"}, nil, ErrCanonSize},
+ {"8100", calls{"Bytes"}, nil, ErrCanonSize},
+ {"B800", calls{"Kind"}, withoutInputLimit, ErrCanonSize},
+ {"B90000", calls{"Kind"}, withoutInputLimit, ErrCanonSize},
+ {"B90055", calls{"Kind"}, withoutInputLimit, ErrCanonSize},
+ {"BA0002FFFF", calls{"Bytes"}, withoutInputLimit, ErrCanonSize},
+ {"F800", calls{"Kind"}, withoutInputLimit, ErrCanonSize},
+ {"F90000", calls{"Kind"}, withoutInputLimit, ErrCanonSize},
+ {"F90055", calls{"Kind"}, withoutInputLimit, ErrCanonSize},
+ {"FA0002FFFF", calls{"List"}, withoutInputLimit, ErrCanonSize},
+
+ // Expected EOF
+ {"", calls{"Kind"}, nil, io.EOF},
+ {"", calls{"Uint"}, nil, io.EOF},
+ {"", calls{"List"}, nil, io.EOF},
+ {"8158", calls{"Uint", "Uint"}, nil, io.EOF},
+ {"C0", calls{"List", "ListEnd", "List"}, nil, io.EOF},
+
+ // Input limit errors.
+ {"81", calls{"Bytes"}, nil, ErrValueTooLarge},
+ {"81", calls{"Uint"}, nil, ErrValueTooLarge},
+ {"81", calls{"Raw"}, nil, ErrValueTooLarge},
+ {"BFFFFFFFFFFFFFFFFFFF", calls{"Bytes"}, nil, ErrValueTooLarge},
+ {"C801", calls{"List"}, nil, ErrValueTooLarge},
+
+ // Test for list element size check overflow.
+ {"CD04040404FFFFFFFFFFFFFFFFFF0303", calls{"List", "Uint", "Uint", "Uint", "Uint", "List"}, nil, ErrElemTooLarge},
+
+ // Test for input limit overflow. Since we are counting the limit
+ // down toward zero in Stream.remaining, reading too far can overflow
+ // remaining to a large value, effectively disabling the limit.
+ {"C40102030401", calls{"Raw", "Uint"}, withCustomInputLimit(5), io.EOF},
+ {"C4010203048158", calls{"Raw", "Uint"}, withCustomInputLimit(6), ErrValueTooLarge},
+
+ // Check that the same calls are fine without a limit.
+ {"C40102030401", calls{"Raw", "Uint"}, withoutInputLimit, nil},
+ {"C4010203048158", calls{"Raw", "Uint"}, withoutInputLimit, nil},
+
+ // Unexpected EOF. This only happens when there is
+ // no input limit, so the reader needs to be 'dumbed down'.
+ {"81", calls{"Bytes"}, withoutInputLimit, io.ErrUnexpectedEOF},
+ {"81", calls{"Uint"}, withoutInputLimit, io.ErrUnexpectedEOF},
+ {"BFFFFFFFFFFFFFFF", calls{"Bytes"}, withoutInputLimit, io.ErrUnexpectedEOF},
+ {"C801", calls{"List", "Uint", "Uint"}, withoutInputLimit, io.ErrUnexpectedEOF},
// This test verifies that the input position is advanced
// correctly when calling Bytes for empty strings. Kind can be called
@@ -109,12 +161,15 @@ func TestStreamErrors(t *testing.T) {
"Bytes", // past final element
"Bytes", // this one should fail
- }, EOL},
+ }, nil, EOL},
}
testfor:
for i, test := range tests {
- s := NewStream(bytes.NewReader(unhex(test.string)))
+ if test.newStream == nil {
+ test.newStream = func(b []byte) *Stream { return NewStream(bytes.NewReader(b), 0) }
+ }
+ s := test.newStream(unhex(test.string))
rs := reflect.ValueOf(s)
for j, call := range test.calls {
fval := rs.MethodByName(call)
@@ -124,11 +179,17 @@ testfor:
err = lastret.(error).Error()
}
if j == len(test.calls)-1 {
- if err != test.error.Error() {
- t.Errorf("test %d: last call (%s) error mismatch\ngot: %s\nwant: %v",
+ want := "<nil>"
+ if test.error != nil {
+ want = test.error.Error()
+ }
+ if err != want {
+ t.Log(test)
+ t.Errorf("test %d: last call (%s) error mismatch\ngot: %s\nwant: %s",
i, call, err, test.error)
}
} else if err != "<nil>" {
+ t.Log(test)
t.Errorf("test %d: call %d (%s) unexpected error: %q", i, j, call, err)
continue testfor
}
@@ -137,7 +198,7 @@ testfor:
}
func TestStreamList(t *testing.T) {
- s := NewStream(bytes.NewReader(unhex("C80102030405060708")))
+ s := NewStream(bytes.NewReader(unhex("C80102030405060708")), 0)
len, err := s.List()
if err != nil {
@@ -166,7 +227,7 @@ func TestStreamList(t *testing.T) {
}
func TestStreamRaw(t *testing.T) {
- s := NewStream(bytes.NewReader(unhex("C58401010101")))
+ s := NewStream(bytes.NewReader(unhex("C58401010101")), 0)
s.List()
want := unhex("8401010101")
@@ -219,7 +280,7 @@ type simplestruct struct {
type recstruct struct {
I uint
- Child *recstruct
+ Child *recstruct `rlp:"nil"`
}
var (
@@ -229,78 +290,58 @@ var (
)
)
-var (
- sharedByteArray [5]byte
- sharedPtr = new(*uint)
-)
-
var decodeTests = []decodeTest{
// integers
{input: "05", ptr: new(uint32), value: uint32(5)},
{input: "80", ptr: new(uint32), value: uint32(0)},
- {input: "8105", ptr: new(uint32), value: uint32(5)},
{input: "820505", ptr: new(uint32), value: uint32(0x0505)},
{input: "83050505", ptr: new(uint32), value: uint32(0x050505)},
{input: "8405050505", ptr: new(uint32), value: uint32(0x05050505)},
{input: "850505050505", ptr: new(uint32), error: "rlp: input string too long for uint32"},
{input: "C0", ptr: new(uint32), error: "rlp: expected input string or byte for uint32"},
+ {input: "00", ptr: new(uint32), error: "rlp: non-canonical integer (leading zero bytes) for uint32"},
+ {input: "8105", ptr: new(uint32), error: "rlp: non-canonical size information for uint32"},
+ {input: "820004", ptr: new(uint32), error: "rlp: non-canonical integer (leading zero bytes) for uint32"},
+ {input: "B8020004", ptr: new(uint32), error: "rlp: non-canonical size information for uint32"},
// slices
{input: "C0", ptr: new([]uint), value: []uint{}},
{input: "C80102030405060708", ptr: new([]uint), value: []uint{1, 2, 3, 4, 5, 6, 7, 8}},
+ {input: "F8020004", ptr: new([]uint), error: "rlp: non-canonical size information for []uint"},
// arrays
- {input: "C0", ptr: new([5]uint), value: [5]uint{}},
{input: "C50102030405", ptr: new([5]uint), value: [5]uint{1, 2, 3, 4, 5}},
+ {input: "C0", ptr: new([5]uint), error: "rlp: input list has too few elements for [5]uint"},
+ {input: "C102", ptr: new([5]uint), error: "rlp: input list has too few elements for [5]uint"},
{input: "C6010203040506", ptr: new([5]uint), error: "rlp: input list has too many elements for [5]uint"},
+ {input: "F8020004", ptr: new([5]uint), error: "rlp: non-canonical size information for [5]uint"},
+
+ // zero sized arrays
+ {input: "C0", ptr: new([0]uint), value: [0]uint{}},
+ {input: "C101", ptr: new([0]uint), error: "rlp: input list has too many elements for [0]uint"},
// byte slices
{input: "01", ptr: new([]byte), value: []byte{1}},
{input: "80", ptr: new([]byte), value: []byte{}},
{input: "8D6162636465666768696A6B6C6D", ptr: new([]byte), value: []byte("abcdefghijklm")},
- {input: "C0", ptr: new([]byte), value: []byte{}},
- {input: "C3010203", ptr: new([]byte), value: []byte{1, 2, 3}},
-
- {
- input: "C3820102",
- ptr: new([]byte),
- error: "rlp: input string too long for uint8, decoding into ([]uint8)[0]",
- },
+ {input: "C0", ptr: new([]byte), error: "rlp: expected input string or byte for []uint8"},
+ {input: "8105", ptr: new([]byte), error: "rlp: non-canonical size information for []uint8"},
// byte arrays
- {input: "01", ptr: new([5]byte), value: [5]byte{1}},
- {input: "80", ptr: new([5]byte), value: [5]byte{}},
+ {input: "02", ptr: new([1]byte), value: [1]byte{2}},
{input: "850102030405", ptr: new([5]byte), value: [5]byte{1, 2, 3, 4, 5}},
- {input: "C0", ptr: new([5]byte), value: [5]byte{}},
- {input: "C3010203", ptr: new([5]byte), value: [5]byte{1, 2, 3, 0, 0}},
- {
- input: "C3820102",
- ptr: new([5]byte),
- error: "rlp: input string too long for uint8, decoding into ([5]uint8)[0]",
- },
- {
- input: "86010203040506",
- ptr: new([5]byte),
- error: "rlp: input string too long for [5]uint8",
- },
- {
- input: "850101",
- ptr: new([5]byte),
- error: io.ErrUnexpectedEOF.Error(),
- },
-
- // byte array reuse (should be zeroed)
- {input: "850102030405", ptr: &sharedByteArray, value: [5]byte{1, 2, 3, 4, 5}},
- {input: "8101", ptr: &sharedByteArray, value: [5]byte{1}}, // kind: String
- {input: "850102030405", ptr: &sharedByteArray, value: [5]byte{1, 2, 3, 4, 5}},
- {input: "01", ptr: &sharedByteArray, value: [5]byte{1}}, // kind: Byte
- {input: "C3010203", ptr: &sharedByteArray, value: [5]byte{1, 2, 3, 0, 0}},
- {input: "C101", ptr: &sharedByteArray, value: [5]byte{1}}, // kind: List
+ // byte array errors
+ {input: "02", ptr: new([5]byte), error: "rlp: input string too short for [5]uint8"},
+ {input: "80", ptr: new([5]byte), error: "rlp: input string too short for [5]uint8"},
+ {input: "820000", ptr: new([5]byte), error: "rlp: input string too short for [5]uint8"},
+ {input: "C0", ptr: new([5]byte), error: "rlp: expected input string or byte for [5]uint8"},
+ {input: "C3010203", ptr: new([5]byte), error: "rlp: expected input string or byte for [5]uint8"},
+ {input: "86010203040506", ptr: new([5]byte), error: "rlp: input string too long for [5]uint8"},
+ {input: "8105", ptr: new([1]byte), error: "rlp: non-canonical size information for [1]uint8"},
// zero sized byte arrays
{input: "80", ptr: new([0]byte), value: [0]byte{}},
- {input: "C0", ptr: new([0]byte), value: [0]byte{}},
{input: "01", ptr: new([0]byte), error: "rlp: input string too long for [0]uint8"},
{input: "8101", ptr: new([0]byte), error: "rlp: input string too long for [0]uint8"},
@@ -312,20 +353,44 @@ var decodeTests = []decodeTest{
// big ints
{input: "01", ptr: new(*big.Int), value: big.NewInt(1)},
{input: "89FFFFFFFFFFFFFFFFFF", ptr: new(*big.Int), value: veryBigInt},
- {input: "820001", ptr: new(big.Int), error: "rlp: canon int error appends zero's for *big.Int"},
{input: "10", ptr: new(big.Int), value: *big.NewInt(16)}, // non-pointer also works
{input: "C0", ptr: new(*big.Int), error: "rlp: expected input string or byte for *big.Int"},
+ {input: "820001", ptr: new(big.Int), error: "rlp: non-canonical integer (leading zero bytes) for *big.Int"},
+ {input: "8105", ptr: new(big.Int), error: "rlp: non-canonical size information for *big.Int"},
// structs
- {input: "C0", ptr: new(simplestruct), value: simplestruct{0, ""}},
- {input: "C105", ptr: new(simplestruct), value: simplestruct{5, ""}},
- {input: "C50583343434", ptr: new(simplestruct), value: simplestruct{5, "444"}},
{
- input: "C501C302C103",
+ input: "C50583343434",
+ ptr: new(simplestruct),
+ value: simplestruct{5, "444"},
+ },
+ {
+ input: "C601C402C203C0",
ptr: new(recstruct),
value: recstruct{1, &recstruct{2, &recstruct{3, nil}}},
},
+ // struct errors
+ {
+ input: "C0",
+ ptr: new(simplestruct),
+ error: "rlp: too few elements for rlp.simplestruct",
+ },
+ {
+ input: "C105",
+ ptr: new(simplestruct),
+ error: "rlp: too few elements for rlp.simplestruct",
+ },
+ {
+ input: "C7C50583343434C0",
+ ptr: new([]*simplestruct),
+ error: "rlp: too few elements for rlp.simplestruct, decoding into ([]*rlp.simplestruct)[1]",
+ },
+ {
+ input: "83222222",
+ ptr: new(simplestruct),
+ error: "rlp: expected input list for rlp.simplestruct",
+ },
{
input: "C3010101",
ptr: new(simplestruct),
@@ -338,20 +403,16 @@ var decodeTests = []decodeTest{
},
// pointers
- {input: "00", ptr: new(*uint), value: (*uint)(nil)},
- {input: "80", ptr: new(*uint), value: (*uint)(nil)},
- {input: "C0", ptr: new(*uint), value: (*uint)(nil)},
+ {input: "00", ptr: new(*[]byte), value: &[]byte{0}},
+ {input: "80", ptr: new(*uint), value: uintp(0)},
+ {input: "C0", ptr: new(*uint), error: "rlp: expected input string or byte for uint"},
{input: "07", ptr: new(*uint), value: uintp(7)},
- {input: "8108", ptr: new(*uint), value: uintp(8)},
+ {input: "8158", ptr: new(*uint), value: uintp(0x58)},
{input: "C109", ptr: new(*[]uint), value: &[]uint{9}},
{input: "C58403030303", ptr: new(*[][]byte), value: &[][]byte{{3, 3, 3, 3}}},
// check that input position is advanced also for empty values.
- {input: "C3808005", ptr: new([]*uint), value: []*uint{nil, nil, uintp(5)}},
-
- // pointer should be reset to nil
- {input: "05", ptr: sharedPtr, value: uintp(5)},
- {input: "80", ptr: sharedPtr, value: (*uint)(nil)},
+ {input: "C3808005", ptr: new([]*uint), value: []*uint{uintp(0), uintp(0), uintp(5)}},
// interface{}
{input: "00", ptr: new(interface{}), value: []byte{0}},
@@ -401,11 +462,17 @@ func TestDecodeWithByteReader(t *testing.T) {
})
}
-// dumbReader reads from a byte slice but does not
-// implement ReadByte.
-type dumbReader []byte
+// plainReader reads from a byte slice but does not
+// implement ReadByte. It is also not recognized by the
+// size validation. This is useful to test how the decoder
+// behaves on a non-buffered input stream.
+type plainReader []byte
+
+func newPlainReader(b []byte) io.Reader {
+ return (*plainReader)(&b)
+}
-func (r *dumbReader) Read(buf []byte) (n int, err error) {
+func (r *plainReader) Read(buf []byte) (n int, err error) {
if len(*r) == 0 {
return 0, io.EOF
}
@@ -416,15 +483,14 @@ func (r *dumbReader) Read(buf []byte) (n int, err error) {
func TestDecodeWithNonByteReader(t *testing.T) {
runTests(t, func(input []byte, into interface{}) error {
- r := dumbReader(input)
- return Decode(&r, into)
+ return Decode(newPlainReader(input), into)
})
}
func TestDecodeStreamReset(t *testing.T) {
- s := NewStream(nil)
+ s := NewStream(nil, 0)
runTests(t, func(input []byte, into interface{}) error {
- s.Reset(bytes.NewReader(input))
+ s.Reset(bytes.NewReader(input), 0)
return s.Decode(into)
})
}
@@ -516,9 +582,36 @@ func ExampleDecode() {
// Decoded value: rlp.example{A:0xa, B:0x14, private:0x0, String:"foobar"}
}
+func ExampleDecode_structTagNil() {
+ // In this example, we'll use the "nil" struct tag to change
+ // how a pointer-typed field is decoded. The input contains an RLP
+ // list of one element, an empty string.
+ input := []byte{0xC1, 0x80}
+
+ // This type uses the normal rules.
+ // The empty input string is decoded as a pointer to an empty Go string.
+ var normalRules struct {
+ String *string
+ }
+ Decode(bytes.NewReader(input), &normalRules)
+ fmt.Printf("normal: String = %q\n", *normalRules.String)
+
+ // This type uses the struct tag.
+ // The empty input string is decoded as a nil pointer.
+ var withEmptyOK struct {
+ String *string `rlp:"nil"`
+ }
+ Decode(bytes.NewReader(input), &withEmptyOK)
+ fmt.Printf("with nil tag: String = %v\n", withEmptyOK.String)
+
+ // Output:
+ // normal: String = ""
+ // with nil tag: String = <nil>
+}
+
func ExampleStream() {
input, _ := hex.DecodeString("C90A1486666F6F626172")
- s := NewStream(bytes.NewReader(input))
+ s := NewStream(bytes.NewReader(input), 0)
// Check what kind of value lies ahead
kind, size, _ := s.Kind()