forked from icza/s2prot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
types.go
363 lines (316 loc) · 10.9 KB
/
types.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
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
/*
Types describing decoding instructions for protocol types.
*/
package s2prot
import (
"encoding/hex"
"encoding/json"
"fmt"
"strconv"
"strings"
)
// S2protocol type
type s2pType int
// S2protocol types
const (
s2pInt s2pType = iota // An integer number
s2pStruct // A structure (list of fields)
s2pChoice // A choice of multiple types (one of multiple)
s2pArr // List of elements of the same type
s2pBitArr // List of bits (packed into a byte array)
s2pBlob // A byte array
s2pOptional // Optionally a value (of a specified type)
s2pBool // A bool value
s2pFourCC // 4 bytes data, usually interpreted as string
s2pNull // Exactly as its name says: nothing
)
// Precached map from type names to S2pType value (for faster parsing).
// First 2 character (excluding the underscore '_') is unique, so just use that:
var nameS2pTypes = map[string]s2pType{"in": s2pInt, "st": s2pStruct, "ch": s2pChoice, "ar": s2pArr,
"bi": s2pBitArr, "bl": s2pBlob, "op": s2pOptional, "bo": s2pBool, "fo": s2pFourCC, "nu": s2pNull}
// Describes a field in structures.
// Fields used for structures (stStruct) have/use the tag attribute,
// fields used for choices (stChoice) omit the tag.
type field struct {
name string // Name of the field
typeid int // Type id (index) of the type info of the field's value
tag int // Optional tag of the field (often used for field index).
isNameParent bool // Tells if field name equals to "__parent" (for optimization purposes, it is checked many times and the result is constant)
}
// Decoding info for a specific type.
type typeInfo struct {
s2pType s2pType // Type selector; specifies how to read the value and what further fields are valid/filled
// Optional parameters for decoding, filled values depend on typeSel
// Bounds for int (and also for choice and array and bitarray and blob)
offset32 int32 // 32-bit offset to add to the read value
offset64 int64 // 64-bit offset to add to the read value
bits int // Number of bits to read
// For struct, and also for choice
fields []field // List of fields (in case of struct)
// For array, also used for optional
typeid int // Type id (index) of the elements of the array
}
// parseTypeInfo parses a TypeInfo from a python string representation.
// Panics if input is in invalid format.
func parseTypeInfo(s string) typeInfo {
var err error
// Decode type name, example:
// ('_int',[(0,7)]), #0
s = s[strings.IndexByte(s, '\'')+2:] // All start with an underscore '_', cut that also
// Map keys are the first 2 characters of the names
ti := typeInfo{s2pType: nameS2pTypes[s[:2]]}
if ti.s2pType == s2pOptional {
// In case of Optional no parenthesis follows, only skip 1 character, 2nd is part of the number
s = s[strings.IndexByte(s, '[')+1:]
} else {
s = s[strings.IndexByte(s, '[')+2:]
}
// Helper function to read intbounds specified in the form of "(0,7)" (positioned after the parenthesis)
// Returns the last index (closing parenthesis)
readBounds := func() int {
// Parameters: offset and bits which will provide an integer value
i := strings.IndexByte(s, ',')
j := strings.IndexByte(s, ')')
if ti.bits, err = strconv.Atoi(s[i+1 : j]); err != nil {
panic(err)
}
if ti.offset64, err = strconv.ParseInt(s[:i], 10, 64); err != nil {
panic(err)
}
if ti.bits <= 32 {
ti.offset32 = int32(ti.offset64)
}
return j
}
switch ti.s2pType {
case s2pInt: // ('_int',[(0,7)]), #0
// Parameters: offset and bits which will provide the integer value
readBounds()
case s2pStruct: // ('_struct',[[('m_name',71,-3),('m_type',6,-2),('m_data',20,-1)]]), #73
// Parameters: list of fields
fields := make([]field, 0, 8)
for {
i := strings.IndexByte(s, '\'')
if i < 0 {
break // No more fields
}
s = s[i+1:]
i = strings.IndexByte(s, '\'')
f := field{name: s[:i]}
f.isNameParent = f.name == "__parent"
// Most field names start with "m_". Cut that off.
if strings.HasPrefix(f.name, "m_") {
f.name = f.name[2:]
}
s = s[i+2:]
i = strings.IndexByte(s, ',')
j := strings.IndexByte(s, ')')
if f.typeid, err = strconv.Atoi(s[:i]); err != nil {
panic(err)
}
if f.tag, err = strconv.Atoi(s[i+1 : j]); err != nil {
panic(err)
}
fields = append(fields, f)
}
// Copy a trimmed version of this to type info:
ti.fields = make([]field, len(fields))
copy(ti.fields, fields)
case s2pChoice: // ('_choice',[(0,2),{0:('None',91),1:('TargetPoint',93),2:('TargetUnit',94),3:('Data',6)}]), #95
// Parameters: offset and bits which will provide the index integer value to choose
// from the following field list
i := readBounds()
s = s[i+1:]
fields := make([]field, 0, 8)
for {
if s[1] == '}' {
break // No more fields
}
s = s[2:]
i := strings.IndexByte(s, ':')
f := field{}
if f.tag, err = strconv.Atoi(s[:i]); err != nil {
panic(err)
}
s = s[strings.IndexByte(s, '\'')+1:]
i = strings.IndexByte(s, '\'')
f.name = s[:i]
s = s[i+2:]
i = strings.IndexByte(s, ')')
if f.typeid, err = strconv.Atoi(s[:i]); err != nil {
panic(err)
}
s = s[i:]
fields = append(fields, f)
}
// Copy a trimmed version of this to type info:
ti.fields = make([]field, len(fields))
copy(ti.fields, fields)
case s2pArr: // ('_array',[(16,0),10]), #14
// Parameters: offset+bits which will provide the array length, and a typeid (element type)
s = s[readBounds()+2:]
j := strings.IndexByte(s, ']')
if ti.typeid, err = strconv.Atoi(s[:j]); err != nil {
panic(err)
}
case s2pBitArr: // ('_bitarray',[(0,6)]), #52
// Parameters: offset and bits which will provide the number of bits
readBounds()
case s2pBlob: // ('_blob',[(0,8)]), #9
// Parameters: offset and bits which will provide the array length (number of bytes)
readBounds()
case s2pOptional: // ('_optional',[14]), #15
// Parameters: typeid (type of the value that optionally follows)
j := strings.IndexByte(s, ']')
if ti.typeid, err = strconv.Atoi(s[:j]); err != nil {
panic(err)
}
case s2pBool: // ('_bool',[]), #13
// We're done, nothing to do (no parameters)
case s2pFourCC: // ('_fourcc',[]), #19
// We're done, nothing to do (no parameters)
case s2pNull: // ('_null',[]), #91
// We're done, nothing to do (no parameters)
}
return ti
}
// Struct represents a decoded struct.
// It is a dynamic struct modelled with a general map with helper methods to access its content.
//
// Tip: the Struct type defines a String() method which returns a nicely formatted JSON representation,
// so simply printing a Struct results in a nice JSON text.
type Struct map[string]interface{}
// Value returns the value specified by the path.
// zero value is returned if path is invalid.
func (s *Struct) Value(path ...string) interface{} {
if len(path) == 0 {
return nil
}
var ok bool
ss := *s
last := len(path) - 1
for i := 0; i < last; i++ {
if ss, ok = ss[path[i]].(Struct); !ok {
return nil
}
}
return ss[path[last]]
}
// Structv returns the (sub) Struct specified by the path.
// zero value is returned if path is invalid.
func (s *Struct) Structv(path ...string) (v Struct) {
v, _ = s.Value(path...).(Struct)
return
}
// Int returns the integer specified by the path.
// zero value is returned if path is invalid.
func (s *Struct) Int(path ...string) (v int64) {
v, _ = s.Value(path...).(int64)
return
}
// Float returns the floating point number specified by the path.
// zero value is returned if path is invalid.
func (s *Struct) Float(path ...string) (v float64) {
v, _ = s.Value(path...).(float64)
return
}
// Bool returns the bool specified by the path.
// zero value is returned if path is invalid.
func (s *Struct) Bool(path ...string) (v bool) {
v, _ = s.Value(path...).(bool)
return
}
// Bytes returns the []byte specified by the path.
// zero value is returned if path is invalid.
func (s *Struct) Bytes(path ...string) (v []byte) {
v, _ = s.Value(path...).([]byte)
return
}
// Text returns the []byte specified by the path converted to string.
// zero value is returned if path is invalid.
func (s *Struct) Text(path ...string) string {
v, ok := s.Value(path...).([]byte)
if ok {
return string(v)
}
return ""
}
// Stringv returns the string specified by the path.
// zero value is returned if path is invalid.
func (s *Struct) Stringv(path ...string) (v string) {
v, _ = s.Value(path...).(string)
return
}
// Array returns the array (of empty interfaces) specified by the path.
// zero value is returned if path is invalid.
func (s *Struct) Array(path ...string) (v []interface{}) {
v, _ = s.Value(path...).([]interface{})
return
}
// BitArr returns the bit array specified by the path.
// zero value is returned if path is invalid.
func (s *Struct) BitArr(path ...string) (v BitArr) {
v, _ = s.Value(path...).(BitArr)
return
}
// String returns the indented JSON string representation of the Struct.
// Defined with value receiver so this gets called even if a non-pointer is printed.
func (s Struct) String() string {
b, _ := json.MarshalIndent(s, "", " ")
return string(b)
}
// Event descriptor
type Event struct {
Struct
*EvtType // Pointer only to avoid copying
}
// Loop returns the loop (time) of the event.
func (e *Event) Loop() int64 {
return e.Int("loop")
}
// UserID returns the ID of the user that issued the event.
func (e *Event) UserID() int64 {
return e.Int("userid", "userId")
}
// BitArr is a bit array which stores the bits in a byte slice.
type BitArr struct {
Count int // Bits count
Data []byte // Data holding the bits
}
// Bit masks having exactly 1 one bit at the position specified by the index (zero-based).
var singleBitMasks = [...]byte{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}
// Bit tells if the bit at the specified position (zero-based) is 1.
func (b *BitArr) Bit(n int) bool {
return b.Data[n>>3]&singleBitMasks[n&0x07] != 0
}
// Cached array which tells the nubmer of 1 bits in the number specified by the index.
var ones [256]int
func init() {
// Initialize / compute the ones array.
for i := range ones {
c := 0
for j := i; j > 0; j >>= 1 {
if j&0x01 != 0 {
c++
}
}
ones[i] = c
}
}
// Ones returns the number of 1 bits in the bit array.
func (b *BitArr) Ones() (c int) {
for _, d := range b.Data {
c += ones[d]
}
return
}
// String returns the string representation of the bit array in hexadecimal form.
// Using value receiver so printing a BitArr value will call this method.
func (b BitArr) String() string {
return fmt.Sprintf("0x%s (count=%d)", hex.EncodeToString(b.Data), b.Count)
}
// MarshalJSON produces a custom JSON string for a more informative and more compact representation of the bitarray.
// The essence is that the Data slice is presented in hex format (instead of the default Base64 encoding).
func (b BitArr) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`{"Count":%d,"Data": "0x%s"}`, b.Count, hex.EncodeToString(b.Data))), nil
}