safing-portbase/formats/dsd/dsd.go
2021-09-26 13:41:31 +02:00

150 lines
3.5 KiB
Go

package dsd
// dynamic structured data
// check here for some benchmarks: https://github.com/alecthomas/go_serialization_benchmarks
import (
"encoding/json"
"errors"
"fmt"
// "github.com/pkg/bson"
"github.com/safing/portbase/formats/varint"
"github.com/safing/portbase/utils"
)
// define types
const (
AUTO = 0
NONE = 1
// special
LIST = 76 // L
// serialization
STRING = 83 // S
BYTES = 88 // X
JSON = 74 // J
BSON = 66 // B
GenCode = 71 // G
// compression
GZIP = 90 // Z
)
// define errors
var errNoMoreSpace = errors.New("dsd: no more space left after reading dsd type")
var errNotImplemented = errors.New("dsd: this type is not yet implemented")
// Load loads an dsd structured data blob into the given interface.
func Load(data []byte, t interface{}) (interface{}, error) {
format, read, err := varint.Unpack8(data)
if err != nil {
return nil, err
}
if len(data) <= read {
return nil, errNoMoreSpace
}
switch format {
case GZIP:
return DecompressAndLoad(data[read:], format, t)
default:
return LoadAsFormat(data[read:], format, t)
}
}
// LoadAsFormat loads a data blob into the interface using the specified format.
func LoadAsFormat(data []byte, format uint8, t interface{}) (interface{}, error) {
switch format {
case STRING:
return string(data), nil
case BYTES:
return data, nil
case JSON:
err := json.Unmarshal(data, t)
if err != nil {
return nil, fmt.Errorf("dsd: failed to unpack json: %s, data: %s", err, utils.SafeFirst16Bytes(data))
}
return t, nil
case BSON:
return nil, errNotImplemented
// err := bson.Unmarshal(data[read:], t)
// if err != nil {
// return nil, err
// }
// return t, nil
case GenCode:
genCodeStruct, ok := t.(GenCodeCompatible)
if !ok {
return nil, errors.New("dsd: gencode is not supported by the given data structure")
}
_, err := genCodeStruct.GenCodeUnmarshal(data)
if err != nil {
return nil, fmt.Errorf("dsd: failed to unpack gencode: %s, data: %s", err, utils.SafeFirst16Bytes(data))
}
return t, nil
default:
return nil, fmt.Errorf("dsd: tried to load unknown type %d, data: %s", format, utils.SafeFirst16Bytes(data))
}
}
// Dump stores the interface as a dsd formatted data structure.
func Dump(t interface{}, format uint8) ([]byte, error) {
return DumpIndent(t, format, "")
}
// DumpIndent stores the interface as a dsd formatted data structure with indentation, if available.
func DumpIndent(t interface{}, format uint8, indent string) ([]byte, error) {
if format == AUTO {
switch t.(type) {
case string:
format = STRING
case []byte:
format = BYTES
default:
format = JSON
}
}
f := varint.Pack8(format)
var data []byte
var err error
switch format {
case STRING:
data = []byte(t.(string))
case BYTES:
data = t.([]byte)
case JSON:
// TODO: use SetEscapeHTML(false)
if indent != "" {
data, err = json.MarshalIndent(t, "", indent)
} else {
data, err = json.Marshal(t)
}
if err != nil {
return nil, err
}
case BSON:
return nil, errNotImplemented
// data, err = bson.Marshal(t)
// if err != nil {
// return nil, err
// }
case GenCode:
genCodeStruct, ok := t.(GenCodeCompatible)
if !ok {
return nil, errors.New("dsd: gencode is not supported by the given data structure")
}
data, err = genCodeStruct.GenCodeMarshal(nil)
if err != nil {
return nil, fmt.Errorf("dsd: failed to pack gencode struct: %s", err)
}
default:
return nil, fmt.Errorf("dsd: tried to dump with unknown format %d", format)
}
r := append(f, data...)
return r, nil
}