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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
|
package rrdformat
import (
"bytes"
"encoding"
"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)) }
const XMLNS = "https://oss.oetiker.ch/rrdtool/rrdtool-dump.xml"
// rrdtool:
// rrd_format.h: stat_head_t -- static header of the database
//
// javascriptRRD:
// rrdFile.js: RRDHeader
type Header struct {
// identification section
Cookie []byte // 4 bytes
Version []byte // 4 bytes (eh, we let the \0-terminator decided how long)
// padding
FloatCookie []byte // 8 bytes
Bytes
// structure definition
DSCnt uint64 // how many different DS provide input to the RRD
RRACnt uint64 // how many RRAs will be maintained in the RRD
PDPStep uint64 // PDP interval in seconds
Parameters [10]Unival
// sniffed metadata
ByteOrder binary.ByteOrder
FloatWidth int
FloatAlign int
IntWidth int
IntAlign int
UnivalWidth int
UnivalAlign int
}
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{}
|