-
Notifications
You must be signed in to change notification settings - Fork 0
/
serializer.go
291 lines (219 loc) · 6.29 KB
/
serializer.go
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
package arc
import (
"bytes"
"encoding/binary"
"hash/crc32"
)
const (
// magicByte is the first byte of an Arc file.
magicByte = byte(0x41)
// fileFormatVersion is the database file format version.
fileFormatVersion = uint8(1)
// sizeOfUint8 is the size of uint8 in bytes.
sizeOfUint8 = 1
// sizeOfUint16 is the size of uint16 in bytes.
sizeOfUint16 = 2
// sizeOfUint32 is the size of uint32 in bytes.
sizeOfUint32 = 4
// sizeOfUint64 is the size of uint64 in bytes.
sizeOfUint64 = 8
// checksumLen is the length of a checksum in bytes.
checksumLen = sizeOfUint32
// minNodeBytesLen is the minimum length of a serialized node.
minNodeBytesLen = sizeOfUint8 + sizeOfUint16 + sizeOfUint16 + sizeOfUint32 + sizeOfUint64 + sizeOfUint64
// arcHeaderBytesLen is the length of the arc file header.
arcHeaderBytesLen = sizeOfUint8 + sizeOfUint8 + sizeOfUint8 + checksumLen
)
// Index node flags.
const (
flagIsRecord = 1 << iota // 0b00000001
flagHasBlob // 0b00000010
)
const (
arcFileClosed = 0
arcFileOpened = 1
)
type arcHeader struct {
magic byte
version byte
status byte
}
func newArcHeader() arcHeader {
return arcHeader{
magic: magicByte,
version: fileFormatVersion,
status: arcFileClosed,
}
}
func (ah *arcHeader) serialize() ([]byte, error) {
var buf bytes.Buffer
buf.WriteByte(ah.magic)
buf.WriteByte(ah.version)
buf.WriteByte(ah.status)
checksum, err := computeChecksum(buf.Bytes())
if err != nil {
return nil, err
}
if err = binary.Write(&buf, binary.LittleEndian, checksum); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func newArcHeaderFromBytes(src []byte) (arcHeader, error) {
var ret arcHeader
if len(src) != arcHeaderBytesLen {
return ret, ErrCorrupted
}
reader := bytes.NewReader(src)
if err := binary.Read(reader, binary.LittleEndian, &ret.magic); err != nil {
return ret, err
}
if err := binary.Read(reader, binary.LittleEndian, &ret.version); err != nil {
return ret, err
}
if err := binary.Read(reader, binary.LittleEndian, &ret.status); err != nil {
return ret, err
}
return ret, nil
}
// persistentNode is the on-disk structure of Arc's radix tree node.
// All fields in this struct are persisted in the same order.
type persistentNode struct {
flags uint8
numChildren uint16
keyLen uint16
dataLen uint32
firstChildOffset uint64
nextSiblingOffset uint64
key []byte
data []byte
}
func makePersistentNode(n node) persistentNode {
var ret persistentNode
if n.isRecord {
ret.flags |= flagIsRecord
}
if n.blobValue {
ret.flags |= flagHasBlob
}
ret.numChildren = uint16(n.numChildren)
ret.keyLen = uint16(len(n.key))
ret.dataLen = uint32(len(n.data))
ret.key = n.key
ret.data = n.data
// Node offsets are unknown at initialization phase.
ret.firstChildOffset = 0
ret.nextSiblingOffset = 0
return ret
}
func makePersistentNodeFromBytes(src []byte) (persistentNode, error) {
var ret persistentNode
if len(src) < minNodeBytesLen {
return ret, ErrNodeCorrupted
}
var err error
var gotChecksum uint32
var wantChecksum uint32
checksumPos := src[len(src)-sizeOfUint32:]
checksumBuf := bytes.NewReader(checksumPos)
if err = binary.Read(checksumBuf, binary.LittleEndian, &wantChecksum); err != nil {
return ret, err
}
nodeData := src[:len(src)-sizeOfUint32]
if gotChecksum, err = computeChecksum(nodeData); err != nil {
return ret, err
}
if gotChecksum != wantChecksum {
return ret, ErrInvalidChecksum
}
nodeRegion := src[:len(src)-sizeOfUint32]
nodeReader := bytes.NewReader(nodeRegion)
if err := binary.Read(nodeReader, binary.LittleEndian, &ret.flags); err != nil {
return ret, err
}
if err := binary.Read(nodeReader, binary.LittleEndian, &ret.numChildren); err != nil {
return ret, err
}
if err := binary.Read(nodeReader, binary.LittleEndian, &ret.keyLen); err != nil {
return ret, err
}
if err := binary.Read(nodeReader, binary.LittleEndian, &ret.dataLen); err != nil {
return ret, err
}
if err := binary.Read(nodeReader, binary.LittleEndian, &ret.firstChildOffset); err != nil {
return ret, err
}
if err := binary.Read(nodeReader, binary.LittleEndian, &ret.nextSiblingOffset); err != nil {
return ret, err
}
// Done reading fixed length fields. Ensure that the dynamic length
// regions are available. If not, the node is corrupted.
remaining := nodeReader.Len()
expectedRemaining := int(ret.keyLen) + int(ret.dataLen)
if expectedRemaining != remaining {
return ret, ErrNodeCorrupted
}
ret.key = make([]byte, ret.keyLen)
if _, err := nodeReader.Read(ret.key); err != nil {
return ret, err
}
if ret.isRecord() {
ret.data = make([]byte, ret.dataLen)
if _, err := nodeReader.Read(ret.data); err != nil {
return ret, err
}
}
return ret, nil
}
// isRecord returns true if the isRecord flag is set.
func (pn persistentNode) isRecord() bool {
return pn.flags&flagIsRecord != 0
}
// hasBlob returns true if the hasBlob flag is set.
func (pn persistentNode) hasBlob() bool {
return pn.flags&flagHasBlob != 0
}
// serialize serializes the persistentNode into a standardized byte slice.
func (pn persistentNode) serialize() ([]byte, error) {
var buf bytes.Buffer
if err := buf.WriteByte(pn.flags); err != nil {
return nil, err
}
if err := binary.Write(&buf, binary.LittleEndian, pn.numChildren); err != nil {
return nil, err
}
if err := binary.Write(&buf, binary.LittleEndian, pn.keyLen); err != nil {
return nil, err
}
if err := binary.Write(&buf, binary.LittleEndian, pn.dataLen); err != nil {
return nil, err
}
if err := binary.Write(&buf, binary.LittleEndian, pn.firstChildOffset); err != nil {
return nil, err
}
if err := binary.Write(&buf, binary.LittleEndian, pn.nextSiblingOffset); err != nil {
return nil, err
}
if _, err := buf.Write(pn.key); err != nil {
return nil, err
}
if _, err := buf.Write(pn.data); err != nil {
return nil, err
}
// Append the checksum at the end of the serialized node.
checksum, err := computeChecksum(buf.Bytes())
if err != nil {
return nil, err
}
if err := binary.Write(&buf, binary.LittleEndian, checksum); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func computeChecksum(src []byte) (uint32, error) {
h := crc32.NewIEEE()
if _, err := h.Write(src); err != nil {
return 0, err
}
return h.Sum32(), nil
}