erigon-pulse/rlp/parse.go

169 lines
5.1 KiB
Go
Raw Normal View History

2021-07-27 09:53:55 +00:00
/*
Copyright 2021 Erigon contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
2021-07-27 05:03:59 +00:00
package rlp
import (
"fmt"
"github.com/holiman/uint256"
)
2021-07-27 05:18:40 +00:00
// BeInt parses Big Endian representation of an integer from given payload at given position
func BeInt(payload []byte, pos, length int) (int, error) {
2021-07-27 05:03:59 +00:00
var r int
if length > 0 && payload[pos] == 0 {
return 0, fmt.Errorf("integer encoding for RLP must not have leading zeros: %x", payload[pos:pos+length])
}
for _, b := range payload[pos : pos+length] {
r = (r << 8) | int(b)
}
return r, nil
}
2021-07-27 08:47:33 +00:00
// Prefix parses RLP Prefix from given payload at given position. It returns the offset and length of the RLP element
2021-07-27 05:03:59 +00:00
// as well as the indication of whether it is a list of string
2021-07-27 08:47:33 +00:00
func Prefix(payload []byte, pos int) (dataPos int, dataLen int, isList bool, err error) {
2021-07-27 05:03:59 +00:00
switch first := payload[pos]; {
case first < 128:
dataPos = pos
2021-07-27 05:03:59 +00:00
dataLen = 1
2021-07-27 05:18:40 +00:00
isList = false
2021-07-27 05:03:59 +00:00
case first < 184:
// string of len < 56, and it is non-legacy transaction
dataPos = pos + 1
2021-07-27 05:03:59 +00:00
dataLen = int(first) - 128
2021-07-27 05:18:40 +00:00
isList = false
2021-07-27 05:03:59 +00:00
case first < 192:
// string of len >= 56, and it is non-legacy transaction
beLen := int(first) - 183
dataPos = pos + 1 + beLen
2021-07-27 05:18:40 +00:00
dataLen, err = BeInt(payload, pos+1, beLen)
isList = false
2021-07-27 05:03:59 +00:00
case first < 248:
2021-07-27 05:18:40 +00:00
// isList of len < 56, and it is a legacy transaction
dataPos = pos + 1
2021-07-27 05:03:59 +00:00
dataLen = int(first) - 192
2021-07-27 05:18:40 +00:00
isList = true
2021-07-27 05:03:59 +00:00
default:
2021-07-27 05:18:40 +00:00
// isList of len >= 56, and it is a legacy transaction
2021-07-27 05:03:59 +00:00
beLen := int(first) - 247
dataPos = pos + 1 + beLen
2021-07-27 05:18:40 +00:00
dataLen, err = BeInt(payload, pos+1, beLen)
isList = true
2021-07-27 05:03:59 +00:00
}
if err != nil {
if dataPos+dataLen >= len(payload) {
err = fmt.Errorf("unexpected end of payload")
}
}
2021-07-27 05:03:59 +00:00
return
}
2021-07-27 06:06:38 +00:00
func List(payload []byte, pos int) (dataPos int, dataLen int, err error) {
2021-07-27 08:47:33 +00:00
dataPos, dataLen, isList, err := Prefix(payload, pos)
if err != nil {
return 0, 0, err
}
if !isList {
return 0, 0, fmt.Errorf("must be a list")
}
return
}
2021-07-27 06:06:38 +00:00
func String(payload []byte, pos int) (dataPos int, dataLen int, err error) {
2021-07-27 08:47:33 +00:00
dataPos, dataLen, isList, err := Prefix(payload, pos)
if err != nil {
return 0, 0, err
}
if isList {
return 0, 0, fmt.Errorf("must be a string, instead of a list")
}
return
}
2021-07-27 06:06:38 +00:00
func StringOfLen(payload []byte, pos, expectedLen int) (dataPos int, err error) {
dataPos, dataLen, err := String(payload, pos)
if err != nil {
return 0, err
}
if dataLen != expectedLen {
return 0, fmt.Errorf("expected string of len %d, got %d", expectedLen, dataLen)
}
return
}
2021-07-27 05:18:40 +00:00
// U64 parses uint64 number from given payload at given position
func U64(payload []byte, pos int) (int, uint64, error) {
2021-07-27 08:47:33 +00:00
dataPos, dataLen, isList, err := Prefix(payload, pos)
2021-07-27 05:03:59 +00:00
if err != nil {
return 0, 0, err
}
2021-07-27 05:18:40 +00:00
if isList {
return 0, 0, fmt.Errorf("uint64 must be a string, not isList")
2021-07-27 05:03:59 +00:00
}
if dataLen > 8 {
return 0, 0, fmt.Errorf("uint64 must not be more than 8 bytes long, got %d", dataLen)
}
if dataLen > 0 && payload[dataPos] == 0 {
return 0, 0, fmt.Errorf("integer encoding for RLP must not have leading zeros: %x", payload[dataPos:dataPos+dataLen])
2021-07-27 05:03:59 +00:00
}
var r uint64
for _, b := range payload[dataPos : dataPos+dataLen] {
2021-07-27 05:03:59 +00:00
r = (r << 8) | uint64(b)
}
return dataPos + dataLen, r, nil
2021-07-27 05:03:59 +00:00
}
2021-07-27 05:18:40 +00:00
// U256 parses uint256 number from given payload at given position
func U256(payload []byte, pos int, x *uint256.Int) (int, error) {
2021-07-27 07:28:15 +00:00
dataPos, dataLen, err := String(payload, pos)
2021-07-27 05:03:59 +00:00
if err != nil {
return 0, err
}
if dataLen > 32 {
return 0, fmt.Errorf("uint256 must not be more than 8 bytes long, got %d", dataLen)
}
if dataLen > 0 && payload[dataPos] == 0 {
return 0, fmt.Errorf("integer encoding for RLP must not have leading zeros: %x", payload[dataPos:dataPos+dataLen])
2021-07-27 05:03:59 +00:00
}
x.SetBytes(payload[dataPos : dataPos+dataLen])
return dataPos + dataLen, nil
2021-07-27 05:03:59 +00:00
}
// ParseHash extracts the next hash from the RLP encoding (payload) from a given position.
// It appends the hash to the given slice, reusing the space if there is enough capacity
// The first returned value is the slice where hash is appended to.
// The second returned value is the new position in the RLP payload after the extraction
// of the hash.
func ParseHash(payload []byte, pos int, hashbuf []byte) ([]byte, int, error) {
2021-07-27 07:28:15 +00:00
dataPos, err := StringOfLen(payload, pos, 32)
2021-07-27 05:03:59 +00:00
if err != nil {
return nil, 0, fmt.Errorf("%s: hash len: %w", ParseHashErrorPrefix, err)
}
2021-07-27 05:18:40 +00:00
hashbuf = ensureEnoughSize(hashbuf, 32)
2021-07-27 07:28:15 +00:00
copy(hashbuf, payload[dataPos:dataPos+32])
return hashbuf, dataPos + 32, nil
2021-07-27 05:18:40 +00:00
}
func ensureEnoughSize(in []byte, size int) []byte {
if cap(in) < size {
return make([]byte, size)
2021-07-27 05:03:59 +00:00
}
2021-07-27 05:18:40 +00:00
return in[:size] // Reuse the space if it has enough capacity
2021-07-27 05:03:59 +00:00
}
const ParseHashErrorPrefix = "parse hash payload"