summaryrefslogtreecommitdiff
path: root/rrdformat/rrdbinary/decode.go
diff options
context:
space:
mode:
Diffstat (limited to 'rrdformat/rrdbinary/decode.go')
-rw-r--r--rrdformat/rrdbinary/decode.go319
1 files changed, 319 insertions, 0 deletions
diff --git a/rrdformat/rrdbinary/decode.go b/rrdformat/rrdbinary/decode.go
new file mode 100644
index 0000000..f3ae80e
--- /dev/null
+++ b/rrdformat/rrdbinary/decode.go
@@ -0,0 +1,319 @@
+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, tag)
+}
+
+func (d *Decoder) decode(v reflect.Value, tag string) error {
+ if v.CanInterface() {
+ if u, ok := v.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 *Float) unmarshalRRD(d *Decoder, tag string) error {
+ if d.arch.FloatWidth != 8 {
+ return archErrorf("rrdbinary does not support FloatWidth=%d; only supports 8", d.arch.FloatWidth)
+ }
+ if tag != "" {
+ return typeErrorf("invalid rrdbinary struct tag for float: %q", tag)
+ }
+
+ data := d.data[d.pos:]
+
+ padding := 0
+ if d.pos%d.arch.FloatAlign != 0 {
+ padding = d.arch.FloatAlign - (d.pos % d.arch.FloatAlign)
+ }
+ if len(data) < padding {
+ return d.binErrorf(padding+d.arch.FloatWidth, "unexpected end-of-file in %d-byte padding-before-float", padding)
+ }
+ data = data[padding:]
+
+ if len(data) < d.arch.FloatWidth {
+ return d.binErrorf(d.arch.FloatWidth, "unexpected end-of-file in %d-byte float", d.arch.FloatWidth)
+ }
+
+ *obj = Float(math.Float64frombits(d.arch.ByteOrder.Uint64(data)))
+ d.pos += padding + d.arch.FloatWidth
+ return nil
+}
+
+func (obj *Uint) unmarshalRRD(d *Decoder, tag string) error {
+ if d.arch.IntWidth != 4 && d.arch.IntWidth != 8 {
+ return archErrorf("rrdbinary does not support IntWidth=%d; only supports 4 or 8", d.arch.IntWidth)
+ }
+ if tag != "" {
+ return typeErrorf("invalid rrdbinary struct tag for uint: %q", tag)
+ }
+
+ data := d.data[d.pos:]
+
+ padding := 0
+ if d.pos%d.arch.IntAlign != 0 {
+ padding = d.arch.IntAlign - (d.pos % d.arch.IntAlign)
+ }
+ if len(data) < padding {
+ return d.binErrorf(padding+d.arch.IntWidth, "unexpected end-of-file in %d-byte padding-before-uint", padding)
+ }
+ data = data[padding:]
+
+ if len(data) < d.arch.IntWidth {
+ return d.binErrorf(d.arch.IntWidth, "unexpected end-of-file in %d-byte uint", d.arch.IntWidth)
+ }
+
+ switch d.arch.IntWidth {
+ case 4:
+ *obj = Uint(d.arch.ByteOrder.Uint32(data))
+ case 8:
+ *obj = Uint(d.arch.ByteOrder.Uint64(data))
+ }
+ d.pos += padding + d.arch.IntWidth
+ return nil
+}
+
+func (obj *Int) unmarshalRRD(d *Decoder, tag string) error {
+ if d.arch.IntWidth != 4 && d.arch.IntWidth != 8 {
+ return archErrorf("rrdbinary does not support IntWidth=%d; only supports 4 or 8", d.arch.IntWidth)
+ }
+ if tag != "" {
+ return typeErrorf("invalid rrdbinary struct tag for int: %q", tag)
+ }
+
+ data := d.data[d.pos:]
+
+ padding := 0
+ if d.pos%d.arch.IntAlign != 0 {
+ padding = d.arch.IntAlign - (d.pos % d.arch.IntAlign)
+ }
+ if len(data) < padding {
+ return d.binErrorf(padding+d.arch.IntWidth, "unexpected end-of-file in %d-byte padding-before-int", padding)
+ }
+ data = data[padding:]
+
+ if len(data) < d.arch.IntWidth {
+ return d.binErrorf(d.arch.IntWidth, "unexpected end-of-file in %d-byte int", d.arch.IntWidth)
+ }
+
+ switch d.arch.IntWidth {
+ case 4:
+ *obj = Int(int32(d.arch.ByteOrder.Uint32(data)))
+ case 8:
+ *obj = Int(d.arch.ByteOrder.Uint64(data))
+ }
+ d.pos += padding + d.arch.IntWidth
+ 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 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(d.arch.ByteOrder.Uint64(data))
+ 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
+}