summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2020-01-26 14:11:07 -0500
committerLuke Shumaker <lukeshu@lukeshu.com>2020-01-26 14:11:28 -0500
commit1f5dbbbab170bc7f6dd2d47c7aface716ecb294c (patch)
tree5b956d920455036b9ae7a675b4e497a76edbb314
parenta5cba20c1e3b0956737327ef9214fd2cbc221add (diff)
wip
-rw-r--r--rrdformat/format.go41
-rw-r--r--rrdformat/rrdbinary/errors.go (renamed from rrdformat/errors_binary.go)27
-rw-r--r--rrdformat/rrdbinary/errors_test.go (renamed from rrdformat/errors_binary_test.go)2
-rw-r--r--rrdformat/rrdbinary/types.go27
-rw-r--r--rrdformat/rrdbinary/unmarshal.go240
-rw-r--r--rrdformat/rrdbinary/util.go7
6 files changed, 337 insertions, 7 deletions
diff --git a/rrdformat/format.go b/rrdformat/format.go
index 0233a5c..7ea15b7 100644
--- a/rrdformat/format.go
+++ b/rrdformat/format.go
@@ -6,12 +6,9 @@ import (
"encoding/binary"
"encoding/xml"
"math"
-)
-
-type Unival uint64
-func (u Unival) AsUint64() uint64 { return uint64(u) }
-func (u Unival) AsFloat64() float64 { return math.Float64frombits(uint64(u)) }
+ "git.lukeshu.com/go/librrd/rrdformat/rrdbinary"
+)
const XMLNS = "https://oss.oetiker.ch/rrdtool/rrdtool-dump.xml"
@@ -21,6 +18,40 @@ const XMLNS = "https://oss.oetiker.ch/rrdtool/rrdtool-dump.xml"
// javascriptRRD:
// rrdFile.js: RRDHeader
type Header struct {
+ Cookie rrdbinary.String `rrdbinary:"size=3" xml:"-"`
+ Version rrdbinary.String `rrdbinary:"size=4" xml:"version"`
+ FloatCookie rrdbinary.Float `xml:"-"`
+ DSCnt rrdbinary.Uint `xml:"-"`
+ RRACnt rrdbinary.Uint `xml:"-"`
+ DPDStep rrdbinary.Uint `xml:"step"`
+ Parameters [10]rrdbinary.Unival `xml:"-"`
+}
+
+type DSDef struct {
+ DSName rrdbinary.String `rrdbinary:"size=20"`
+ DSType rrdbinary.String `rrdbinary:"size=20"`
+ Parameters [10]rrdbinary.Unival
+}
+
+type RRADef struct {
+ CFName rrdbinary.String `rrdbinary:"size=20"`
+ RowCnt rrdbinary.Uint
+ PDPCnt rrdBinary.Uint
+}
+
+type LiveHead struct {
+
+}
+
+// rrdtool:
+// rrd_format.h: rrd_t One single struct to hold all the others.
+type RRD struct {
+ DSDefs []DSDef
+ RRADefs []RRADef
+ LiveHead LiveHead
+}
+
+type Header struct {
// identification section
Cookie []byte // 4 bytes
Version []byte // 4 bytes (eh, we let the \0-terminator decided how long)
diff --git a/rrdformat/errors_binary.go b/rrdformat/rrdbinary/errors.go
index 7329927..984e0a5 100644
--- a/rrdformat/errors_binary.go
+++ b/rrdformat/rrdbinary/errors.go
@@ -1,10 +1,11 @@
-package rrdformat
+package rrdbinary
import (
"fmt"
"io"
)
+// A BinaryError is an error that results from malformed binary input.
type BinaryError struct {
msg string
ctxPos int
@@ -73,3 +74,27 @@ func (e BinaryError) Format(s fmt.State, verb rune) {
fmt.Fprintf(s, "%q", e.Error())
}
}
+
+// A TypeError is an error that results from Marshal or Unmmarshal
+// being passed a Go type that they cannot handle.
+type TypeError string
+
+func (e TypeError) Error() string {
+ return string(e)
+}
+
+func typeErrorf(format string, a ...interface{}) error {
+ return TypeError(fmt.Sprintf(format, a...))
+}
+
+// An ArchError is an error that results from Marshal or Unmmarshal
+// being passed an invalid Architecture.
+type ArchError string
+
+func (e ArchError) Error() string {
+ return "invalid rrdbinary.Architecture: "+string(e)
+}
+
+func archErrorf(format string, a ...interface{}) error {
+ return ArchError(fmt.Sprintf(format, a...))
+}
diff --git a/rrdformat/errors_binary_test.go b/rrdformat/rrdbinary/errors_test.go
index f420a1b..f65929b 100644
--- a/rrdformat/errors_binary_test.go
+++ b/rrdformat/rrdbinary/errors_test.go
@@ -1,4 +1,4 @@
-package rrdformat
+package rrdbinary
import (
"fmt"
diff --git a/rrdformat/rrdbinary/types.go b/rrdformat/rrdbinary/types.go
new file mode 100644
index 0000000..cbe3e2d
--- /dev/null
+++ b/rrdformat/rrdbinary/types.go
@@ -0,0 +1,27 @@
+package rrdbinary
+
+import (
+ "encoding/binary"
+ "math"
+)
+
+type Architecture struct {
+ ByteOrder binary.ByteOrder
+ // C `double`
+ FloatWidth int // always 8
+ FloatAlign int
+ // C `unsigned long`
+ UintWidth int
+ UintAlign int
+ // C `union { unsigned long; double; }`
+ UnivalWidth int // max(FloatWidth, IntWidth)
+ UnivalAlign int // max(FloatAlign, IntAlign)
+}
+
+type String string // \0-terminatd
+type Float float64 // 8 bytes
+type Uint uint64 // 4 or 8 bytes
+type Unival uint64 // 8 bytes
+
+func (u Unival) AsUint64() uint64 { return uint64(u) }
+func (u Unival) AsFloat64() float64 { return math.Float64frombits(uint64(u)) }
diff --git a/rrdformat/rrdbinary/unmarshal.go b/rrdformat/rrdbinary/unmarshal.go
new file mode 100644
index 0000000..be0b492
--- /dev/null
+++ b/rrdformat/rrdbinary/unmarshal.go
@@ -0,0 +1,240 @@
+package rrdbinary
+
+import (
+ "bytes"
+ "fmt"
+ "math"
+ "reflect"
+ "strconv"
+ "strings"
+)
+
+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{
+ arch: arch,
+ pos: 0,
+ data: data,
+ }
+ return decoder.unmarshal(ptrValue, "")
+}
+
+type unmarshaler struct {
+ arch Architecture
+ pos int
+ data []byte
+}
+
+func (d *unmarshaler) 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 {
+ 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(Unival(0)):
+ return d.unmarshalUnival(v, tag)
+ default:
+ switch v.Type().Kind() {
+ case reflect.Struct:
+ return d.unmarshalStruct(v, tag)
+ case reflect.Array:
+ return d.unmarshalArray(v, tag)
+ default:
+ return typeErrorf("invalid type for rrdbinary.Unmarshal: %v", v.Type())
+ }
+ }
+}
+
+func (d *unmarshaler) unmarshalStruct(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.unmarshal(v.Field(i), tag); err != nil {
+ return fmt.Errorf("field %s: %w", fieldInfo.Name, err)
+ }
+ }
+ return nil
+}
+
+func (d *unmarshaler) unmarshalArray(v reflect.Value, tag string) error {
+ panicUnless(v.Kind() == reflect.Array)
+ panicUnless(v.CanSet())
+
+ for i := 0; i < v.Len(); i++ {
+ if err := d.unmarshal(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())
+
+ 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
+ }
+
+ v.SetString(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())
+
+ 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)
+ }
+
+ v.SetFloat(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())
+
+ if d.arch.UintWidth != 4 && d.arch.UintWidth != 8 {
+ return archErrorf("rrdbinary does not support UintWidth=%d; only supports 4 or 8", d.arch.UintWidth)
+ }
+ if tag != "" {
+ return typeErrorf("invalid rrdbinary struct tag for uint: %q", tag)
+ }
+
+ data := d.data[d.pos:]
+
+ padding := 0
+ if d.pos%d.arch.UintAlign != 0 {
+ padding = d.arch.UintAlign - (d.pos % d.arch.UintAlign)
+ }
+ if len(data) < padding {
+ return d.binErrorf(padding+d.arch.UintWidth, "unexpected end-of-file in %d-byte padding-before-uint", padding)
+ }
+ data = data[padding:]
+
+ if len(data) < d.arch.UintWidth {
+ return d.binErrorf(d.arch.UintWidth, "unexpected end-of-file in %d-byte uint", d.arch.UintWidth)
+ }
+
+ switch d.arch.UintWidth {
+ case 4:
+ v.SetUint(uint64(d.arch.ByteOrder.Uint32(data)))
+ case 8:
+ v.SetUint(d.arch.ByteOrder.Uint64(data))
+ }
+ d.pos += padding + d.arch.UintWidth
+ return nil
+}
+
+func (d *unmarshaler) unmarshalUnival(v reflect.Value, tag string) error {
+ panicUnless(v.Type() == reflect.TypeOf(Unival(0)))
+ panicUnless(v.CanSet())
+
+ 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)
+ }
+
+ v.SetUint(d.arch.ByteOrder.Uint64(data))
+ d.pos += padding + d.arch.UnivalWidth
+ return nil
+}
diff --git a/rrdformat/rrdbinary/util.go b/rrdformat/rrdbinary/util.go
new file mode 100644
index 0000000..1ae2c57
--- /dev/null
+++ b/rrdformat/rrdbinary/util.go
@@ -0,0 +1,7 @@
+package rrdbinary
+
+func panicUnless(ok bool) {
+ if !ok {
+ panic("assertion failed")
+ }
+}