-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserialization.go
257 lines (241 loc) · 5.65 KB
/
serialization.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
// package haxeremote provides Serialization and Unserializtion of data in Haxe format.
/* Haxe serialization prefixes :
a : array - DONE
b : hash
c : class
d : Float - DONE
e : reserved (float exp) - DONE
f : false - DONE
g : object end
h : array/list/hash end - DONE for array
i : Int - DONE
j : enum (by index)
k : NaN - DONE
l : list
m : -Inf - DONE
n : null - DONE
o : object
p : +Inf - DONE
q : haxe.ds.IntMap
r : reference
s : bytes (base64) - DONE
t : true - DONE
u : array nulls - DONE
v : date
w : enum
x : exception
y : urlencoded string - DONE
z : zero - DONE
A : Class<Dynamic>
B : Enum<Dynamic>
M : haxe.ds.ObjectMap
C : custom
*/
package haxeremote
import (
"encoding/base64"
"errors"
"fmt"
"math"
"net/url"
"strconv"
)
// Unserialize the in the Haxe format from a given buffer.
func Unserialize(buf []byte) (data interface{}, remains []byte, err error) {
//fmt.Println("DEBUG Unserialize:", string(buf))
if len(buf) == 0 {
return nil, nil, nil
}
code := buf[0]
remains = buf[1:]
switch code {
case 'a': // Array
dataArray := []interface{}{}
var item interface{}
arrayLoop:
item, remains, err = Unserialize(remains)
if err != nil {
return nil, nil, err
}
dataArray = append(dataArray, item)
arrayLoopEnd:
if len(remains) > 0 && err == nil {
switch remains[0] {
default:
goto arrayLoop
case 'u': // some number of null/nil entries
remains = remains[1:]
for i := range remains {
switch remains[i] {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
default:
strInt := remains[:i]
remains = remains[i:]
numNil := 0
numNil, err = strconv.Atoi(string(strInt))
for j := 0; j < numNil; j++ {
dataArray = append(dataArray, nil)
}
goto arrayLoopEnd
}
}
return nil, nil,
errors.New("invalid u item in Haxe array serialization: " + string(remains))
case 'h': // end of array
remains = remains[1:]
}
}
data = dataArray
case 'i': // Integer
for i := range remains {
switch remains[i] {
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
default:
strInt := remains[:i]
remains = remains[i:]
data, err = strconv.Atoi(string(strInt))
goto done
}
}
data, err = strconv.Atoi(string(remains))
remains = []byte{}
case 'y': // String
for i, j := range remains {
switch j {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
// NoOp
case ':':
strInt := string(remains[:i])
remains = remains[i+1:]
var length int
length, err = strconv.Atoi(strInt)
//fmt.Printf("DEBUG string len: %s, %d, %v\n", strInt, length, err)
if err == nil {
raw := string(remains[:length])
clean, err := url.QueryUnescape(raw)
if err == nil {
data = clean
} else {
data = raw
}
remains = remains[length:]
//fmt.Printf("DEBUG string decoded, remaining: %s, %s\n", data, remains)
}
goto done
default:
err = errors.New("unrecognised string length: " + string(remains))
}
}
case 'd': // Float
for i := range remains {
switch remains[i] {
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e':
default:
strFloat := string(remains[:i])
remains = remains[i:]
data, err = strconv.ParseFloat(strFloat, 64)
goto done
}
}
data, err = strconv.ParseFloat(string(remains), 64)
remains = []byte{}
// the single letter values
case 'n':
data = nil
case 't':
data = true
case 'f':
data = false
case 'k':
data = math.NaN()
case 'p':
data = math.Inf(+1)
case 'm':
data = math.Inf(-1)
case 'z': // TODO should zero be floating point?
data = 0
case 's': // haxe.io.Bytes
for i, j := range remains {
switch j {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
// NoOp
case ':':
strInt := string(remains[:i])
remains = remains[i+1:]
var length int
length, err = strconv.Atoi(strInt)
if err == nil {
raw := string(remains[:length])
//println("DEBUG raw=" + raw)
data, err = base64.StdEncoding.DecodeString(raw)
//fmt.Printf("DEBUG data %v:%T err=%v\n", data, data, err)
remains = remains[length:]
}
goto done
default:
err = errors.New("unrecognised string length: " + string(remains))
}
}
// TODO many more letters !
default:
err = errors.New("unhandled Haxe serialization code: " + string(code))
}
done:
return
}
// Serialize the given data into a string in Haxe serialization format.
func Serialize(data interface{}) string {
if data == nil {
return "n"
}
switch data.(type) {
case int:
if data.(int) == 0 {
return "z"
}
return fmt.Sprintf("i%d", data.(int))
case string:
result := url.QueryEscape(data.(string))
return fmt.Sprintf("y%d:%s", len(result), result)
case bool:
if data.(bool) {
return "t"
}
return "f"
case float64: // NOTE no special processing of 0=>z for Float
val := data.(float64)
if math.IsInf(val, -1) {
return "m"
}
if math.IsInf(val, +1) {
return "p"
}
if math.IsNaN(val) {
return "k"
}
return "d" + strconv.FormatFloat(val, 'g', -1, 64)
case []interface{}:
ret := "a"
nilCount := 0
for _, a := range data.([]interface{}) {
if a == nil {
nilCount++
} else {
if nilCount > 0 {
ret += fmt.Sprintf("u%d", nilCount)
nilCount = 0
}
ret += Serialize(a)
}
}
if nilCount > 0 {
ret += fmt.Sprintf("u%d", nilCount)
}
return ret + "h"
case []byte:
strForm := base64.StdEncoding.EncodeToString(data.([]byte))
return fmt.Sprintf("s%d:%s", len(strForm), strForm)
default:
panic(fmt.Sprintf("unhandled type to be serialized for Haxe %v : %T", data, data))
}
}