Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,24 +103,42 @@ func (f Fields) Pack(buf []byte, val reflect.Value, options *Options) (int, erro
}

func (f Fields) Unpack(r io.Reader, val reflect.Value, options *Options) error {

for val.Kind() == reflect.Ptr {
val = val.Elem()
}

var tmp [8]byte
var buf []byte

for i, field := range f {

if field == nil {
continue
}

v := val.Field(i)

length := field.Len
if field.Sizefrom != nil {
length = f.sizefrom(val, field.Sizefrom)
}

if length == -1 {

length = v.Len()

// if length == 0 {
// return fmt.Errorf("struc: field `%s` is a slice with no length or sizeof field", field.Name)
// }
}

if v.Kind() == reflect.Ptr && !v.Elem().IsValid() {
v.Set(reflect.New(v.Type().Elem()))
}

if field.Type == Struct {

if field.Slice {
vals := v
if !field.Array {
Expand All @@ -140,6 +158,7 @@ func (f Fields) Unpack(r io.Reader, val reflect.Value, options *Options) error {
v.Set(vals)
}
} else {

// TODO: DRY (we repeat the inner loop above)
fields, err := parseFields(v)
if err != nil {
Expand All @@ -151,6 +170,7 @@ func (f Fields) Unpack(r io.Reader, val reflect.Value, options *Options) error {
}
continue
} else {

typ := field.Type.Resolve(options)
if typ == CustomType {
if err := v.Addr().Interface().(Custom).Unpack(r, length, options); err != nil {
Expand Down
30 changes: 29 additions & 1 deletion parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,28 +120,39 @@ func parseField(f reflect.StructField) (fd *Field, tag *strucTag, err error) {
}

func parseFieldsLocked(v reflect.Value) (Fields, error) {

// we need to repeat this logic because parseFields() below can't be recursively called due to locking
for v.Kind() == reflect.Ptr {
v = v.Elem()
}

t := v.Type()

if v.NumField() < 1 {
return nil, errors.New("struc: Struct has no fields.")
}

sizeofMap := make(map[string][]int)
fields := make(Fields, v.NumField())

for i := 0; i < t.NumField(); i++ {

field := t.Field(i)

f, tag, err := parseField(field)

if tag.Skip {
continue
}

if err != nil {
return nil, err
}

if !v.Field(i).CanSet() {
continue
}

f.Index = i
if tag.Sizeof != "" {
target, ok := t.FieldByName(tag.Sizeof)
Expand All @@ -151,19 +162,26 @@ func parseFieldsLocked(v reflect.Value) (Fields, error) {
f.Sizeof = target.Index
sizeofMap[tag.Sizeof] = field.Index
}

if sizefrom, ok := sizeofMap[field.Name]; ok {
f.Sizefrom = sizefrom
}

if tag.Sizefrom != "" {
source, ok := t.FieldByName(tag.Sizefrom)
if !ok {
return nil, fmt.Errorf("struc: `sizefrom=%s` field does not exist", tag.Sizefrom)
}
f.Sizefrom = source.Index
}

if f.Len == -1 && f.Sizefrom == nil {
return nil, fmt.Errorf("struc: field `%s` is a slice with no length or sizeof field", field.Name)
// f.Len = v.Field(i).Len()
// if f.Len == 0 {
// return nil, fmt.Errorf("struc: field `%s` is a slice with no length or sizeof field", field.Name)
// }
}

// recurse into nested structs
// TODO: handle loops (probably by indirecting the []Field and putting pointer in cache)
if f.Type == Struct {
Expand All @@ -179,8 +197,10 @@ func parseFieldsLocked(v reflect.Value) (Fields, error) {
return nil, err
}
}

fields[i] = f
}

return fields, nil
}

Expand All @@ -189,18 +209,23 @@ var fieldCacheLock sync.RWMutex
var parseLock sync.Mutex

func fieldCacheLookup(t reflect.Type) Fields {

fieldCacheLock.RLock()
defer fieldCacheLock.RUnlock()

if cached, ok := fieldCache[t]; ok {
return cached
}

return nil
}

func parseFields(v reflect.Value) (Fields, error) {

for v.Kind() == reflect.Ptr {
v = v.Elem()
}

t := v.Type()

// fast path: hopefully the field parsing is already cached
Expand All @@ -220,11 +245,14 @@ func parseFields(v reflect.Value) (Fields, error) {

// no luck, time to parse and fill the cache ourselves
fields, err := parseFieldsLocked(v)

if err != nil {
return nil, err
}

fieldCacheLock.Lock()
fieldCache[t] = fields
fieldCacheLock.Unlock()

return fields, nil
}
18 changes: 9 additions & 9 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ func TestBadSizeof(t *testing.T) {
}
}

type missingSize struct {
Test []byte
}

func TestMissingSize(t *testing.T) {
if err := parseTest(&missingSize{}); err == nil {
t.Fatal("failed to error on missing field size")
}
}
// type missingSize struct {
// Test []byte
// }

// func TestMissingSize(t *testing.T) {
// if err := parseTest(&missingSize{}); err == nil {
// t.Fatal("failed to error on missing field size")
// }
// }

type badNested struct {
Empty empty
Expand Down
8 changes: 8 additions & 0 deletions struc.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ func init() {
}

func prep(data interface{}) (reflect.Value, Packer, error) {

value := reflect.ValueOf(data)

for value.Kind() == reflect.Ptr {
next := value.Elem().Kind()
if next == reflect.Struct || next == reflect.Ptr {
Expand All @@ -43,17 +45,23 @@ func prep(data interface{}) (reflect.Value, Packer, error) {
break
}
}

switch value.Kind() {
case reflect.Struct:

fields, err := parseFields(value)
return value, fields, err

default:

if !value.IsValid() {
return reflect.Value{}, nil, fmt.Errorf("Invalid reflect.Value for %+v", data)
}

if c, ok := data.(Custom); ok {
return value, customFallback{c}, nil
}

return value, binaryFallback(value), nil
}
}
Expand Down
61 changes: 61 additions & 0 deletions struc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/binary"
"fmt"
"reflect"
"strings"
"testing"
)

Expand Down Expand Up @@ -308,3 +309,63 @@ func TestSliceUnderrun(t *testing.T) {
t.Fatal(err)
}
}

type dynamic struct {
Buf []byte
V int
}

func TestParse(t *testing.T) {

{
dm := &dynamic{Buf: make([]byte, 3)}

r := strings.NewReader("1231234")

err := Unpack(r, dm)

if string(dm.Buf) != "123" {
t.Fatal("invalid len")
}

if err != nil {
t.Fatal("unable to parse this one!")
}
}

{

dm := &dynamic{Buf: make([]byte, 2)}
r := strings.NewReader("1231234")

err := Unpack(r, dm)

if string(dm.Buf) != "12" {
t.Fatal("invalid len")
}

if err != nil {
t.Fatal("unable to parse this one!")
}
}

{

dm := &dynamic{}
r := strings.NewReader("12334")

err := Unpack(r, dm)

if err != nil {
t.Fatal("missing size of []byte")
}

if len(dm.Buf) != 0 {
t.Fatal("mismatch size of []byte")
}

if dm.V == 0 {
t.Fatal("mismatch size of []byte")
}
}
}