Skip to content

Commit 18d5282

Browse files
committed
add support for custom types
1 parent d0f59e6 commit 18d5282

10 files changed

+244
-86
lines changed

binary.go

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package struc
2+
3+
import (
4+
"encoding/binary"
5+
"io"
6+
"reflect"
7+
)
8+
9+
type byteWriter struct {
10+
buf []byte
11+
pos int
12+
}
13+
14+
func (b byteWriter) Write(p []byte) (int, error) {
15+
capacity := len(b.buf) - b.pos
16+
if capacity < len(p) {
17+
p = p[:capacity]
18+
}
19+
if len(p) > 0 {
20+
copy(b.buf[b.pos:], p)
21+
b.pos += len(p)
22+
}
23+
return len(p), nil
24+
}
25+
26+
type binaryFallback reflect.Value
27+
28+
func (b binaryFallback) String() string {
29+
return b.String()
30+
}
31+
32+
func (b binaryFallback) Sizeof(val reflect.Value, options *Options) int {
33+
return binary.Size(val.Interface())
34+
}
35+
36+
func (b binaryFallback) Pack(buf []byte, val reflect.Value, options *Options) (int, error) {
37+
tmp := byteWriter{buf: buf}
38+
var order binary.ByteOrder = binary.BigEndian
39+
if options.Order != nil {
40+
order = options.Order
41+
}
42+
err := binary.Write(tmp, order, val.Interface())
43+
return tmp.pos, err
44+
}
45+
46+
func (b binaryFallback) Unpack(r io.Reader, val reflect.Value, options *Options) error {
47+
var order binary.ByteOrder = binary.BigEndian
48+
if options.Order != nil {
49+
order = options.Order
50+
}
51+
return binary.Read(r, order, val.Interface())
52+
}

custom.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package struc
2+
3+
import (
4+
"io"
5+
"reflect"
6+
)
7+
8+
type Custom interface {
9+
Pack(p []byte, opt *Options) (int, error)
10+
Unpack(r io.Reader, length int, opt *Options) error
11+
Size(opt *Options) int
12+
String() string
13+
}
14+
15+
type customFallback struct {
16+
custom Custom
17+
}
18+
19+
func (c customFallback) Pack(p []byte, val reflect.Value, opt *Options) (int, error) {
20+
return c.custom.Pack(p, opt)
21+
}
22+
23+
func (c customFallback) Unpack(r io.Reader, val reflect.Value, opt *Options) error {
24+
return c.custom.Unpack(r, 1, opt)
25+
}
26+
27+
func (c customFallback) Sizeof(val reflect.Value, opt *Options) int {
28+
return c.custom.Size(opt)
29+
}
30+
31+
func (c customFallback) String() string {
32+
return c.custom.String()
33+
}

custom_test.go

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package struc
2+
3+
import (
4+
"bytes"
5+
"encoding/binary"
6+
"io"
7+
"strconv"
8+
"testing"
9+
)
10+
11+
type Int3 uint32
12+
13+
func (i *Int3) Pack(p []byte, opt *Options) (int, error) {
14+
var tmp [4]byte
15+
binary.BigEndian.PutUint32(tmp[:], uint32(*i))
16+
copy(p, tmp[1:])
17+
return 3, nil
18+
}
19+
func (i *Int3) Unpack(r io.Reader, length int, opt *Options) error {
20+
var tmp [4]byte
21+
if _, err := r.Read(tmp[1:]); err != nil {
22+
return err
23+
}
24+
*i = Int3(binary.BigEndian.Uint32(tmp[:]))
25+
return nil
26+
}
27+
func (i *Int3) Size(opt *Options) int {
28+
return 3
29+
}
30+
func (i *Int3) String() string {
31+
return strconv.FormatUint(uint64(*i), 10)
32+
}
33+
34+
func TestCustom(t *testing.T) {
35+
var buf bytes.Buffer
36+
var i Int3 = 3
37+
if err := Pack(&buf, &i); err != nil {
38+
t.Fatal(err)
39+
}
40+
if !bytes.Equal(buf.Bytes(), []byte{0, 0, 3}) {
41+
t.Fatal("error packing custom int")
42+
}
43+
var i2 Int3
44+
if err := Unpack(&buf, &i2); err != nil {
45+
t.Fatal(err)
46+
}
47+
if i2 != 3 {
48+
t.Fatal("error unpacking custom int")
49+
}
50+
}
51+
52+
type Int3Struct struct {
53+
I Int3
54+
}
55+
56+
func TestCustomStruct(t *testing.T) {
57+
var buf bytes.Buffer
58+
i := Int3Struct{3}
59+
if err := Pack(&buf, &i); err != nil {
60+
t.Fatal(err)
61+
}
62+
if !bytes.Equal(buf.Bytes(), []byte{0, 0, 3}) {
63+
t.Fatal("error packing custom int struct")
64+
}
65+
var i2 Int3Struct
66+
if err := Unpack(&buf, &i2); err != nil {
67+
t.Fatal(err)
68+
}
69+
if i2.I != 3 {
70+
t.Fatal("error unpacking custom int struct")
71+
}
72+
}
73+
74+
// TODO: slices of custom types don't work yet
75+
/*
76+
type Int3SliceStruct struct {
77+
I [2]Int3
78+
}
79+
80+
func TestCustomSliceStruct(t *testing.T) {
81+
var buf bytes.Buffer
82+
i := Int3SliceStruct{[2]Int3{3, 4}}
83+
if err := Pack(&buf, &i); err != nil {
84+
t.Fatal(err)
85+
}
86+
if !bytes.Equal(buf.Bytes(), []byte{0, 0, 3}) {
87+
t.Fatal("error packing custom int struct")
88+
}
89+
var i2 Int3SliceStruct
90+
if err := Unpack(&buf, &i2); err != nil {
91+
t.Fatal(err)
92+
}
93+
if i2.I[0] != 3 && i2.I[1] != 4 {
94+
t.Fatal("error unpacking custom int struct")
95+
}
96+
}
97+
*/

field.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ func (f *Field) String() string {
2929
if f.Type == Pad {
3030
return fmt.Sprintf("{type: Pad, len: %d}", f.Len)
3131
} else {
32-
typeName := typeNames[f.Type]
33-
out = fmt.Sprintf("type: %s, order: %v", typeName, f.Order)
32+
out = fmt.Sprintf("type: %s, order: %v", f.Type.String(), f.Order)
3433
}
3534
if f.Sizefrom != nil {
3635
out += fmt.Sprintf(", sizefrom: %v", f.Sizefrom)
@@ -65,6 +64,8 @@ func (f *Field) Size(val reflect.Value, options *Options) int {
6564
length = f.Len
6665
}
6766
size = length * typ.Size()
67+
} else if typ == CustomType {
68+
return val.Addr().Interface().(Custom).Size(options)
6869
} else {
6970
size = typ.Size()
7071
}
@@ -137,6 +138,10 @@ func (f *Field) packVal(buf []byte, val reflect.Value, length int, options *Opti
137138
size = val.Len()
138139
copy(buf, val.Bytes())
139140
}
141+
case CustomType:
142+
return val.Addr().Interface().(Custom).Pack(buf, options)
143+
default:
144+
panic(fmt.Sprintf("no pack handler for type: %s", typ))
140145
}
141146
return
142147
}
@@ -214,6 +219,8 @@ func (f *Field) unpackVal(buf []byte, val reflect.Value, length int, options *Op
214219
default:
215220
val.SetUint(n)
216221
}
222+
default:
223+
panic(fmt.Sprintf("no unpack handler for type: %s", typ))
217224
}
218225
return nil
219226
}

fields.go

+16-11
Original file line numberDiff line numberDiff line change
@@ -137,18 +137,23 @@ func (f Fields) Unpack(r io.Reader, val reflect.Value, options *Options) error {
137137
}
138138
continue
139139
} else {
140-
size := length * field.Type.Resolve(options).Size()
141-
if size < 8 {
142-
buf = tmp[:size]
140+
typ := field.Type.Resolve(options)
141+
if typ == CustomType {
142+
return v.Addr().Interface().(Custom).Unpack(r, length, options)
143143
} else {
144-
buf = make([]byte, size)
145-
}
146-
if _, err := io.ReadFull(r, buf); err != nil {
147-
return err
148-
}
149-
err := field.Unpack(buf[:size], v, length, options)
150-
if err != nil {
151-
return err
144+
size := length * field.Type.Resolve(options).Size()
145+
if size < 8 {
146+
buf = tmp[:size]
147+
} else {
148+
buf = make([]byte, size)
149+
}
150+
if _, err := io.ReadFull(r, buf); err != nil {
151+
return err
152+
}
153+
err := field.Unpack(buf[:size], v, length, options)
154+
if err != nil {
155+
return err
156+
}
152157
}
153158
}
154159
}

packable.go

-62
This file was deleted.

packer.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package struc
2+
3+
import (
4+
"io"
5+
"reflect"
6+
)
7+
8+
type Packer interface {
9+
Pack(buf []byte, val reflect.Value, options *Options) (int, error)
10+
Unpack(r io.Reader, val reflect.Value, options *Options) error
11+
Sizeof(val reflect.Value, options *Options) int
12+
String() string
13+
}

parse.go

+6
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ func parseField(f reflect.StructField) (fd *Field, err error) {
7373
fd.Ptr = true
7474
fd.kind = f.Type.Elem().Kind()
7575
}
76+
// check for custom types
77+
tmp := reflect.New(f.Type)
78+
if _, ok := tmp.Interface().(Custom); ok {
79+
fd.Type = CustomType
80+
return
81+
}
7682
var defTypeOk bool
7783
fd.defType, defTypeOk = reflectTypeMap[fd.kind]
7884
// find a type in the struct tag

0 commit comments

Comments
 (0)