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.NewDecoder(arch, data).Decode(&header, ""); err != nil { return rrdbinary.Architecture{}, err } // 1. File header magic number if header.Cookie != "RRD" { return rrdbinary.Architecture{}, rrdbinary.NewBinError("not an RRD file: wrong magic number", data, 0, 4) } // 1. File format version string switch header.Version { case RRD_VERSION1, RRD_VERSION2, RRD_VERSION3, RRD_VERSION4, RRD_VERSION5: // do nothing default: return rrdbinary.Architecture{}, rrdbinary.NewBinError(fmt.Sprintf("can't handle RRD file version %q", header.Version), data, 4, 5) } // 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. arch.DoubleWidth = 8 magicFloat := float64(8.642135e130) floatAddrPacked := 9 floatAddr32 := ((floatAddrPacked + 3) / 4) * 4 floatAddr64 := ((floatAddrPacked + 7) / 8) * 8 var restOffset int switch { case len(data) < floatAddr32+arch.DoubleWidth: return rrdbinary.Architecture{}, rrdbinary.NewBinError("unexpected end-of-file", data, floatAddrPacked, floatAddr64+arch.DoubleWidth-floatAddrPacked) case math.Float64frombits(binary.LittleEndian.Uint64(data[floatAddr32:])) == magicFloat: arch.ByteOrder = binary.LittleEndian arch.DoubleAlign = 4 restOffset = floatAddr32 + arch.DoubleWidth case math.Float64frombits(binary.BigEndian.Uint64(data[floatAddr32:])) == magicFloat: arch.ByteOrder = binary.BigEndian arch.DoubleAlign = 4 restOffset = floatAddr32 + arch.DoubleWidth case len(data) < floatAddr64+arch.DoubleWidth: return rrdbinary.Architecture{}, rrdbinary.NewBinError("unexpected end-of-file", data, floatAddrPacked, floatAddr64+arch.DoubleWidth-floatAddrPacked) case math.Float64frombits(binary.LittleEndian.Uint64(data[floatAddr64:])) == magicFloat: arch.ByteOrder = binary.LittleEndian arch.DoubleAlign = 8 restOffset = floatAddr64 + arch.DoubleWidth case math.Float64frombits(binary.BigEndian.Uint64(data[floatAddr64:])) == magicFloat: arch.ByteOrder = binary.BigEndian arch.DoubleAlign = 8 restOffset = floatAddr64 + arch.DoubleWidth default: return rrdbinary.Architecture{}, rrdbinary.NewBinError("failed to sniff byte-order and float-alignment", data, floatAddrPacked, floatAddr64+arch.DoubleWidth-floatAddrPacked) } // 5, 6. ds_cnt, rra_cnt switch arch.DoubleAlign { case 4: // Assume that if floats are only 32-bit aligned, then everything is 32-bit arch.LongWidth = 4 arch.LongAlign = 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 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 }[arch.ByteOrder] if arch.ByteOrder.Uint32(data[offset:]) == 0 { arch.LongWidth = 8 arch.LongAlign = 8 } else { arch.LongWidth = 4 arch.LongAlign = 4 } } // The above just os happens that DoubleXXX >= LongXXX, so we // can just set the Unival stuff to the Double Stuff. arch.UnivalWidth = arch.DoubleWidth // max(DoubleWidth, LongWidth) arch.UnivalAlign = arch.DoubleAlign // max(DoubleAlign, LongAlign) // 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.LongWidth arch.TimeAlign = arch.LongAlign // FIXME: Figure out how to sniff the sizeof(short). // // javascriptRRD doesn't deal with this at all (it only comes // up in parsing the params for DST_CDEF). // // For now, just assume it's sizeof(long)/2, which is true on // i686, x86_64, and arm. (It is not true on alpha or ia64.) arch.ShortWidth = arch.LongWidth / 2 arch.ShortAlign = arch.LongAlign / 2 return arch, nil }