-
-
Save dshaw/1145691 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### | |
# An implementation of the msgpack serialization format - http://msgpack.org/ | |
# By Devon Govett | |
# MIT LICENCE | |
### | |
class 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 # TODO: -Infinity? | |
bytes.push 0xcb, 0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | |
# integer | |
else if Math.floor(val) is val | |
if val >= 0 | |
# positive fixnum | |
if val < 0x80 | |
bytes.push val | |
# uint8 | |
else if val < 0x100 | |
bytes.push 0xcc, val | |
# uint16 | |
else if val < 0x10000 | |
bytes.push 0xcd, val >> 8, val & 0xff | |
# uint32 | |
else if val < 0x100000000 | |
bytes.push 0xce, val >>> 24, (val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff | |
# uint64 | |
else if val < 0x10000000000000000 | |
high = Math.floor(val / 0x100000000) | |
low = val & 0xffffffff | |
bytes.push 0xcf, (high >> 24) & 0xff, (high >> 16) & 0xff, | |
(high >> 8) & 0xff, high & 0xff, | |
(low >> 24) & 0xff, (low >> 16) & 0xff, | |
(low >> 8) & 0xff, low & 0xff | |
else | |
throw 'Number too large.' | |
else | |
# negative fixnum | |
if val >= -32 | |
bytes.push 0xe0 + val + 32 | |
# int8 | |
else if val >= -0x80 | |
bytes.push 0xd0, val + 0x100 | |
# int16 | |
else if val >= -0x8000 | |
val += 0x10000 | |
bytes.push 0xd1, val >> 8, val & 0xff | |
# int32 | |
else if val >= -0x80000000 | |
val += 0x100000000 | |
bytes.push 0xd2, val >>> 24, (val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff | |
# int64 | |
else if val >= -0x8000000000000000 | |
high = Math.floor(val / 0x100000000) | |
low = val & 0xffffffff | |
bytes.push 0xd3, (high >> 24) & 0xff, (high >> 16) & 0xff, | |
(high >> 8) & 0xff, high & 0xff, | |
(low >> 24) & 0xff, (low >> 16) & 0xff, | |
(low >> 8) & 0xff, low & 0xff | |
else | |
throw 'Number too small.' | |
# float | |
else | |
# TODO: encode single percision if possible | |
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' | |
len = val.length | |
pos = bytes.length | |
bytes.push 0 # placeholder byte for size, added below | |
# utf8 encode | |
for i in [0...val.length] | |
char = val.charCodeAt(i) | |
if char < 0x80 # ASCII(0x00 ~ 0x7f) | |
bytes.push char & 0x7f | |
else if char < 0x0800 | |
bytes.push ((char >>> 6) & 0x1f) | 0xc0, (char & 0x3f) | 0x80 | |
else if char < 0x10000 | |
bytes.push ((char >>> 12) & 0x0f) | 0xe0, ((char >>> 6) & 0x3f) | 0x80, (char & 0x3f) | 0x80 | |
size = bytes.length - pos - 1 | |
# fixraw | |
if size < 32 | |
bytes[pos] = 0xa0 | size | |
# raw16 | |
else if size < 0x10000 | |
bytes.splice pos, 1, 0xda, size >> 8, size & 0xff | |
# raw32 | |
else if size < 0x100000000 | |
bytes.splice pos, 1, 0xdb, size >>> 24, (size >> 16) & 0xff, (size >> 8) & 0xff, size & 0xff | |
else | |
throw 'String too long.' | |
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, len & 0xff | |
# array 32 | |
else if len < 0x100000000 | |
bytes.push 0xdd, len >>> 24, (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, size >> 8, size & 0xff | |
# map32 | |
else if len < 0x100000000 | |
bytes.push 0xdf, len >>> 24, (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 | |
num = buf[idx++] | |
if num & 0x80 # sign -> avoid 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) | (buf[idx++] << 16) | (buf[idx++] << 8) | buf[idx++] | |
raw = (buf, len) -> | |
iz = idx + len | |
out = [] | |
i = 0 | |
fromCharCode = String.fromCharCode | |
while idx < iz | |
char = buf[idx++] | |
if char < 0x80 | |
out[i++] = fromCharCode char | |
else if char < 0xe0 | |
out[i++] = fromCharCode (char & 0x1f) << 6 | (buf[idx++] & 0x3f) | |
else | |
out[i++] = fromCharCode (char & 0x0f) << 12 | (buf[idx++] & 0x3f) << 6 | (buf[idx++] & 0x3f) | |
return 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 | |
module.exports = CSMsgPack |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment