-
Notifications
You must be signed in to change notification settings - Fork 17
/
enumerated.go
275 lines (246 loc) · 8.71 KB
/
enumerated.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
/*************************************************************************
* Copyright 2017 Gravwell, Inc. All rights reserved.
* Contact: <legal@gravwell.io>
*
* This software may be modified and distributed under the terms of the
* BSD 2-clause license. See the LICENSE file for details.
**************************************************************************/
package entry
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
)
const (
evHeaderLen = 8 // 8 bytes of header
MaxEvNameLength = 1024 // absolutely bonkers for a name, but constants are scary
MaxEvDataLength = (63*1024 - evHeaderLen - 1) // are you attaching a NOVEL?
MaxEvSize = 0xFFFF // this so we can fit the EV header which contains a uint16 length
)
var (
ErrInvalidName = errors.New("invalid enumerated value name")
ErrInvalid = errors.New("invalid enumerated value")
ErrCorruptedEnumeratedValue = errors.New("enumerated value buffer is corrupt")
ErrTruncatedEnumeratedValue = errors.New("enumerated value buffer is truncated")
)
// type expressed for documentation, we hand jam everything for speed here
type evheader struct {
totalLen uint16
nameLen uint16
dataLen uint16
dataType uint8
pad uint8 //used as a bit of an encoding sanity check and to get word alignment
}
type EnumeratedValue struct {
Name string
Value EnumeratedData
}
// NewEnumeratedValue will take the data interface and make a best effort to figure out
// what type it is being given and shove it into this encoding.
// This is the slowest method for creating an enumerated value, use the native types.
func NewEnumeratedValue(name string, data interface{}) (ev EnumeratedValue, err error) {
if len(name) == 0 || len(name) > MaxEvNameLength {
err = ErrInvalidName
return
}
// we attempt to support the set of known types, if we can't figure it out
// we call the stringer on the data portion and stuff it in as a string (TypeUnicode)
ev.Name = name
ev.Value, err = InferEnumeratedData(data)
return
}
// String implements the stringer for Enumerated Values.
func (ev EnumeratedValue) String() string {
return ev.Name + ":" + ev.Value.String()
}
// Valid is a helper function that will indicate if an enumerated value is valid.
// To be valid the enumerated value name must be populated and less than MaxEvNameLength
// and the enumerated data must be valid.
func (ev EnumeratedValue) Valid() bool {
if l := len(ev.Name); l == 0 || l > MaxEvNameLength || !ev.Value.Valid() {
return false
}
return true
}
// Size returns the encoded size of an enumerated value
func (ev EnumeratedValue) Size() int {
return len(ev.Name) + len(ev.Value.data) + evHeaderLen
}
// TypeID returns the underlying type identifier used to cast a raw buffer across types.
// This is just a little accessor used to get raw access to fields without exposing them for assignment.
func (ev EnumeratedValue) TypeID() uint8 {
return ev.Value.evtype
}
// ValueBuff returns the underlying buffer representing the enumerated values data.
// This is just a little accessor used to get raw access to fields without exposing them for assignment.
func (ev EnumeratedValue) ValueBuff() []byte {
return ev.Value.data
}
// Encode will pack the enumerated value into a byte slice. Invalid EVs return nil.
func (ev EnumeratedValue) Encode() []byte {
if !ev.Valid() {
return nil
}
r := make([]byte, evHeaderLen+len(ev.Name)+len(ev.Value.data))
if _, err := ev.encode(r); err != nil {
return nil
}
return r
}
// encode encodes an ev into the provided buffer, returning the number of bytes produced and a potential error.
func (ev EnumeratedValue) encode(r []byte) (n int, err error) {
esize := ev.Size()
if len(r) < esize {
return -1, ErrInvalidBufferSize
}
//drop the header
binary.LittleEndian.PutUint16(r, uint16(ev.Size()))
binary.LittleEndian.PutUint16(r[2:], uint16(len(ev.Name)))
binary.LittleEndian.PutUint16(r[4:], uint16(len(ev.Value.data)))
r[6] = ev.Value.evtype
r[7] = 0
//drop the name
copy(r[evHeaderLen:evHeaderLen+len(ev.Name)], []byte(ev.Name))
//drop the data
copy(r[evHeaderLen+len(ev.Name):], ev.Value.data)
return esize, nil
}
// EncodeWriter will encode an enumerated value into a writer, it returns the number of bytes written and a potential error.
func (ev EnumeratedValue) EncodeWriter(w io.Writer) (int, error) {
if !ev.Valid() {
return -1, ErrInvalid
}
r := make([]byte, evHeaderLen)
//drop the header
binary.LittleEndian.PutUint16(r, uint16(ev.Size()))
binary.LittleEndian.PutUint16(r[2:], uint16(len(ev.Name)))
binary.LittleEndian.PutUint16(r[4:], uint16(len(ev.Value.data)))
r[6] = ev.Value.evtype
r[7] = 0
if err := writeAll(w, r); err != nil {
return -1, err //failed to write header
} else if err = writeAll(w, []byte(ev.Name)); err != nil {
return -1, err //failed to write name
} else if err = writeAll(w, ev.Value.data); err != nil {
return -1, err
}
return evHeaderLen + len(ev.Name) + len(ev.Value.data), nil
}
// Decode is a helper function that returns how much of the buffer we consumed,
// this is used for decoding evblocks
// This function will make a copy of all referenced bytes so that the provided buffer can be re-used.
// The function returns the number of bytes consumed and a potential error.
func (ev *EnumeratedValue) Decode(r []byte) (n int, err error) {
var h evheader
if h, err = decodeHeader(r); err != nil {
return -1, err
}
if len(r) < int(h.totalLen) {
return -1, ErrTruncatedEnumeratedValue
}
r = r[evHeaderLen:]
ev.Name = string(append([]byte(nil), r[:h.nameLen]...))
r = r[h.nameLen:]
ev.Value.data = append([]byte(nil), r[:h.dataLen]...)
ev.Value.evtype = h.dataType
if !ev.Valid() {
err = ErrCorruptedEnumeratedValue
} else {
n = int(h.totalLen)
}
return
}
// DecodeAlt is a helper function that returns how much of the buffer we consumed
// this is used for decoding evblocks.
// This function will will directly reference the underlying buffer.
// Callers cannot re-use the buffer if the enumerated values are enumerated values are in use.
// The function returns the number of bytes consumed and a potential error.
func (ev *EnumeratedValue) DecodeAlt(r []byte) (n int, err error) {
var h evheader
if h, err = decodeHeader(r); err != nil {
return -1, err
}
if len(r) < int(h.totalLen) {
return -1, ErrTruncatedEnumeratedValue
}
r = r[evHeaderLen:]
ev.Name = string(r[:h.nameLen])
r = r[h.nameLen:]
ev.Value.data = r[:h.dataLen]
ev.Value.evtype = h.dataType
if !ev.Valid() {
err = ErrCorruptedEnumeratedValue
} else {
n = int(h.totalLen)
}
return
}
// DecodeReader decodes an enumerated value from the io.Reader and returns the number of bytes read as well as a potential error.
func (ev *EnumeratedValue) DecodeReader(r io.Reader) (int, error) {
var h evheader
//read out the header
buff := make([]byte, evHeaderLen)
if err := readAll(r, buff); err != nil {
return -1, err
} else if h, err = decodeHeader(buff); err != nil {
return -1, err
}
//read out the name
buff = make([]byte, h.nameLen)
if err := readAll(r, buff); err != nil {
return -1, err
}
ev.Name = string(buff)
ev.Value.evtype = h.dataType
ev.Value.data = make([]byte, h.dataLen)
if err := readAll(r, ev.Value.data); err != nil {
return -1, err
}
if !ev.Valid() {
return -1, ErrCorruptedEnumeratedValue
}
return int(evHeaderLen + h.nameLen + h.dataLen), nil //all good
}
// decodeHeader decodes and validates an evheader from a byte buffer.
func decodeHeader(r []byte) (h evheader, err error) {
//make sure we can at least grab a header
if len(r) < evHeaderLen {
err = ErrTruncatedEnumeratedValue
return
} else if r[7] != 0 {
err = ErrCorruptedEnumeratedValue
return
}
if h.totalLen = binary.LittleEndian.Uint16(r); h.totalLen > MaxEvSize {
err = ErrCorruptedEnumeratedValue
return
}
if h.nameLen = binary.LittleEndian.Uint16(r[2:]); h.nameLen == 0 || h.nameLen > MaxEvNameLength {
err = ErrCorruptedEnumeratedValue
return
}
if h.dataLen = binary.LittleEndian.Uint16(r[4:]); h.dataLen > MaxEvDataLength {
err = ErrCorruptedEnumeratedValue
return
}
h.dataType = r[6]
//check purported lengths
if (h.nameLen + h.dataLen + evHeaderLen) != h.totalLen {
err = ErrCorruptedEnumeratedValue
}
return
}
// Compare is a helper function to do comparisons and get errors out describing what is not the same.
func (ev EnumeratedValue) Compare(ev2 EnumeratedValue) (err error) {
//make sure its identical to what went in
if ev.Name != ev2.Name {
return fmt.Errorf("Names do not")
} else if ev.Value.evtype != ev2.Value.evtype {
return fmt.Errorf("evtypes do not match: %d != %d", ev.Value.evtype, ev2.Value.evtype)
} else if !bytes.Equal(ev.Value.data, ev2.Value.data) {
return fmt.Errorf("data buffers do not match")
}
return nil
}