From 496fca7357463f6ca05acab688018e8a1dc11855 Mon Sep 17 00:00:00 2001
From: Luke Shumaker <lukeshu@lukeshu.com>
Date: Mon, 27 Jan 2020 19:14:44 -0500
Subject: rrdbinary: Switch to a interface-based incremental decoder

---
 rrdformat/rrdbinary/types.go     |  10 ++++
 rrdformat/rrdbinary/unmarshal.go | 122 ++++++++++++++++-----------------------
 2 files changed, 59 insertions(+), 73 deletions(-)

(limited to 'rrdformat')

diff --git a/rrdformat/rrdbinary/types.go b/rrdformat/rrdbinary/types.go
index 006af3c..0bf4913 100644
--- a/rrdformat/rrdbinary/types.go
+++ b/rrdformat/rrdbinary/types.go
@@ -31,3 +31,13 @@ type EOF struct{}  // 0 bytes
 
 func (u Unival) AsUint64() uint64   { return uint64(u) }
 func (u Unival) AsFloat64() float64 { return math.Float64frombits(uint64(u)) }
+
+// Statically assert that each of the above types implements the
+// 'unmarshaler' interface.
+var _ unmarshaler = func() *String { return nil }()
+var _ unmarshaler = func() *Float { return nil }()
+var _ unmarshaler = func() *Uint { return nil }()
+var _ unmarshaler = func() *Int { return nil }()
+var _ unmarshaler = func() *Unival { return nil }()
+var _ unmarshaler = func() *Time { return nil }()
+var _ unmarshaler = func() *EOF { return nil }()
diff --git a/rrdformat/rrdbinary/unmarshal.go b/rrdformat/rrdbinary/unmarshal.go
index 142ee9f..00f1222 100644
--- a/rrdformat/rrdbinary/unmarshal.go
+++ b/rrdformat/rrdbinary/unmarshal.go
@@ -9,62 +9,54 @@ import (
 	"strings"
 )
 
+type Decoder struct {
+	arch Architecture
+	pos  int
+	data []byte
+}
+
+type unmarshaler interface {
+	unmarshalRRD(d *Decoder, tag string) error
+}
+
 func Unmarshal(arch Architecture, data []byte, ptr interface{}) error {
 	ptrValue := reflect.ValueOf(ptr)
 	if ptrValue.Kind() != reflect.Ptr {
 		return typeErrorf("ptr is a %v, not a pointer", ptrValue.Kind())
 	}
-	decoder := &unmarshaler{
+	decoder := &Decoder{
 		arch: arch,
 		pos:  0,
 		data: data,
 	}
-	return decoder.unmarshal(ptrValue, "")
+	return decoder.Decode(ptrValue, "")
 }
 
-type unmarshaler struct {
-	arch Architecture
-	pos  int
-	data []byte
-}
-
-func (d *unmarshaler) binError(ctxLen int, msg string) error {
+func (d *Decoder) binError(ctxLen int, msg string) error {
 	return NewBinError(msg, d.data, d.pos, ctxLen)
 }
 
-func (d *unmarshaler) binErrorf(ctxLen int, format string, a ...interface{}) error {
+func (d *Decoder) binErrorf(ctxLen int, format string, a ...interface{}) error {
 	return d.binError(ctxLen, fmt.Sprintf(format, a...))
 }
 
-func (d *unmarshaler) unmarshal(v reflect.Value, tag string) error {
-	switch v.Type() {
-	case reflect.TypeOf(String("")):
-		return d.unmarshalString(v, tag)
-	case reflect.TypeOf(Float(0)):
-		return d.unmarshalFloat(v, tag)
-	case reflect.TypeOf(Uint(0)):
-		return d.unmarshalUint(v, tag)
-	case reflect.TypeOf(Int(0)):
-		return d.unmarshalInt(v, tag)
-	case reflect.TypeOf(Unival(0)):
-		return d.unmarshalUnival(v, tag)
-	case reflect.TypeOf(Time(0)):
-		return d.unmarshalTime(v, tag)
-	case reflect.TypeOf(EOF{}):
-		return d.unmarshalEOF(v, tag)
-	default:
-		switch v.Type().Kind() {
-		case reflect.Struct:
-			return d.unmarshalStruct(v, tag)
-		case reflect.Array, reflect.Slice:
-			return d.unmarshalList(v, tag)
-		default:
-			return typeErrorf("invalid type for rrdbinary.Unmarshal: %v", v.Type())
+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.Decode: %v", v.Type())
+	}
 }
 
-func (d *unmarshaler) unmarshalStruct(v reflect.Value, tag string) error {
+func (d *Decoder) decodeStruct(v reflect.Value, tag string) error {
 	panicUnless(v.Kind() == reflect.Struct)
 	panicUnless(v.CanSet())
 
@@ -78,29 +70,26 @@ func (d *unmarshaler) unmarshalStruct(v reflect.Value, tag string) error {
 		if tag == "-" {
 			continue
 		}
-		if err := d.unmarshal(v.Field(i), tag); err != nil {
+		if err := d.Decode(v.Field(i), tag); err != nil {
 			return fmt.Errorf("field %s: %w", fieldInfo.Name, err)
 		}
 	}
 	return nil
 }
 
-func (d *unmarshaler) unmarshalList(v reflect.Value, tag string) error {
+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.unmarshal(v.Index(i), tag); err != nil {
+		if err := d.Decode(v.Index(i), tag); err != nil {
 			return fmt.Errorf("index %d: %w", i, err)
 		}
 	}
 	return nil
 }
 
-func (d *unmarshaler) unmarshalString(v reflect.Value, tag string) error {
-	panicUnless(v.Type() == reflect.TypeOf(String("")))
-	panicUnless(v.CanSet())
-
+func (obj *String) unmarshalRRD(d *Decoder, tag string) error {
 	size := 0
 	switch {
 	case tag == "":
@@ -142,15 +131,12 @@ func (d *unmarshaler) unmarshalString(v reflect.Value, tag string) error {
 		size = nul + 1
 	}
 
-	v.SetString(string(data[:nul]))
+	*obj = String(data[:nul])
 	d.pos += size
 	return nil
 }
 
-func (d *unmarshaler) unmarshalFloat(v reflect.Value, tag string) error {
-	panicUnless(v.Type() == reflect.TypeOf(Float(0)))
-	panicUnless(v.CanSet())
-
+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)
 	}
@@ -173,15 +159,12 @@ func (d *unmarshaler) unmarshalFloat(v reflect.Value, tag string) error {
 		return d.binErrorf(d.arch.FloatWidth, "unexpected end-of-file in %d-byte float", d.arch.FloatWidth)
 	}
 
-	v.SetFloat(math.Float64frombits(d.arch.ByteOrder.Uint64(data)))
+	*obj = Float(math.Float64frombits(d.arch.ByteOrder.Uint64(data)))
 	d.pos += padding + d.arch.FloatWidth
 	return nil
 }
 
-func (d *unmarshaler) unmarshalUint(v reflect.Value, tag string) error {
-	panicUnless(v.Type() == reflect.TypeOf(Uint(0)))
-	panicUnless(v.CanSet())
-
+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)
 	}
@@ -206,18 +189,15 @@ func (d *unmarshaler) unmarshalUint(v reflect.Value, tag string) error {
 
 	switch d.arch.IntWidth {
 	case 4:
-		v.SetUint(uint64(d.arch.ByteOrder.Uint32(data)))
+		*obj = Uint(d.arch.ByteOrder.Uint32(data))
 	case 8:
-		v.SetUint(d.arch.ByteOrder.Uint64(data))
+		*obj = Uint(d.arch.ByteOrder.Uint64(data))
 	}
 	d.pos += padding + d.arch.IntWidth
 	return nil
 }
 
-func (d *unmarshaler) unmarshalInt(v reflect.Value, tag string) error {
-	panicUnless(v.Type() == reflect.TypeOf(Int(0)))
-	panicUnless(v.CanSet())
-
+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)
 	}
@@ -242,18 +222,15 @@ func (d *unmarshaler) unmarshalInt(v reflect.Value, tag string) error {
 
 	switch d.arch.IntWidth {
 	case 4:
-		v.SetInt(int64(int32(d.arch.ByteOrder.Uint32(data))))
+		*obj = Int(int32(d.arch.ByteOrder.Uint32(data)))
 	case 8:
-		v.SetInt(int64(d.arch.ByteOrder.Uint64(data)))
+		*obj = Int(d.arch.ByteOrder.Uint64(data))
 	}
 	d.pos += padding + d.arch.IntWidth
 	return nil
 }
 
-func (d *unmarshaler) unmarshalUnival(v reflect.Value, tag string) error {
-	panicUnless(v.Type() == reflect.TypeOf(Unival(0)))
-	panicUnless(v.CanSet())
-
+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)
 	}
@@ -276,15 +253,12 @@ func (d *unmarshaler) unmarshalUnival(v reflect.Value, tag string) error {
 		return d.binErrorf(d.arch.UnivalWidth, "unexpected end-of-file in %d-byte unival", d.arch.UnivalWidth)
 	}
 
-	v.SetUint(d.arch.ByteOrder.Uint64(data))
+	*obj = Unival(d.arch.ByteOrder.Uint64(data))
 	d.pos += padding + d.arch.UnivalWidth
 	return nil
 }
 
-func (d *unmarshaler) unmarshalTime(v reflect.Value, tag string) error {
-	panicUnless(v.Type() == reflect.TypeOf(Time(0)))
-	panicUnless(v.CanSet())
-
+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)
 	}
@@ -309,16 +283,18 @@ func (d *unmarshaler) unmarshalTime(v reflect.Value, tag string) error {
 
 	switch d.arch.TimeWidth {
 	case 4:
-		v.SetInt(int64(int32(d.arch.ByteOrder.Uint32(data))))
+		*obj = Time(int32(d.arch.ByteOrder.Uint32(data)))
 	case 8:
-		v.SetInt(int64(d.arch.ByteOrder.Uint64(data)))
+		*obj = Time(d.arch.ByteOrder.Uint64(data))
 	}
 	d.pos += padding + d.arch.TimeWidth
 	return nil
 }
 
-func (d *unmarshaler) unmarshalEOF(v reflect.Value, tag string) error {
-	panicUnless(v.Type() == reflect.TypeOf(EOF{}))
+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 {
-- 
cgit v1.2.3-2-g168b