summaryrefslogtreecommitdiff
path: root/pkg/binstruct/l2.go
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2022-05-10 03:59:33 -0600
committerLuke Shumaker <lukeshu@lukeshu.com>2022-05-10 03:59:33 -0600
commita16049ef805c0c08b90885a5b7dfea7f74e51c5f (patch)
tree5b5dad8b0d44ccd3e7a43ac5bde557ae220a0f04 /pkg/binstruct/l2.go
initial commit
Diffstat (limited to 'pkg/binstruct/l2.go')
-rw-r--r--pkg/binstruct/l2.go156
1 files changed, 156 insertions, 0 deletions
diff --git a/pkg/binstruct/l2.go b/pkg/binstruct/l2.go
new file mode 100644
index 0000000..005d686
--- /dev/null
+++ b/pkg/binstruct/l2.go
@@ -0,0 +1,156 @@
+package binstruct
+
+import (
+ "fmt"
+ "reflect"
+ "strconv"
+ "strings"
+)
+
+type End struct{}
+
+type structHandler struct {
+ typ reflect.Type
+ fields []structField
+ size int64
+}
+
+type structField struct {
+ tag
+ handler
+ name string
+}
+
+func (sh structHandler) Unmarshal(dat []byte) interface{} {
+ val := reflect.Zero(sh.typ)
+ for i, field := range sh.fields {
+ if field.skip {
+ continue
+ }
+ val.Field(i).Set(reflect.ValueOf(field.Unmarshal(dat[field.off:])))
+ }
+ return val.Interface()
+}
+func (sh structHandler) Marshal(_val interface{}) []byte {
+ val := reflect.ValueOf(_val)
+ ret := make([]byte, 0, sh.size)
+ for i, field := range sh.fields {
+ if field.skip {
+ continue
+ }
+ if int64(len(ret)) != field.off {
+ panic(fmt.Errorf("field %d %q: len(ret)=0x%x but field.off=0x%x", i, field.name, len(ret), field.off))
+ }
+ ret = append(ret, field.Marshal(val.Field(i).Interface())...)
+ }
+ return ret
+}
+func (sh structHandler) Size() int64 {
+ return sh.size
+}
+
+var _ handler = structHandler{}
+
+func genStructHandler(structInfo reflect.Type) (handler, error) {
+ ret := structHandler{
+ typ: structInfo,
+ }
+
+ var curOffset, endOffset int64
+ for i := 0; i < structInfo.NumField(); i++ {
+ var fieldInfo reflect.StructField = structInfo.Field(i)
+
+ fieldTag, err := parseStructTag(fieldInfo.Tag.Get("bin"))
+ if err != nil {
+ return nil, fmt.Errorf("%v: field %q: %w",
+ structInfo, fieldInfo.Name, err)
+ }
+ if fieldTag.skip {
+ ret.fields = append(ret.fields, structField{
+ tag: fieldTag,
+ name: fieldInfo.Name,
+ })
+ continue
+ }
+
+ if fieldTag.off != curOffset {
+ err := fmt.Errorf("tag says off=0x%x but curOffset=0x%x", fieldTag.off, curOffset)
+ return nil, fmt.Errorf("%v: field %q: %w",
+ structInfo, fieldInfo.Name, err)
+ }
+ if fieldInfo.Type == reflect.TypeOf(End{}) {
+ endOffset = curOffset
+ }
+
+ fieldHandler, err := getHandler(fieldInfo.Type)
+ if err != nil {
+ return nil, fmt.Errorf("%v: field %q: %w",
+ structInfo, fieldInfo.Name, err)
+ }
+
+ if fieldTag.siz != fieldHandler.Size() {
+ err := fmt.Errorf("tag says siz=0x%x but handler.Size()=0x%x", fieldTag.siz, fieldHandler.Size())
+ return nil, fmt.Errorf("%v: field %q: %w",
+ structInfo, fieldInfo.Name, err)
+ }
+ curOffset += fieldTag.siz
+
+ ret.fields = append(ret.fields, structField{
+ tag: fieldTag,
+ handler: fieldHandler,
+ name: fieldInfo.Name,
+ })
+ }
+ ret.size = curOffset
+
+ if ret.size != endOffset {
+ return nil, fmt.Errorf("%v: .size=%v but endOffset=%v",
+ structInfo, ret.size, endOffset)
+ }
+
+ return ret, nil
+}
+
+type tag struct {
+ skip bool
+
+ off int64
+ siz int64
+ desc string
+}
+
+func parseStructTag(str string) (tag, error) {
+ var ret tag
+ for _, part := range strings.Split(str, ",") {
+ part = strings.TrimSpace(part)
+ if part == "" {
+ continue
+ }
+ if part == "-" {
+ return tag{skip: true}, nil
+ }
+ keyval := strings.SplitN(part, "=", 2)
+ if len(keyval) != 2 {
+ return tag{}, fmt.Errorf("option is not a key=value pair: %q", part)
+ }
+ key := keyval[0]
+ val := keyval[1]
+ switch key {
+ case "off":
+ vint, err := strconv.ParseInt(val, 16, 64)
+ if err != nil {
+ return tag{}, err
+ }
+ ret.off = vint
+ case "siz":
+ vint, err := strconv.ParseInt(val, 16, 64)
+ if err != nil {
+ return tag{}, err
+ }
+ ret.siz = vint
+ case "desc":
+ ret.desc = val
+ }
+ }
+ return ret, nil
+}