151 lines
4.0 KiB
Go
151 lines
4.0 KiB
Go
package solana
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
|
|
"github.com/lunixbochs/struc"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type VariantDefinition struct {
|
|
typeIDToType map[Varuint16]reflect.Type
|
|
typeIDToName map[Varuint16]string
|
|
typeNameToID map[string]Varuint16
|
|
}
|
|
|
|
type VariantType struct {
|
|
Name string
|
|
Type interface{}
|
|
}
|
|
|
|
func NewVariantDefinition(types []VariantType) (out *VariantDefinition) {
|
|
if len(types) < 0 {
|
|
panic("it's not valid to create a variant definition without any types")
|
|
}
|
|
|
|
typeCount := len(types)
|
|
out = &VariantDefinition{
|
|
typeIDToType: make(map[Varuint16]reflect.Type, typeCount),
|
|
typeIDToName: make(map[Varuint16]string, typeCount),
|
|
typeNameToID: make(map[string]Varuint16, typeCount),
|
|
}
|
|
|
|
for i, typeDef := range types {
|
|
typeID := Varuint16(i)
|
|
|
|
// FIXME: Check how the reflect.Type is used and cache all its usage in the definition.
|
|
// Right now, on each Unmarshal, we re-compute some expensive stuff that can be
|
|
// re-used like the `typeGo.Elem()` which is always the same. It would be preferable
|
|
// to have those already pre-defined here so we can actually speed up the
|
|
// Unmarshal code.
|
|
out.typeIDToType[typeID] = reflect.TypeOf(typeDef.Type)
|
|
out.typeIDToName[typeID] = typeDef.Name
|
|
out.typeNameToID[typeDef.Name] = typeID
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func (d *VariantDefinition) IDForType(impl interface{}) Varuint16 {
|
|
for typeNum, reflectType := range d.typeIDToType {
|
|
if reflect.TypeOf(impl) == reflectType {
|
|
return typeNum
|
|
}
|
|
}
|
|
panic(fmt.Sprintf("type %T undefined on variant definition %#v", impl, d))
|
|
}
|
|
|
|
type BaseVariant struct {
|
|
Type Varuint16
|
|
Impl interface{}
|
|
}
|
|
|
|
func (bv BaseVariant) Pack(p []byte, opt *struc.Options) (written int, err error) {
|
|
if traceEnabled {
|
|
zlog.Debug("packing variant to binary", zap.Uint16("type", uint16(bv.Type)))
|
|
}
|
|
|
|
w := &byteCounterWritter{Writer: bytes.NewBuffer(p)}
|
|
|
|
err = struc.Pack(w, bv.Type)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("pack type: %w", err)
|
|
}
|
|
|
|
err = struc.Pack(w, bv.Impl)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("pack impl: %w", err)
|
|
}
|
|
|
|
return w.byteCount, nil
|
|
}
|
|
|
|
func (bv BaseVariant) Size(opt *struc.Options) int {
|
|
return 0
|
|
}
|
|
|
|
func (bv BaseVariant) String() string {
|
|
return fmt.Sprintf("%T (%d)", bv.Type, bv.Impl)
|
|
}
|
|
|
|
func (bv *BaseVariant) Unpack(def *VariantDefinition, r io.Reader, length int, opt *struc.Options) (err error) {
|
|
if err = struc.Unpack(r, &bv.Type); err != nil {
|
|
return
|
|
}
|
|
|
|
typeGo := def.typeIDToType[bv.Type]
|
|
if typeGo == nil {
|
|
return fmt.Errorf("no known type for type %d on %#v", bv.Type, def)
|
|
}
|
|
|
|
if typeGo.Kind() == reflect.Ptr {
|
|
bv.Impl = reflect.New(typeGo.Elem()).Interface()
|
|
if err = struc.Unpack(r, bv.Impl); err != nil {
|
|
return fmt.Errorf("unable to decode variant type %d: %s", bv.Type, err)
|
|
}
|
|
} else {
|
|
// This is not the most optimal way of doing things for "value"
|
|
// types (over "pointer" types) as we always allocate a new pointer
|
|
// element, unmarshal it and then either keep the pointer type or turn
|
|
// it into a value type.
|
|
//
|
|
// However, in non-reflection based code, one would do like this and
|
|
// avoid an `new` memory allocation:
|
|
//
|
|
// ```
|
|
// name := string("")
|
|
// json.Unmarshal(data, &name)
|
|
// ```
|
|
//
|
|
// This would work without a problem. In reflection code however, I
|
|
// did not find how one can go from `reflect.Zero(typeGo)` (which is
|
|
// the equivalence of doing `name := string("")`) and take the
|
|
// pointer to it so it can be unmarshalled correctly.
|
|
//
|
|
// A played with various iteration, and nothing got it working. Maybe
|
|
// the next step would be to explore the `unsafe` package and obtain
|
|
// an unsafe pointer and play with it.
|
|
value := reflect.New(typeGo)
|
|
if err = struc.Unpack(r, value.Interface()); err != nil {
|
|
return fmt.Errorf("unable to decode variant type %d: %s", bv.Type, err)
|
|
}
|
|
|
|
bv.Impl = value.Elem().Interface()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type byteCounterWritter struct {
|
|
io.Writer
|
|
byteCount int
|
|
}
|
|
|
|
func (w *byteCounterWritter) Write(p []byte) (n int, err error) {
|
|
n, err = w.Writer.Write(p)
|
|
w.byteCount += n
|
|
return
|
|
}
|