-
Notifications
You must be signed in to change notification settings - Fork 6
/
msgpack.coffee
388 lines (303 loc) · 14.8 KB
/
msgpack.coffee
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
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
###
# An implementation of the MessagePack serialization format - http://msgpack.org/
# Copyright (c) 2011 Devon Govett
# MIT LICENSE
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
# software and associated documentation files (the "Software"), to deal in the Software
# without restriction, including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
# to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or
# substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
###
class MsgPack
# export the module to CommonJS or the browser
if module?.exports
module.exports = MsgPack
else
window.MsgPack = MsgPack
idx = 0
@pack: (data, byteArray = false) ->
bytes = pack(data, [])
return bytes if byteArray
fcc = String.fromCharCode
return (fcc byte for byte in bytes).join ''
@unpack: (data) ->
if typeof data is 'string'
data = (data.charCodeAt(i) & 0xff for i in [0...data.length])
idx = 0
unpack(data)
pack = (val, bytes) ->
# if the value has a toJSON function, call it
if val and typeof val is 'object' and typeof val.toJSON is 'function'
val = val.toJSON()
# null or undefined
if not val?
bytes.push 0xc0
# true
else if val is true
bytes.push 0xc3
# false
else if val is false
bytes.push 0xc2
else
switch typeof val
when 'number'
# NaN
if val isnt val
bytes.push 0xcb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
# infinity
else if val is Infinity
bytes.push 0xcb, 0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
else if val is -Infinity
bytes.push 0xcb, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
# integer
else if Math.floor(val) is val and -0x8000000000000000 <= val < 0x10000000000000000
# fixnum
if -0x20 <= val < 0x80
val += 0x100 if val < 0
bytes.push val & 0xff
# (u)int8
else if -0x80 <= val < 0x100
type = if val < 0 then 0xd0 else 0xcc
val += 0x100 if val < 0
bytes.push type, val & 0xff
# (u)int16
else if -0x8000 <= val < 0x10000
type = if val < 0 then 0xd1 else 0xcd
val += 0x10000 if val < 0
bytes.push type, (val >>> 8) & 0xff, val & 0xff
# (u)int32
else if -0x80000000 <= val < 0x100000000
type = if val < 0 then 0xd2 else 0xce
val += 0x100000000 if val < 0
bytes.push type, (val >>> 24) & 0xff, (val >>> 16) & 0xff, (val >>> 8) & 0xff, val & 0xff
# (u)int64
else
type = if val < 0 then 0xd3 else 0xcf
high = Math.floor(val / 0x100000000)
low = val & 0xffffffff
bytes.push type, (high >>> 24) & 0xff, (high >>> 16) & 0xff,
(high >>> 8) & 0xff, high & 0xff,
(low >>> 24) & 0xff, (low >>> 16) & 0xff,
(low >>> 8) & 0xff, low & 0xff
# double
else
# TODO: encode single precision if possible
# based on http://javascript.g.hatena.ne.jp/edvakf/20101128/1291000731
sign = val < 0
val *= -1 if sign
# add offset 1023 to ensure positive
exp = ((Math.log(val) / Math.LN2) + 1023) | 0
# shift 52 - (exp - 1023) bits to make integer part exactly 53 bits,
# then throw away trash less than decimal point
frac = val * Math.pow(2, 52 + 1023 - exp)
low = frac & 0xffffffff
exp |= 0x800 if sign
high = ((frac / 0x100000000) & 0xfffff) | (exp << 20)
bytes.push 0xcb, (high >> 24) & 0xff, (high >> 16) & 0xff,
(high >> 8) & 0xff, high & 0xff,
(low >> 24) & 0xff, (low >> 16) & 0xff,
(low >> 8) & 0xff, low & 0xff
when 'string'
# utf-8 encode
# http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
val = unescape(encodeURIComponent(val))
size = val.length
# fixraw
if size < 0x20
bytes.push 0xa0 | size
# raw16
else if size < 0x10000
bytes.push 0xda, (size >>> 8) & 0xff, size & 0xff
# raw32
else if size < 0x100000000
bytes.push 0xdb, (size >>> 24) & 0xff, (size >>> 16) & 0xff, (size >>> 8) & 0xff, size & 0xff
else
throw 'String too long.'
for i in [0...size]
bytes.push val.charCodeAt(i)
when 'object'
# array
if Array.isArray(val)
len = val.length
# fixarray
if len < 16
bytes.push 0x90 + len
# array16
else if len < 0x10000
bytes.push 0xdc, (len >>> 8) & 0xff, len & 0xff
# array 32
else if len < 0x100000000
bytes.push 0xdd, (len >>> 24) & 0xff, (len >>> 16) & 0xff, (len >>> 8) & 0xff, len & 0xff
else
throw 'Array too long.'
pack item, bytes for item in val
# map
else
len = Object.keys(val).length
# fixmap
if len < 16
bytes.push 0x80 + len
# map16
else if len < 0x10000
bytes.push 0xde, (len >>> 8) & 0xff, len & 0xff
# map32
else if len < 0x100000000
bytes.push 0xdf, (len >>> 24) & 0xff, (len >>> 16) & 0xff, (len >>> 8) & 0xff, len & 0xff
else
throw 'Map has too many keys.'
for key, mapval of val
pack key, bytes
pack mapval, bytes
else
throw 'Unknown value.'
return bytes
unpack = (buf) ->
byte = buf[idx++]
# fixnum
if byte < 0x80
return byte
# negative fixnum
if byte >= 0xe0
return byte - 0x100
if byte >= 0xc0
switch byte
# null
when 0xc0
return null
# false
when 0xc2
return false
# true
when 0xc3
return true
# float
when 0xca
num = uint32(buf)
return 0.0 if not num or num is 0x80000000 # 0.0 or -0.0
sign = (num >> 31) * 2 + 1 # +1 or -1
exp = (num >> 23) & 0xff
frac = num & 0x7fffff
# NaN or Infinity
if exp is 0xff
return if frac then NaN else sign * Infinity
return sign * (frac | 0x00800000) * Math.pow(2, exp - 127 - 23)
# double
when 0xcb
num = uint32(buf)
if not num or num is 0x80000000 # 0.0 or -0.0
idx += 4
return 0.0
sign = (num >> 31) * 2 + 1 # +1 or -1
exp = (num >> 20) & 0x7ff
frac = num & 0xfffff
if exp is 0x7ff
idx += 4
return if frac then NaN else sign * Infinity
num = uint32(buf)
return sign * ((frac | 0x100000) * Math.pow(2, exp - 1023 - 20) + num * Math.pow(2, exp - 1023 - 52))
# uint8
when 0xcc
return buf[idx++]
# uint16
when 0xcd
return uint16(buf)
# uint32
when 0xce
return uint32(buf)
# uint64
when 0xcf
return uint32(buf) * 0x100000000 + uint32(buf)
# int8
when 0xd0
return buf[idx++] - 0x100
# int16
when 0xd1
return (buf[idx++] << 8) | buf[idx++] - 0x10000
# int32
when 0xd2
num = uint32(buf)
return if num < 0x80000000 then num else num - 0x100000000
# int64
when 0xd3
# based on https://github.com/uupaa/msgpack.js/blob/master/msgpack.js#L317
num = buf[idx++]
if num & 0x80 # avoid sign overflow
return ((num ^ 0xff) * 0x100000000000000 +
(buf[idx++] ^ 0xff) * 0x1000000000000 +
(buf[idx++] ^ 0xff) * 0x10000000000 +
(buf[idx++] ^ 0xff) * 0x100000000 +
(buf[idx++] ^ 0xff) * 0x1000000 +
(buf[idx++] ^ 0xff) * 0x10000 +
(buf[idx++] ^ 0xff) * 0x100 +
(buf[idx++] ^ 0xff) + 1) * -1
return num * 0x100000000000000 +
buf[idx++] * 0x1000000000000 +
buf[idx++] * 0x10000000000 +
buf[idx++] * 0x100000000 +
buf[idx++] * 0x1000000 +
buf[idx++] * 0x10000 +
buf[idx++] * 0x100 +
buf[idx++]
# raw16
when 0xda
return raw(buf, uint16(buf))
# raw32
when 0xdb
return raw(buf, uint32(buf))
# array16
when 0xdc
return array(buf, uint16(buf))
# array32
when 0xdd
return array(buf, uint32(buf))
# map16
when 0xde
return map(buf, uint16(buf))
# map32
when 0xdf
return map(buf, uint32(buf))
throw 'Invalid variable code'
# fixraw (utf8 decode)
if byte >= 0xa0
return raw(buf, byte & 0x1f)
# fixarray
if byte >= 0x90
return array(buf, byte & 0xf)
# fixmap
if byte >= 0x80
return map(buf, byte & 0xf)
throw "Unknown sequence encountered."
uint16 = (buf) ->
return (buf[idx++] << 8) | buf[idx++]
uint32 = (buf) ->
return (buf[idx++] << 24 >>> 0) + ((buf[idx++] << 16) | (buf[idx++] << 8) | buf[idx++])
raw = (buf, len) ->
iz = idx + len
out = []
fromCharCode = String.fromCharCode
while idx < iz
out.push fromCharCode buf[idx++]
# utf-8 decode
# http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
return decodeURIComponent(escape(out.join ''))
array = (buf, num) ->
out = []
while num--
out.push unpack(buf, idx)
return out
map = (buf, num) ->
out = {}
while num--
key = unpack(buf, idx)
out[key] = unpack(buf, idx)
return out