summaryrefslogtreecommitdiff
path: root/pkg/binstruct/l2.go
blob: c9b9a9c0a583e33882e21db33d7de3a404c6ee47 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package binstruct

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

type End struct{}

type structHandler struct {
	typ    reflect.Type
	fields []structField
	size   int64
}

type structField struct {
	typ reflect.Type
	tag
	handler
	name string
}

func (sh structHandler) Unmarshal(dat []byte) interface{} {
	val := reflect.New(sh.typ).Elem()
	for i, field := range sh.fields {
		if field.skip {
			continue
		}
		fieldVal := field.Unmarshal(dat[field.off:])
		val.Field(i).Set(reflect.ValueOf(fieldVal).Convert(field.typ))
	}
	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{
			typ:     fieldInfo.Type,
			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
}

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
		default:
			return tag{}, fmt.Errorf("unrecognized option %q", key)
		}
	}
	return ret, nil
}