package rrdbinary import ( "bytes" "fmt" "math" "reflect" "strconv" "strings" ) type Decoder struct { arch Architecture pos int data []byte } func NewDecoder(arch Architecture, data []byte) *Decoder { return &Decoder{ arch: arch, pos: 0, data: data, } } type unmarshaler interface { unmarshalRRD(d *Decoder, tag string) error } func Unmarshal(arch Architecture, data []byte, ptr interface{}) error { decoder := NewDecoder(arch, data) if err := decoder.Decode(ptr, ""); err != nil { return err } if err := decoder.Decode(&EOF{}, ""); err != nil { return err } return nil } func (d *Decoder) binError(ctxLen int, msg string) error { return NewBinError(msg, d.data, d.pos, ctxLen) } func (d *Decoder) binErrorf(ctxLen int, format string, a ...interface{}) error { return d.binError(ctxLen, fmt.Sprintf(format, a...)) } func (d *Decoder) Decode(ptr interface{}, tag string) error { ptrValue := reflect.ValueOf(ptr) if ptrValue.Kind() != reflect.Ptr { return typeErrorf("ptr is a %v, not a pointer", ptrValue.Kind()) } return d.decode(ptrValue.Elem(), tag) } func (d *Decoder) decode(v reflect.Value, tag string) error { if v.Kind() != reflect.Ptr && v.CanAddr() { vptr := v.Addr() if vptr.CanInterface() { if u, ok := vptr.Interface().(unmarshaler); ok { return u.unmarshalRRD(d, tag) } } } switch v.Type().Kind() { case reflect.Struct: return d.decodeStruct(v, tag) case reflect.Array, reflect.Slice: return d.decodeList(v, tag) default: return typeErrorf("invalid type for rrdbinary.Decoder: %v", v.Type()) } } func (d *Decoder) decodeStruct(v reflect.Value, tag string) error { panicUnless(v.Kind() == reflect.Struct) panicUnless(v.CanSet()) if tag != "" { return typeErrorf("invalid rrdbinary struct tag for struct: %q", tag) } for i := 0; i < v.NumField(); i++ { fieldInfo := v.Type().Field(i) tag := fieldInfo.Tag.Get("rrdbinary") if tag == "-" { continue } if err := d.decode(v.Field(i), tag); err != nil { return fmt.Errorf("field %s: %w", fieldInfo.Name, err) } } return nil } func (d *Decoder) decodeList(v reflect.Value, tag string) error { panicUnless(v.Kind() == reflect.Array || v.Kind() == reflect.Slice) panicUnless(v.CanSet()) for i := 0; i < v.Len(); i++ { if err := d.decode(v.Index(i), tag); err != nil { return fmt.Errorf("index %d: %w", i, err) } } return nil } func (obj *String) unmarshalRRD(d *Decoder, tag string) error { size := 0 switch { case tag == "": // do nothing case strings.HasPrefix(tag, "size="): var err error size, err = strconv.Atoi(strings.TrimPrefix(tag, "size=")) if err != nil { return typeErrorf("invalid rrdbinary struct tag for string: %q", tag) } default: return typeErrorf("invalid rrdbinary struct tag for string: %q", tag) } data := d.data[d.pos:] if size > 0 { if len(data) < size { return d.binErrorf(size, "unexpected end-of-file in %d-byte string", size) } data = data[:size] } nul := bytes.IndexByte(data, 0) if nul < 0 { if size > 0 { return d.binErrorf(size, "missing null-terminator on %d-byte string", size) } else { return d.binErrorf(16, "unexpected end-of-file looking for null-terminator on string") } } if size > 0 { for _, byte := range data[nul:] { if byte != 0 { return d.binErrorf(size, "garbage data after null-terminator on fixed %d-byte string", size) } } } else { size = nul + 1 } *obj = String(data[:nul]) d.pos += size return nil } func (obj *Double) unmarshalRRD(d *Decoder, tag string) error { if d.arch.DoubleWidth != 8 { return archErrorf("rrdbinary does not support DoubleWidth=%d; only supports 8", d.arch.DoubleWidth) } if tag != "" { return typeErrorf("invalid rrdbinary struct tag for float: %q", tag) } data := d.data[d.pos:] padding := 0 if d.pos%d.arch.DoubleAlign != 0 { padding = d.arch.DoubleAlign - (d.pos % d.arch.DoubleAlign) } if len(data) < padding { return d.binErrorf(padding+d.arch.DoubleWidth, "unexpected end-of-file in %d-byte padding-before-float", padding) } data = data[padding:] if len(data) < d.arch.DoubleWidth { return d.binErrorf(d.arch.DoubleWidth, "unexpected end-of-file in %d-byte float", d.arch.DoubleWidth) } *obj = Double(math.Float64frombits(d.arch.ByteOrder.Uint64(data))) d.pos += padding + d.arch.DoubleWidth return nil } func (obj *ULong) unmarshalRRD(d *Decoder, tag string) error { if d.arch.LongWidth != 4 && d.arch.LongWidth != 8 { return archErrorf("rrdbinary does not support LongWidth=%d; only supports 4 or 8", d.arch.LongWidth) } if tag != "" { return typeErrorf("invalid rrdbinary struct tag for uint: %q", tag) } data := d.data[d.pos:] padding := 0 if d.pos%d.arch.LongAlign != 0 { padding = d.arch.LongAlign - (d.pos % d.arch.LongAlign) } if len(data) < padding { return d.binErrorf(padding+d.arch.LongWidth, "unexpected end-of-file in %d-byte padding-before-uint", padding) } data = data[padding:] if len(data) < d.arch.LongWidth { return d.binErrorf(d.arch.LongWidth, "unexpected end-of-file in %d-byte uint", d.arch.LongWidth) } switch d.arch.LongWidth { case 4: *obj = ULong(d.arch.ByteOrder.Uint32(data)) case 8: *obj = ULong(d.arch.ByteOrder.Uint64(data)) } d.pos += padding + d.arch.LongWidth return nil } func (obj *Long) unmarshalRRD(d *Decoder, tag string) error { if d.arch.LongWidth != 4 && d.arch.LongWidth != 8 { return archErrorf("rrdbinary does not support LongWidth=%d; only supports 4 or 8", d.arch.LongWidth) } if tag != "" { return typeErrorf("invalid rrdbinary struct tag for int: %q", tag) } data := d.data[d.pos:] padding := 0 if d.pos%d.arch.LongAlign != 0 { padding = d.arch.LongAlign - (d.pos % d.arch.LongAlign) } if len(data) < padding { return d.binErrorf(padding+d.arch.LongWidth, "unexpected end-of-file in %d-byte padding-before-int", padding) } data = data[padding:] if len(data) < d.arch.LongWidth { return d.binErrorf(d.arch.LongWidth, "unexpected end-of-file in %d-byte int", d.arch.LongWidth) } switch d.arch.LongWidth { case 4: *obj = Long(int32(d.arch.ByteOrder.Uint32(data))) case 8: *obj = Long(d.arch.ByteOrder.Uint64(data)) } d.pos += padding + d.arch.LongWidth return nil } func (obj *Unival) unmarshalRRD(d *Decoder, tag string) error { if d.arch.UnivalWidth != 8 { return archErrorf("rrdbinary does not support UnivalWidth=%d; only supports 8", d.arch.UnivalWidth) } if d.arch.ShortWidth != 2 && d.arch.ShortWidth != 4 { return archErrorf("rrdbinary does not support ShortWidth=%d; only supports 2 or 4", d.arch.ShortWidth) } if tag != "" { return typeErrorf("invalid rrdbinary struct tag for unival: %q", tag) } data := d.data[d.pos:] padding := 0 if d.pos%d.arch.UnivalAlign != 0 { padding = d.arch.UnivalAlign - (d.pos % d.arch.UnivalAlign) } if len(data) < padding { return d.binErrorf(padding+d.arch.UnivalWidth, "unexpected end-of-file in %d-byte padding-before-unival", padding) } data = data[padding:] if len(data) < d.arch.UnivalWidth { return d.binErrorf(d.arch.UnivalWidth, "unexpected end-of-file in %d-byte unival", d.arch.UnivalWidth) } *obj = Unival{ arch: d.arch, data: data[:d.arch.UnivalWidth], } d.pos += padding + d.arch.UnivalWidth return nil } func (obj *Time) unmarshalRRD(d *Decoder, tag string) error { if d.arch.TimeWidth != 4 && d.arch.TimeWidth != 8 { return archErrorf("rrdbinary does not support TimeWidth=%d; only supports 4 or 8", d.arch.TimeWidth) } if tag != "" { return typeErrorf("invalid rrdbinary struct tag for time: %q", tag) } data := d.data[d.pos:] padding := 0 if d.pos%d.arch.TimeAlign != 0 { padding = d.arch.TimeAlign - (d.pos % d.arch.TimeAlign) } if len(data) < padding { return d.binErrorf(padding+d.arch.TimeWidth, "unexpected end-of-file in %d-byte padding-before-time", padding) } data = data[padding:] if len(data) < d.arch.TimeWidth { return d.binErrorf(d.arch.TimeWidth, "unexpected end-of-file in %d-byte time", d.arch.TimeWidth) } switch d.arch.TimeWidth { case 4: *obj = Time(int32(d.arch.ByteOrder.Uint32(data))) case 8: *obj = Time(d.arch.ByteOrder.Uint64(data)) } d.pos += padding + d.arch.TimeWidth return nil } func (_ *EOF) unmarshalRRD(d *Decoder, tag string) error { if tag != "" { return typeErrorf("invalid rrdbinary struct tag for eof: %q", tag) } data := d.data[d.pos:] if len(data) > 0 { return d.binErrorf(16, "extra %d bytes of data after expected end-of-file", len(data)) } return nil }