From 02003547bc96b3758355f0af0275170da1dba942 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sun, 26 Jan 2020 21:46:56 -0500 Subject: wip --- rrdformat/sniff.go | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 rrdformat/sniff.go (limited to 'rrdformat/sniff.go') diff --git a/rrdformat/sniff.go b/rrdformat/sniff.go new file mode 100644 index 0000000..f8f3397 --- /dev/null +++ b/rrdformat/sniff.go @@ -0,0 +1,125 @@ +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) + } + 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) + } + null += 4 + h.Version = data[4:null] + switch string(h.Version) { + case "0003": + case "0004": + case "0005": + default: + } + + // 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 + magicFloat := float64(8.642135e130) + floatAddrPacked := null + 1 + 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 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 + 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) + 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 + 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 + default: + return newBinError("failed to sniff byte-order and float-alignment", + data, floatAddrPacked, floatAddr64+h.FloatWidth-floatAddrPacked) + } + + switch h.FloatAlign { + case 4: + // Assume that if floats are only 32-bit aligned, then everything is 32-bit + h.IntWidth = 4 + h.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. + + // (The following heuristic is borrowed from javascriptRRD, and adjusted to also work with big-endian.) + // + // The next 2 things after the float_cookie are ds_cnt and rra_cnt (both 'unsigned long'--which may be + // either 32 or 64 bit). We'll inspect the bytes a bit to guess how long a long is. + // + // By assuming + // 1. ds_cnt <= math.MaxUint32 + // 2l. rra_cnt > 0 (relevant if little-endian) + // 2b. ds_cnt > 0 (relevant if big-endian) + // we can inspect the 4 bytes (marked "big" or "little" below) that are either + // - the most significant bits of 64-bit ds_cnt, or + // - the entirety of 32-bit rra_cnt (if littlen-endian) or 32-bit ds_cnt (if big-endian) + // If we see that those 4 bytes are all 0, then we assume that it's part of a 64-bit ds_cnt. + // + // | | | | | | | big | little | + // |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31| + // 32 | R| R| D|\0| 0| 0| 0| 3|\0| |<----doublecookie----->|<--ds_cnt->|<-rra_cnt->| + // 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) + } + 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 + } else { + h.IntWidth = 4 + h.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{} + +var _ xml.Marshaler = &Header{} + +//var _ xml.Unmarshaler = &Header{} -- cgit v1.2.3