diff options
-rw-r--r-- | rrdformat/format.go | 60 | ||||
-rw-r--r-- | rrdformat/rrdbinary/errors.go | 2 | ||||
-rw-r--r-- | rrdformat/rrdbinary/errors_test.go | 4 | ||||
-rw-r--r-- | rrdformat/rrdbinary/types.go | 2 | ||||
-rw-r--r-- | rrdformat/rrdbinary/unmarshal.go | 6 | ||||
-rw-r--r-- | rrdformat/sniff.go | 141 |
6 files changed, 118 insertions, 97 deletions
diff --git a/rrdformat/format.go b/rrdformat/format.go index 71bd966..8898ffd 100644 --- a/rrdformat/format.go +++ b/rrdformat/format.go @@ -1,11 +1,8 @@ package rrdformat import ( - "bytes" - "encoding" - "encoding/binary" + //"encoding" "encoding/xml" - "math" "git.lukeshu.com/go/librrd/rrdformat/rrdbinary" ) @@ -48,12 +45,12 @@ const XMLNS = "https://oss.oetiker.ch/rrdtool/rrdtool-dump.xml" type RRDValue = rrdbinary.Float type Header struct { - Cookie rrdbinary.String `rrdbinary:"size=3" xml:"-"` - Version rrdbinary.String `rrdbinary:"size=4" xml:"version"` + Cookie rrdbinary.String `rrdbinary:"size=4" xml:"-"` + Version rrdbinary.String `rrdbinary:"size=5" xml:"version"` FloatCookie rrdbinary.Float `xml:"-"` DSCnt rrdbinary.Uint `xml:"-"` RRACnt rrdbinary.Uint `xml:"-"` - DPDStep rrdbinary.Uint `xml:"step"` + PDPStep rrdbinary.Uint `xml:"step"` Parameters [10]rrdbinary.Unival `xml:"-"` } @@ -66,10 +63,10 @@ type DSDef struct { type RRADef struct { CFName rrdbinary.String `rrdbinary:"size=20"` RowCnt rrdbinary.Uint - PDPCnt rrdBinary.Uint + PDPCnt rrdbinary.Uint } -type Timestamp struct { +type Time struct { Sec rrdbinary.Time Usec rrdbinary.Int // signed, but always >= 0 } @@ -92,24 +89,41 @@ type RRDv0004 = RRDv0003 type RRDv0003 struct { Header Header - DSDefs []DSDef - RRADefs []RRADef - LastUpdated Timestamp - PDPPrep TODO - CPDPrep TODO - RRAPtr TODO - Values []RRDValue + DSDefs []DSDef // .Header.DSCnt + RRADefs []RRADef // .Header.RRACnt + LastUpdated Time + PDPPrep []PDPPrep // .Header.DSCnt + CDPPrep []CDPPrep // .Header.DSCnt * .Header.RRACnt + RRAPtr []RRAPtr // .Header.RRACnt + Values []RRDValue // Σ .RRADefs[i].RowCnt*.Header.DsCnt } type RRDv0002 = RRDv0001 type RRDv0001 struct { Header Header - DSDefs []DSDef - RRADefs []RRADef - LastUpdated rrdbinary.Timestamp - PDPPrep TODO - CPDPrep TODO - RRAPtr TODO - Values []RRDValue + DSDefs []DSDef // .Header.DSCnt + RRADefs []RRADef // .Header.RRACnt + LastUpdated rrdbinary.Time + PDPPrep []PDPPrep // .Header.DSCnt + CDPPrep []CDPPrep // .Header.DSCnt * .Header.RRACnt + RRAPtr []RRAPtr // .Header.RRACnt + Values []RRDValue // Σ .RRADefs[i].RowCnt*.Header.DsCnt } + +func (h *Header) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + if err := e.EncodeElement(h.Version, xml.StartElement{Name: xml.Name{Local: "version", Space: XMLNS}}); err != nil { + return err + } + if err := e.EncodeElement(h.PDPStep, xml.StartElement{Name: xml.Name{Local: "step", Space: XMLNS}}); err != nil { + return err + } + return nil +} + +//var _ encoding.BinaryMarshaler = &Header{} +//var _ encoding.BinaryUnmarshaler = &Header{} + +var _ xml.Marshaler = &Header{} + +//var _ xml.Unmarshaler = &Header{} diff --git a/rrdformat/rrdbinary/errors.go b/rrdformat/rrdbinary/errors.go index 984e0a5..1e61af0 100644 --- a/rrdformat/rrdbinary/errors.go +++ b/rrdformat/rrdbinary/errors.go @@ -13,7 +13,7 @@ type BinaryError struct { ctxEOF bool } -func newBinError(msg string, ctxFile []byte, ctxStart, ctxLen int) error { +func NewBinError(msg string, ctxFile []byte, ctxStart, ctxLen int) error { if ctxStart+ctxLen > len(ctxFile) { ctxLen = len(ctxFile) - ctxStart } diff --git a/rrdformat/rrdbinary/errors_test.go b/rrdformat/rrdbinary/errors_test.go index f65929b..86ae809 100644 --- a/rrdformat/rrdbinary/errors_test.go +++ b/rrdformat/rrdbinary/errors_test.go @@ -11,7 +11,7 @@ func TestBinaryError(t *testing.T) { assert := assert.New(t) bad404 := []byte(`<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"`) - err := newBinError("not an RRD file: wrong magic number", bad404, 0, 4) + err := NewBinError("not an RRD file: wrong magic number", bad404, 0, 4) assert.Equal(err.Error(), `invalid RRD: not an RRD file: wrong magic number`) assert.Equal(fmt.Sprintf("%v", err), `invalid RRD: not an RRD file: wrong magic number`) assert.Equal(fmt.Sprintf("%q", err), `"invalid RRD: not an RRD file: wrong magic number"`) @@ -22,7 +22,7 @@ func TestBinaryError(t *testing.T) { `) badShort := []byte{'R'} - err = newBinError("not an RRD file: wrong magic number", badShort, 0, 4) + err = NewBinError("not an RRD file: wrong magic number", badShort, 0, 4) assert.Equal(err.Error(), `invalid RRD: not an RRD file: wrong magic number`) assert.Equal(fmt.Sprintf("%v", err), `invalid RRD: not an RRD file: wrong magic number`) assert.Equal(fmt.Sprintf("%q", err), `"invalid RRD: not an RRD file: wrong magic number"`) diff --git a/rrdformat/rrdbinary/types.go b/rrdformat/rrdbinary/types.go index 36e89fa..a036d5d 100644 --- a/rrdformat/rrdbinary/types.go +++ b/rrdformat/rrdbinary/types.go @@ -24,7 +24,7 @@ type Architecture struct { type String string // \0-terminated type Float float64 // 8 bytes type Uint uint64 // 4 or 8 bytes -type _Int int64 // 4 or 8 bytes +type Int int64 // 4 or 8 bytes type Unival uint64 // 8 bytes type Time int64 // 4 or 8 bytes, only has second-precision diff --git a/rrdformat/rrdbinary/unmarshal.go b/rrdformat/rrdbinary/unmarshal.go index e0f8988..1cfee34 100644 --- a/rrdformat/rrdbinary/unmarshal.go +++ b/rrdformat/rrdbinary/unmarshal.go @@ -29,7 +29,7 @@ type unmarshaler struct { } func (d *unmarshaler) binError(ctxLen int, msg string) error { - return newBinError(msg, d.data, d.pos, ctxLen) + return NewBinError(msg, d.data, d.pos, ctxLen) } func (d *unmarshaler) binErrorf(ctxLen int, format string, a ...interface{}) error { @@ -44,7 +44,7 @@ func (d *unmarshaler) unmarshal(v reflect.Value, tag string) error { return d.unmarshalFloat(v, tag) case reflect.TypeOf(Uint(0)): return d.unmarshalUint(v, tag) - case reflect.TypeOf(_Int(0)): + case reflect.TypeOf(Int(0)): return d.unmarshalInt(v, tag) case reflect.TypeOf(Unival(0)): return d.unmarshalUnival(v, tag) @@ -213,7 +213,7 @@ func (d *unmarshaler) unmarshalUint(v reflect.Value, tag string) error { } func (d *unmarshaler) unmarshalInt(v reflect.Value, tag string) error { - panicUnless(v.Type() == reflect.TypeOf(_Int(0))) + panicUnless(v.Type() == reflect.TypeOf(Int(0))) panicUnless(v.CanSet()) if d.arch.IntWidth != 4 && d.arch.IntWidth != 8 { diff --git a/rrdformat/sniff.go b/rrdformat/sniff.go index f8f3397..db48959 100644 --- a/rrdformat/sniff.go +++ b/rrdformat/sniff.go @@ -1,71 +1,81 @@ -func (h *Header) UnmarshalBinary(data []byte) error { - // magic number cookie - if !bytes.HasPrefix(data, []byte("RRD\x00")) { - return newBinError("not an RRD file: wrong magic number", data, 0, 4) +package rrdformat + +import ( + "encoding/binary" + "fmt" + "math" + + "git.lukeshu.com/go/librrd/rrdformat/rrdbinary" +) + +func SniffArchitecture(data []byte) (rrdbinary.Architecture, error) { + var arch rrdbinary.Architecture + + var header struct { + Cookie rrdbinary.String `rrdbinary:"size=4" xml:"-"` + Version rrdbinary.String `rrdbinary:"size=5" xml:"version"` + } + if err := rrdbinary.Unmarshal(arch, data, &header); err != nil { + return rrdbinary.Architecture{}, err } - h.Cookie = data[0:4] - // version string - null := bytes.IndexByte(data[4:], 0) - if null < 0 { - return newBinError("no null-terminator on version string", data, 4, 5) + // 1. File header magic number + if header.Cookie != "RRD" { + return rrdbinary.Architecture{}, rrdbinary.NewBinError("not an RRD file: wrong magic number", data, 0, 4) } - null += 4 - h.Version = data[4:null] - switch string(h.Version) { - case "0003": - case "0004": - case "0005": + + // 1. File format version string + switch header.Version { + case "0001", "0002", "0003", "0004", "0005": + // do nothing default: + return rrdbinary.Architecture{}, rrdbinary.NewBinError(fmt.Sprintf("can't handle RRD file version %q", header.Version), data, 4, 5) } - // float cookie + // 3. float cookie // // Assume IEEE 754 doubles. C doesn't assume 754 doubles, but anything that doesn't use 754 doubles is exotic // enough that I'm OK saying "you're going to need to use `rrdtool dump`". This lets us assume that: // - a 'double' is 8 bytes wide // - the value will be exactly equal, and we don't need to worry about weird rounding. - h.FloatWidth = 8 + arch.FloatWidth = 8 magicFloat := float64(8.642135e130) - floatAddrPacked := null + 1 + floatAddrPacked := 9 floatAddr32 := ((floatAddrPacked + 3) / 4) * 4 floatAddr64 := ((floatAddrPacked + 7) / 8) * 8 var restOffset int switch { - case len(data) < floatAddr32+h.FloatWidth: - return newBinError("unexpected end of file", data, floatAddrPacked, floatAddr64+h.FloatWidth-floatAddrPacked) + case len(data) < floatAddr32+arch.FloatWidth: + return rrdbinary.Architecture{}, rrdbinary.NewBinError("unexpected end-of-file", data, floatAddrPacked, floatAddr64+arch.FloatWidth-floatAddrPacked) case math.Float64frombits(binary.LittleEndian.Uint64(data[floatAddr32:])) == magicFloat: - h.FloatCookie = data[floatAddr32 : floatAddr32+h.FloatWidth] - h.ByteOrder = binary.LittleEndian - h.FloatAlign = 4 - restOffset = floatAddr32 + h.FloatWidth + arch.ByteOrder = binary.LittleEndian + arch.FloatAlign = 4 + restOffset = floatAddr32 + arch.FloatWidth case math.Float64frombits(binary.BigEndian.Uint64(data[floatAddr32:])) == magicFloat: - h.FloatCookie = data[floatAddr32 : floatAddr32+h.FloatWidth] - h.ByteOrder = binary.BigEndian - h.FloatAlign = 4 - restOffset = floatAddr32 + h.FloatWidth - case len(data) < floatAddr64+h.FloatWidth: - return newBinError("unexpected end of file", data, floatAddrPacked, floatAddr64+h.FloatWidth-floatAddrPacked) + arch.ByteOrder = binary.BigEndian + arch.FloatAlign = 4 + restOffset = floatAddr32 + arch.FloatWidth + case len(data) < floatAddr64+arch.FloatWidth: + return rrdbinary.Architecture{}, rrdbinary.NewBinError("unexpected end-of-file", data, floatAddrPacked, floatAddr64+arch.FloatWidth-floatAddrPacked) case math.Float64frombits(binary.LittleEndian.Uint64(data[floatAddr64:])) == magicFloat: - h.FloatCookie = data[floatAddr64 : floatAddr64+h.FloatWidth] - h.ByteOrder = binary.LittleEndian - h.FloatAlign = 8 - restOffset = floatAddr64 + h.FloatWidth + arch.ByteOrder = binary.LittleEndian + arch.FloatAlign = 8 + restOffset = floatAddr64 + arch.FloatWidth case math.Float64frombits(binary.BigEndian.Uint64(data[floatAddr64:])) == magicFloat: - h.FloatCookie = data[floatAddr64 : floatAddr64+h.FloatWidth] - h.ByteOrder = binary.BigEndian - h.FloatAlign = 8 - restOffset = floatAddr64 + h.FloatWidth + arch.ByteOrder = binary.BigEndian + arch.FloatAlign = 8 + restOffset = floatAddr64 + arch.FloatWidth default: - return newBinError("failed to sniff byte-order and float-alignment", - data, floatAddrPacked, floatAddr64+h.FloatWidth-floatAddrPacked) + return rrdbinary.Architecture{}, rrdbinary.NewBinError("failed to sniff byte-order and float-alignment", + data, floatAddrPacked, floatAddr64+arch.FloatWidth-floatAddrPacked) } - switch h.FloatAlign { + // 5, 6. ds_cnt, rra_cnt + switch arch.FloatAlign { case 4: // Assume that if floats are only 32-bit aligned, then everything is 32-bit - h.IntWidth = 4 - h.IntAlign = 4 + arch.IntWidth = 4 + arch.IntAlign = 4 case 8: // If floats are 64-bit aligned, then this might be all-in on 64-bit, or it might 32-bit ints. @@ -89,37 +99,34 @@ func (h *Header) UnmarshalBinary(data []byte) error { // 64le | R| R| D|\0| 0| 0| 0| 3|\0| |<----doublecookie----->|<1111----ds_cnt---0000>| // 64be | R| R| D|\0| 0| 0| 0| 3|\0| |<----doublecookie----->|<0000----ds_cnt---1111>| if len(data) < restOffset+8 { - return newBinError("unexpected end of file", data, restOffset, 8) + return rrdbinary.Architecture{}, rrdbinary.NewBinError("unexpected end of file", data, restOffset, 8) } offset := map[binary.ByteOrder]int{ binary.BigEndian: restOffset, // 24 in the above diagram binary.LittleEndian: restOffset + 4, // 28 in the above diagram - }[h.ByteOrder] - if h.ByteOrder.Uint32(data[offset:]) == 0 { - h.IntWidth = 8 - h.IntAlign = 8 + }[arch.ByteOrder] + if arch.ByteOrder.Uint32(data[offset:]) == 0 { + arch.IntWidth = 8 + arch.IntAlign = 8 } else { - h.IntWidth = 4 - h.IntAlign = 4 + arch.IntWidth = 4 + arch.IntAlign = 4 } } - return nil -} - -func (h *Header) MarshalXML(e *xml.Encoder, start xml.StartElement) error { - if err := e.EncodeElement(h.Version, xml.StartElement{Name: xml.Name{Local: "version", Space: XMLNS}}); err != nil { - return err - } - if err := e.EncodeElement(h.PDPStep, xml.StartElement{Name: xml.Name{Local: "step", Space: XMLNS}}); err != nil { - return err - } - return nil -} - -//var _ encoding.BinaryMarshaler = &Header{} -var _ encoding.BinaryUnmarshaler = &Header{} + // The above just os happens that FloatXXX >= IntXXX, so we + // can just set the Unival stuff to the Float Stuff. + arch.UnivalWidth = arch.FloatWidth // max(FloatWidth, IntWidth) + arch.UnivalAlign = arch.FloatAlign // max(FloatAlign, IntAlign) -var _ xml.Marshaler = &Header{} + // FIXME: Figure out how to sniff the sizeof(time_t). + // + // javascriptRRD assumes that it's the same as sizeof(long), + // which his historically been true, but + // - isn't true of proprietary 32-bit Unixen that are 2038-safe + // - isn't true of the Linux kernel with the x32 ABI + arch.TimeWidth = arch.IntWidth + arch.TimeAlign = arch.IntAlign -//var _ xml.Unmarshaler = &Header{} + return arch, nil +} |