411 lines
7.2 KiB
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)
|
|
}
|