erigon-pulse/rlp.go

165 lines
3.7 KiB
Go

package main
import (
"fmt"
"bytes"
"math"
)
func BinaryLength(n uint64) uint64 {
if n == 0 { return 0 }
return 1 + BinaryLength(n / 256)
}
func ToBinarySlice(n uint64, length uint64) []uint64 {
if length == 0 {
length = BinaryLength(n)
}
if n == 0 { return make([]uint64, 1) }
slice := ToBinarySlice(n / 256, 0)
slice = append(slice, n % 256)
return slice
}
func ToBin(n uint64, length uint64) string {
var buf bytes.Buffer
for _, val := range ToBinarySlice(n, length) {
buf.WriteString(string(val))
}
return buf.String()
}
func FromBin(data []byte) uint64 {
if len(data) == 0 { return 0 }
return FromBin(data[:len(data)-1]) * 256 + uint64(data[len(data)-1])
}
func Decode(data []byte, pos int) (interface{}, int) {
if pos > len(data)-1 {
panic(fmt.Sprintf("index out of range %d for data %q, l = %d", pos, data, len(data)))
}
char := int(data[pos])
slice := make([]interface{}, 0)
switch {
case char < 24:
return data[pos], pos + 1
case char < 56:
b := int(data[pos]) - 23
return FromBin(data[pos+1 : pos+1+b]), pos + 1 + b
case char < 64:
b := int(data[pos]) - 55
b2 := int(FromBin(data[pos+1 : pos+1+b]))
return FromBin(data[pos+1+b : pos+1+b+b2]), pos+1+b+b2
case char < 120:
b := int(data[pos]) - 64
return data[pos+1:pos+1+b], pos+1+b
case char < 128:
b := int(data[pos]) - 119
b2 := int(FromBin(data[pos+1 : pos+1+b]))
return data[pos+1+b : pos+1+b+b2], pos+1+b+b2
case char < 184:
b := int(data[pos]) - 128
pos++
for i := 0; i < b; i++ {
var obj interface{}
obj, pos = Decode(data, pos)
slice = append(slice, obj)
}
return slice, pos
case char < 192:
b := int(data[pos]) - 183
//b2 := int(FromBin(data[pos+1 : pos+1+b])) (ref implementation has an unused variable)
pos = pos+1+b
for i := 0; i < b; i++ {
var obj interface{}
obj, pos = Decode(data, pos)
slice = append(slice, obj)
}
return slice, pos
default:
panic(fmt.Sprintf("byte not supported: %q", char))
}
return slice, 0
}
func Encode(object interface{}) []byte {
var buff bytes.Buffer
switch t := object.(type) {
case uint32, uint64:
var num uint64
if _num, ok := t.(uint64); ok {
num = _num
} else if _num, ok := t.(uint32); ok {
num = uint64(_num)
}
if num >= 0 && num < 24 {
buff.WriteString(string(num))
} else if num <= uint64(math.Pow(2, 256)) {
b := ToBin(num, 0)
buff.WriteString(string(len(b) + 23) + b)
} else {
b := ToBin(num, 0)
b2 := ToBin(uint64(len(b)), 0)
buff.WriteString(string(len(b2) + 55) + b2 + b)
}
case string:
if len(t) < 56 {
buff.WriteString(string(len(t) + 64) + t)
} else {
b2 := ToBin(uint64(len(t)), 0)
buff.WriteString(string(len(b2) + 119) + b2 + t)
}
case []byte:
// Cast the byte slice to a string
buff.Write(Encode(string(t)))
case []interface{}, []string:
// Inline function for writing the slice header
WriteSliceHeader := func(length int) {
if length < 56 {
buff.WriteByte(byte(length + 128))
} else {
b2 := ToBin(uint64(length), 0)
buff.WriteByte(byte(len(b2) + 183))
buff.WriteString(b2)
}
}
// FIXME How can I do this "better"?
if interSlice, ok := t.([]interface{}); ok {
WriteSliceHeader(len(interSlice))
for _, val := range interSlice {
buff.Write(Encode(val))
}
} else if stringSlice, ok := t.([]string); ok {
WriteSliceHeader(len(stringSlice))
for _, val := range stringSlice {
buff.Write(Encode(val))
}
}
}
return buff.Bytes()
}