From 1f5dbbbab170bc7f6dd2d47c7aface716ecb294c Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sun, 26 Jan 2020 14:11:07 -0500 Subject: wip --- rrdformat/errors_binary.go | 75 ------------ rrdformat/errors_binary_test.go | 34 ------ rrdformat/format.go | 41 ++++++- rrdformat/rrdbinary/errors.go | 100 ++++++++++++++++ rrdformat/rrdbinary/errors_test.go | 34 ++++++ rrdformat/rrdbinary/types.go | 27 +++++ rrdformat/rrdbinary/unmarshal.go | 240 +++++++++++++++++++++++++++++++++++++ rrdformat/rrdbinary/util.go | 7 ++ 8 files changed, 444 insertions(+), 114 deletions(-) delete mode 100644 rrdformat/errors_binary.go delete mode 100644 rrdformat/errors_binary_test.go create mode 100644 rrdformat/rrdbinary/errors.go create mode 100644 rrdformat/rrdbinary/errors_test.go create mode 100644 rrdformat/rrdbinary/types.go create mode 100644 rrdformat/rrdbinary/unmarshal.go create mode 100644 rrdformat/rrdbinary/util.go diff --git a/rrdformat/errors_binary.go b/rrdformat/errors_binary.go deleted file mode 100644 index 7329927..0000000 --- a/rrdformat/errors_binary.go +++ /dev/null @@ -1,75 +0,0 @@ -package rrdformat - -import ( - "fmt" - "io" -) - -type BinaryError struct { - msg string - ctxPos int - ctxDat []byte - ctxEOF bool -} - -func newBinError(msg string, ctxFile []byte, ctxStart, ctxLen int) error { - if ctxStart+ctxLen > len(ctxFile) { - ctxLen = len(ctxFile) - ctxStart - } - return BinaryError{ - msg: msg, - ctxPos: ctxStart, - ctxDat: ctxFile[ctxStart : ctxStart+ctxLen], - ctxEOF: ctxStart+ctxLen == len(ctxFile), - } -} - -func (e BinaryError) Error() string { - return "invalid RRD: " + e.msg -} - -var cAsciiEscapes = map[byte]byte{ - 0x00: '0', - 0x07: 'a', - 0x08: 'b', - 0x09: 't', - 0x0A: 'n', - 0x0B: 'v', - 0x0C: 'f', - 0x0D: 'r', -} - -func (e BinaryError) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - io.WriteString(s, e.Error()) - if s.Flag('+') { - fmt.Fprintf(s, "\n\tat byte %d:", e.ctxPos) - io.WriteString(s, "\n\t\tascii:") - for _, byte := range e.ctxDat { - if ' ' <= byte && byte <= '~' { - fmt.Fprintf(s, " %c", byte) - } else if c, ok := cAsciiEscapes[byte]; ok { - fmt.Fprintf(s, " \\%c", c) - } else { - io.WriteString(s, " ??") - } - } - if e.ctxEOF { - io.WriteString(s, " ") - } - io.WriteString(s, "\n\t\thex :") - for _, byte := range e.ctxDat { - fmt.Fprintf(s, " %02x", byte) - } - if e.ctxEOF { - io.WriteString(s, " ") - } - io.WriteString(s, "\n") - } - case 's': - io.WriteString(s, e.Error()) - case 'q': - fmt.Fprintf(s, "%q", e.Error()) - } -} diff --git a/rrdformat/errors_binary_test.go b/rrdformat/errors_binary_test.go deleted file mode 100644 index f420a1b..0000000 --- a/rrdformat/errors_binary_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package rrdformat - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestBinaryError(t *testing.T) { - assert := assert.New(t) - - bad404 := []byte(` - hex : 52 -`) -} 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" @@ -20,6 +17,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 diff --git a/rrdformat/rrdbinary/errors.go b/rrdformat/rrdbinary/errors.go new file mode 100644 index 0000000..984e0a5 --- /dev/null +++ b/rrdformat/rrdbinary/errors.go @@ -0,0 +1,100 @@ +package rrdbinary + +import ( + "fmt" + "io" +) + +// A BinaryError is an error that results from malformed binary input. +type BinaryError struct { + msg string + ctxPos int + ctxDat []byte + ctxEOF bool +} + +func newBinError(msg string, ctxFile []byte, ctxStart, ctxLen int) error { + if ctxStart+ctxLen > len(ctxFile) { + ctxLen = len(ctxFile) - ctxStart + } + return BinaryError{ + msg: msg, + ctxPos: ctxStart, + ctxDat: ctxFile[ctxStart : ctxStart+ctxLen], + ctxEOF: ctxStart+ctxLen == len(ctxFile), + } +} + +func (e BinaryError) Error() string { + return "invalid RRD: " + e.msg +} + +var cAsciiEscapes = map[byte]byte{ + 0x00: '0', + 0x07: 'a', + 0x08: 'b', + 0x09: 't', + 0x0A: 'n', + 0x0B: 'v', + 0x0C: 'f', + 0x0D: 'r', +} + +func (e BinaryError) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + io.WriteString(s, e.Error()) + if s.Flag('+') { + fmt.Fprintf(s, "\n\tat byte %d:", e.ctxPos) + io.WriteString(s, "\n\t\tascii:") + for _, byte := range e.ctxDat { + if ' ' <= byte && byte <= '~' { + fmt.Fprintf(s, " %c", byte) + } else if c, ok := cAsciiEscapes[byte]; ok { + fmt.Fprintf(s, " \\%c", c) + } else { + io.WriteString(s, " ??") + } + } + if e.ctxEOF { + io.WriteString(s, " ") + } + io.WriteString(s, "\n\t\thex :") + for _, byte := range e.ctxDat { + fmt.Fprintf(s, " %02x", byte) + } + if e.ctxEOF { + io.WriteString(s, " ") + } + io.WriteString(s, "\n") + } + case 's': + io.WriteString(s, e.Error()) + case 'q': + 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/rrdbinary/errors_test.go b/rrdformat/rrdbinary/errors_test.go new file mode 100644 index 0000000..f65929b --- /dev/null +++ b/rrdformat/rrdbinary/errors_test.go @@ -0,0 +1,34 @@ +package rrdbinary + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBinaryError(t *testing.T) { + assert := assert.New(t) + + bad404 := []byte(` + hex : 52 +`) +} 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") + } +} -- cgit v1.2.3