summaryrefslogtreecommitdiff
path: root/rrdformat/sniff.go
blob: f8f3397363760caf84ea7c7404a4355155d8e318 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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{}