This repository has been archived on 2024-04-07. You can view files and clone it, but cannot push or open issues or pull requests.
go-monero/pkg/levin/portable_storage.go

411 lines
7.2 KiB
Go

package levin
import (
"encoding/binary"
"fmt"
)
const (
PortableStorageSignatureA uint32 = 0x01011101
PortableStorageSignatureB uint32 = 0x01020101
PortableStorageFormatVersion byte = 0x01
PortableRawSizeMarkMask byte = 0x03
PortableRawSizeMarkByte byte = 0x00
PortableRawSizeMarkWord uint16 = 0x01
PortableRawSizeMarkDword uint32 = 0x02
PortableRawSizeMarkInt64 uint64 = 0x03
)
type Entry struct {
Name string
Serializable Serializable `json:"-,omitempty"`
Value interface{}
}
func (e Entry) String() string {
v, ok := e.Value.(string)
if !ok {
panic(fmt.Errorf("interface couldnt be casted to string"))
}
return v
}
func (e Entry) Uint8() uint8 {
v, ok := e.Value.(uint8)
if !ok {
panic(fmt.Errorf("interface couldnt be casted to uint8"))
}
return v
}
func (e Entry) Uint16() uint16 {
v, ok := e.Value.(uint16)
if !ok {
panic(fmt.Errorf("interface couldnt be casted to uint16"))
}
return v
}
func (e Entry) Uint32() uint32 {
v, ok := e.Value.(uint32)
if !ok {
panic(fmt.Errorf("interface couldnt be casted to uint32"))
}
return v
}
func (e Entry) Uint64() uint64 {
v, ok := e.Value.(uint64)
if !ok {
panic(fmt.Errorf("interface couldnt be casted to uint64"))
}
return v
}
func (e Entry) Entries() Entries {
v, ok := e.Value.(Entries)
if !ok {
panic(fmt.Errorf("interface couldnt be casted to levin.Entries"))
}
return v
}
func (e Entry) Bytes() []byte {
return nil
}
type Entries []Entry
func (e Entries) Bytes() []byte {
return nil
}
type PortableStorage struct {
Entries Entries
}
func NewPortableStorageFromBytes(bytes []byte) (*PortableStorage, error) {
var (
size = 0
idx = 0
)
{ // sig-a
size = 4
if len(bytes[idx:]) < size {
return nil, fmt.Errorf("sig-a out of bounds")
}
sig := binary.LittleEndian.Uint32(bytes[idx : idx+size])
idx += size
if sig != uint32(PortableStorageSignatureA) {
return nil, fmt.Errorf("sig-a doesn't match")
}
}
{ // sig-b
size = 4
sig := binary.LittleEndian.Uint32(bytes[idx : idx+size])
idx += size
if sig != uint32(PortableStorageSignatureB) {
return nil, fmt.Errorf("sig-b doesn't match")
}
}
{ // format ver
size = 1
version := bytes[idx]
idx += size
if version != PortableStorageFormatVersion {
return nil, fmt.Errorf("version doesn't match")
}
}
ps := &PortableStorage{}
_, ps.Entries = ReadObject(bytes[idx:])
return ps, nil
}
func ReadString(bytes []byte) (int, string) {
idx := 0
n, strLen := ReadVarInt(bytes)
idx += n
return idx + strLen, string(bytes[idx : idx+strLen])
}
func ReadObject(bytes []byte) (int, Entries) {
idx := 0
n, i := ReadVarInt(bytes[idx:])
idx += n
entries := make(Entries, i)
for iter := 0; iter < i; iter++ {
entries[iter] = Entry{}
entry := &entries[iter]
lenName := int(bytes[idx])
idx += 1
entry.Name = string(bytes[idx : idx+lenName])
idx += lenName
ttype := bytes[idx]
idx += 1
n, obj := ReadAny(bytes[idx:], ttype)
idx += n
entry.Value = obj
}
return idx, entries
}
func ReadArray(ttype byte, bytes []byte) (int, Entries) {
var (
idx = 0
n = 0
)
n, i := ReadVarInt(bytes[idx:])
idx += n
entries := make(Entries, i)
for iter := 0; iter < i; iter++ {
n, obj := ReadAny(bytes[idx:], ttype)
idx += n
entries[iter] = Entry{
Value: obj,
}
}
return idx, entries
}
func ReadAny(bytes []byte, ttype byte) (int, interface{}) {
var (
idx = 0
n = 0
)
if ttype&BoostSerializeFlagArray != 0 {
internalType := ttype &^ BoostSerializeFlagArray
n, obj := ReadArray(internalType, bytes[idx:])
idx += n
return idx, obj
}
if ttype == BoostSerializeTypeObject {
n, obj := ReadObject(bytes[idx:])
idx += n
return idx, obj
}
if ttype == BoostSerializeTypeUint8 {
obj := uint8(bytes[idx])
n += 1
idx += n
return idx, obj
}
if ttype == BoostSerializeTypeUint16 {
obj := binary.LittleEndian.Uint16(bytes[idx:])
n += 2
idx += n
return idx, obj
}
if ttype == BoostSerializeTypeUint32 {
obj := binary.LittleEndian.Uint32(bytes[idx:])
n += 4
idx += n
return idx, obj
}
if ttype == BoostSerializeTypeUint64 {
obj := binary.LittleEndian.Uint64(bytes[idx:])
n += 8
idx += n
return idx, obj
}
if ttype == BoostSerializeTypeInt64 {
obj := binary.LittleEndian.Uint64(bytes[idx:])
n += 8
idx += n
return idx, int64(obj)
}
if ttype == BoostSerializeTypeString {
n, obj := ReadString(bytes[idx:])
idx += n
return idx, obj
}
if ttype == BoostSerializeTypeBool {
obj := bytes[idx] > 0
n += 1
idx += n
return idx, obj
}
panic(fmt.Errorf("unknown ttype %x", ttype))
return -1, nil
}
// reads var int, returning number of bytes read and the integer in that byte
// sequence.
func ReadVarInt(b []byte) (int, int) {
sizeMask := b[0] & PortableRawSizeMarkMask
switch uint32(sizeMask) {
case uint32(PortableRawSizeMarkByte):
return 1, int(b[0] >> 2)
case uint32(PortableRawSizeMarkWord):
return 2, int((binary.LittleEndian.Uint16(b[0:2])) >> 2)
case PortableRawSizeMarkDword:
return 4, int((binary.LittleEndian.Uint32(b[0:4])) >> 2)
case uint32(PortableRawSizeMarkInt64):
panic("int64 not supported") // TODO
// return int((binary.LittleEndian.Uint64(b[0:8])) >> 2)
// '-> bad
default:
panic(fmt.Errorf("malformed sizemask: %+v", sizeMask))
}
return -1, -1
}
func (s *PortableStorage) Bytes() []byte {
var (
body = make([]byte, 9) // fit _at least_ signatures + format ver
b = make([]byte, 8) // biggest type
idx = 0
size = 0
)
{ // signature a
size = 4
binary.LittleEndian.PutUint32(b, PortableStorageSignatureA)
copy(body[idx:], b[:size])
idx += size
}
{ // signature b
size = 4
binary.LittleEndian.PutUint32(b, PortableStorageSignatureB)
copy(body[idx:], b[:size])
idx += size
}
{ // format ver
size = 1
b[0] = PortableStorageFormatVersion
copy(body[idx:], b[:size])
idx += size
}
// // write_var_in
varInB, err := VarIn(len(s.Entries))
if err != nil {
panic(fmt.Errorf("varin '%d': %w", len(s.Entries), err))
}
body = append(body, varInB...)
for _, entry := range s.Entries {
body = append(body, byte(len(entry.Name))) // section name length
body = append(body, []byte(entry.Name)...) // section name
body = append(body, entry.Serializable.Bytes()...)
}
return body
}
type Serializable interface {
Bytes() []byte
}
type Section struct {
Entries []Entry
}
func (s Section) Bytes() []byte {
body := []byte{
BoostSerializeTypeObject,
}
varInB, err := VarIn(len(s.Entries))
if err != nil {
panic(fmt.Errorf("varin '%d': %w", len(s.Entries), err))
}
body = append(body, varInB...)
for _, entry := range s.Entries {
body = append(body, byte(len(entry.Name))) // section name length
body = append(body, []byte(entry.Name)...) // section name
body = append(body, entry.Serializable.Bytes()...)
}
return body
}
func VarIn(i int) ([]byte, error) {
if i <= 63 {
return []byte{
(byte(i) << 2) | PortableRawSizeMarkByte,
}, nil
}
if i <= 16383 {
b := []byte{0x00, 0x00}
binary.LittleEndian.PutUint16(b,
(uint16(i)<<2)|PortableRawSizeMarkWord,
)
return b, nil
}
if i <= 1073741823 {
b := []byte{0x00, 0x00, 0x00, 0x00}
binary.LittleEndian.PutUint32(b,
(uint32(i)<<2)|PortableRawSizeMarkDword,
)
return b, nil
}
return nil, fmt.Errorf("int %d too big", i)
}