Skip to content

Commit 088da24

Browse files
authored
rlp: improve decoder stream implementation (ethereum#22858)
This commit makes various cleanup changes to rlp.Stream. * rlp: shrink Stream struct This removes a lot of unused padding space in Stream by reordering the fields. The size of Stream changes from 120 bytes to 88 bytes. Stream instances are internally cached and reused using sync.Pool, so this does not improve performance. * rlp: simplify list stack The list stack kept track of the size of the current list context as well as the current offset into it. The size had to be stored in the stack in order to subtract it from the remaining bytes of any enclosing list in ListEnd. It seems that this can be implemented in a simpler way: just subtract the size from the enclosing list context in List instead.
1 parent 3e6f46c commit 088da24

File tree

1 file changed

+86
-89
lines changed

1 file changed

+86
-89
lines changed

rlp/decode.go

+86-89
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ func decodeDecoder(s *Stream, val reflect.Value) error {
518518
}
519519

520520
// Kind represents the kind of value contained in an RLP stream.
521-
type Kind int
521+
type Kind int8
522522

523523
const (
524524
Byte Kind = iota
@@ -561,22 +561,16 @@ type ByteReader interface {
561561
type Stream struct {
562562
r ByteReader
563563

564-
// number of bytes remaining to be read from r.
565-
remaining uint64
566-
limited bool
567-
568-
// auxiliary buffer for integer decoding
569-
uintbuf []byte
570-
571-
kind Kind // kind of value ahead
572-
size uint64 // size of value ahead
573-
byteval byte // value of single byte in type tag
574-
kinderr error // error from last readKind
575-
stack []listpos
564+
remaining uint64 // number of bytes remaining to be read from r
565+
size uint64 // size of value ahead
566+
kinderr error // error from last readKind
567+
stack []uint64 // list sizes
568+
uintbuf [8]byte // auxiliary buffer for integer decoding
569+
kind Kind // kind of value ahead
570+
byteval byte // value of single byte in type tag
571+
limited bool // true if input limit is in effect
576572
}
577573

578-
type listpos struct{ pos, size uint64 }
579-
580574
// NewStream creates a new decoding stream reading from r.
581575
//
582576
// If r implements the ByteReader interface, Stream will
@@ -646,8 +640,8 @@ func (s *Stream) Raw() ([]byte, error) {
646640
s.kind = -1 // rearm Kind
647641
return []byte{s.byteval}, nil
648642
}
649-
// the original header has already been read and is no longer
650-
// available. read content and put a new header in front of it.
643+
// The original header has already been read and is no longer
644+
// available. Read content and put a new header in front of it.
651645
start := headsize(size)
652646
buf := make([]byte, uint64(start)+size)
653647
if err := s.readFull(buf[start:]); err != nil {
@@ -730,7 +724,14 @@ func (s *Stream) List() (size uint64, err error) {
730724
if kind != List {
731725
return 0, ErrExpectedList
732726
}
733-
s.stack = append(s.stack, listpos{0, size})
727+
728+
// Remove size of inner list from outer list before pushing the new size
729+
// onto the stack. This ensures that the remaining outer list size will
730+
// be correct after the matching call to ListEnd.
731+
if inList, limit := s.listLimit(); inList {
732+
s.stack[len(s.stack)-1] = limit - size
733+
}
734+
s.stack = append(s.stack, size)
734735
s.kind = -1
735736
s.size = 0
736737
return size, nil
@@ -739,17 +740,13 @@ func (s *Stream) List() (size uint64, err error) {
739740
// ListEnd returns to the enclosing list.
740741
// The input reader must be positioned at the end of a list.
741742
func (s *Stream) ListEnd() error {
742-
if len(s.stack) == 0 {
743+
// Ensure that no more data is remaining in the current list.
744+
if inList, listLimit := s.listLimit(); !inList {
743745
return errNotInList
744-
}
745-
tos := s.stack[len(s.stack)-1]
746-
if tos.pos != tos.size {
746+
} else if listLimit > 0 {
747747
return errNotAtEOL
748748
}
749749
s.stack = s.stack[:len(s.stack)-1] // pop
750-
if len(s.stack) > 0 {
751-
s.stack[len(s.stack)-1].pos += tos.size
752-
}
753750
s.kind = -1
754751
s.size = 0
755752
return nil
@@ -777,7 +774,7 @@ func (s *Stream) Decode(val interface{}) error {
777774

778775
err = decoder(s, rval.Elem())
779776
if decErr, ok := err.(*decodeError); ok && len(decErr.ctx) > 0 {
780-
// add decode target type to error so context has more meaning
777+
// Add decode target type to error so context has more meaning.
781778
decErr.ctx = append(decErr.ctx, fmt.Sprint("(", rtyp.Elem(), ")"))
782779
}
783780
return err
@@ -800,6 +797,9 @@ func (s *Stream) Reset(r io.Reader, inputLimit uint64) {
800797
case *bytes.Reader:
801798
s.remaining = uint64(br.Len())
802799
s.limited = true
800+
case *bytes.Buffer:
801+
s.remaining = uint64(br.Len())
802+
s.limited = true
803803
case *strings.Reader:
804804
s.remaining = uint64(br.Len())
805805
s.limited = true
@@ -818,10 +818,8 @@ func (s *Stream) Reset(r io.Reader, inputLimit uint64) {
818818
s.size = 0
819819
s.kind = -1
820820
s.kinderr = nil
821-
if s.uintbuf == nil {
822-
s.uintbuf = make([]byte, 8)
823-
}
824821
s.byteval = 0
822+
s.uintbuf = [8]byte{}
825823
}
826824

827825
// Kind returns the kind and size of the next value in the
@@ -836,35 +834,29 @@ func (s *Stream) Reset(r io.Reader, inputLimit uint64) {
836834
// the value. Subsequent calls to Kind (until the value is decoded)
837835
// will not advance the input reader and return cached information.
838836
func (s *Stream) Kind() (kind Kind, size uint64, err error) {
839-
var tos *listpos
840-
if len(s.stack) > 0 {
841-
tos = &s.stack[len(s.stack)-1]
842-
}
843-
if s.kind < 0 {
844-
s.kinderr = nil
845-
// Don't read further if we're at the end of the
846-
// innermost list.
847-
if tos != nil && tos.pos == tos.size {
848-
return 0, 0, EOL
849-
}
850-
s.kind, s.size, s.kinderr = s.readKind()
851-
if s.kinderr == nil {
852-
if tos == nil {
853-
// At toplevel, check that the value is smaller
854-
// than the remaining input length.
855-
if s.limited && s.size > s.remaining {
856-
s.kinderr = ErrValueTooLarge
857-
}
858-
} else {
859-
// Inside a list, check that the value doesn't overflow the list.
860-
if s.size > tos.size-tos.pos {
861-
s.kinderr = ErrElemTooLarge
862-
}
863-
}
837+
if s.kind >= 0 {
838+
return s.kind, s.size, s.kinderr
839+
}
840+
841+
// Check for end of list. This needs to be done here because readKind
842+
// checks against the list size, and would return the wrong error.
843+
inList, listLimit := s.listLimit()
844+
if inList && listLimit == 0 {
845+
return 0, 0, EOL
846+
}
847+
// Read the actual size tag.
848+
s.kind, s.size, s.kinderr = s.readKind()
849+
if s.kinderr == nil {
850+
// Check the data size of the value ahead against input limits. This
851+
// is done here because many decoders require allocating an input
852+
// buffer matching the value size. Checking it here protects those
853+
// decoders from inputs declaring very large value size.
854+
if inList && s.size > listLimit {
855+
s.kinderr = ErrElemTooLarge
856+
} else if s.limited && s.size > s.remaining {
857+
s.kinderr = ErrValueTooLarge
864858
}
865859
}
866-
// Note: this might return a sticky error generated
867-
// by an earlier call to readKind.
868860
return s.kind, s.size, s.kinderr
869861
}
870862

@@ -891,37 +883,35 @@ func (s *Stream) readKind() (kind Kind, size uint64, err error) {
891883
s.byteval = b
892884
return Byte, 0, nil
893885
case b < 0xB8:
894-
// Otherwise, if a string is 0-55 bytes long,
895-
// the RLP encoding consists of a single byte with value 0x80 plus the
896-
// length of the string followed by the string. The range of the first
897-
// byte is thus [0x80, 0xB7].
886+
// Otherwise, if a string is 0-55 bytes long, the RLP encoding consists
887+
// of a single byte with value 0x80 plus the length of the string
888+
// followed by the string. The range of the first byte is thus [0x80, 0xB7].
898889
return String, uint64(b - 0x80), nil
899890
case b < 0xC0:
900-
// If a string is more than 55 bytes long, the
901-
// RLP encoding consists of a single byte with value 0xB7 plus the length
902-
// of the length of the string in binary form, followed by the length of
903-
// the string, followed by the string. For example, a length-1024 string
904-
// would be encoded as 0xB90400 followed by the string. The range of
905-
// the first byte is thus [0xB8, 0xBF].
891+
// If a string is more than 55 bytes long, the RLP encoding consists of a
892+
// single byte with value 0xB7 plus the length of the length of the
893+
// string in binary form, followed by the length of the string, followed
894+
// by the string. For example, a length-1024 string would be encoded as
895+
// 0xB90400 followed by the string. The range of the first byte is thus
896+
// [0xB8, 0xBF].
906897
size, err = s.readUint(b - 0xB7)
907898
if err == nil && size < 56 {
908899
err = ErrCanonSize
909900
}
910901
return String, size, err
911902
case b < 0xF8:
912-
// If the total payload of a list
913-
// (i.e. the combined length of all its items) is 0-55 bytes long, the
914-
// RLP encoding consists of a single byte with value 0xC0 plus the length
915-
// of the list followed by the concatenation of the RLP encodings of the
916-
// items. The range of the first byte is thus [0xC0, 0xF7].
903+
// If the total payload of a list (i.e. the combined length of all its
904+
// items) is 0-55 bytes long, the RLP encoding consists of a single byte
905+
// with value 0xC0 plus the length of the list followed by the
906+
// concatenation of the RLP encodings of the items. The range of the
907+
// first byte is thus [0xC0, 0xF7].
917908
return List, uint64(b - 0xC0), nil
918909
default:
919-
// If the total payload of a list is more than 55 bytes long,
920-
// the RLP encoding consists of a single byte with value 0xF7
921-
// plus the length of the length of the payload in binary
922-
// form, followed by the length of the payload, followed by
923-
// the concatenation of the RLP encodings of the items. The
924-
// range of the first byte is thus [0xF8, 0xFF].
910+
// If the total payload of a list is more than 55 bytes long, the RLP
911+
// encoding consists of a single byte with value 0xF7 plus the length of
912+
// the length of the payload in binary form, followed by the length of
913+
// the payload, followed by the concatenation of the RLP encodings of
914+
// the items. The range of the first byte is thus [0xF8, 0xFF].
925915
size, err = s.readUint(b - 0xF7)
926916
if err == nil && size < 56 {
927917
err = ErrCanonSize
@@ -940,22 +930,20 @@ func (s *Stream) readUint(size byte) (uint64, error) {
940930
return uint64(b), err
941931
default:
942932
start := int(8 - size)
943-
for i := 0; i < start; i++ {
944-
s.uintbuf[i] = 0
945-
}
933+
s.uintbuf = [8]byte{}
946934
if err := s.readFull(s.uintbuf[start:]); err != nil {
947935
return 0, err
948936
}
949937
if s.uintbuf[start] == 0 {
950-
// Note: readUint is also used to decode integer
951-
// values. The error needs to be adjusted to become
952-
// ErrCanonInt in this case.
938+
// Note: readUint is also used to decode integer values.
939+
// The error needs to be adjusted to become ErrCanonInt in this case.
953940
return 0, ErrCanonSize
954941
}
955-
return binary.BigEndian.Uint64(s.uintbuf), nil
942+
return binary.BigEndian.Uint64(s.uintbuf[:]), nil
956943
}
957944
}
958945

946+
// readFull reads into buf from the underlying stream.
959947
func (s *Stream) readFull(buf []byte) (err error) {
960948
if err := s.willRead(uint64(len(buf))); err != nil {
961949
return err
@@ -977,6 +965,7 @@ func (s *Stream) readFull(buf []byte) (err error) {
977965
return err
978966
}
979967

968+
// readByte reads a single byte from the underlying stream.
980969
func (s *Stream) readByte() (byte, error) {
981970
if err := s.willRead(1); err != nil {
982971
return 0, err
@@ -988,16 +977,16 @@ func (s *Stream) readByte() (byte, error) {
988977
return b, err
989978
}
990979

980+
// willRead is called before any read from the underlying stream. It checks
981+
// n against size limits, and updates the limits if n doesn't overflow them.
991982
func (s *Stream) willRead(n uint64) error {
992983
s.kind = -1 // rearm Kind
993984

994-
if len(s.stack) > 0 {
995-
// check list overflow
996-
tos := s.stack[len(s.stack)-1]
997-
if n > tos.size-tos.pos {
985+
if inList, limit := s.listLimit(); inList {
986+
if n > limit {
998987
return ErrElemTooLarge
999988
}
1000-
s.stack[len(s.stack)-1].pos += n
989+
s.stack[len(s.stack)-1] = limit - n
1001990
}
1002991
if s.limited {
1003992
if n > s.remaining {
@@ -1007,3 +996,11 @@ func (s *Stream) willRead(n uint64) error {
1007996
}
1008997
return nil
1009998
}
999+
1000+
// listLimit returns the amount of data remaining in the innermost list.
1001+
func (s *Stream) listLimit() (inList bool, limit uint64) {
1002+
if len(s.stack) == 0 {
1003+
return false, 0
1004+
}
1005+
return true, s.stack[len(s.stack)-1]
1006+
}

0 commit comments

Comments
 (0)