Skip to content

Instantly share code, notes, and snippets.

@uupaa
Created November 18, 2012 17:34
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save uupaa/4106426 to your computer and use it in GitHub Desktop.
Save uupaa/4106426 to your computer and use it in GitHub Desktop.
msgpack.2.0alpha.js
/*!{id:"msgpack.js",ver:"2.0alpha",license:"MIT",author:"uupaa.js@gmail.com"}*/
(function(global) { // @arg Global: window or global
// --- header ----------------------------------------------
global.msgpack = {
pack: msgpack_pack, // msgpack.pack(mix:Mix,
// size:Integer = 16Kb):Uint8Array
unpack: msgpack_unpack // msgpack.unpack(view:Uint8Array):Mix
};
// --- library scope vars ----------------------------------
var _idx = 0, // decoder: offset
_MAX_DEPTH = 512;
// --- implement -------------------------------------------
function msgpack_pack(mix, // @arg Mix:
size) { // @arg Integer(= 16Kb): buffer size
// @ret Uint8Array:
// @desc: msgpack.pack
size = size || 1024 * 16;
var view, offset;
while (1) {
view = new Uint8Array(size);
try {
offset = _recursiveMessagePackEncoder(view, 0, size, mix, 0);
if (offset > size) {
throw new RangeError("INDEX_IS_OUT_OF_RANGE");
}
break;
} catch (err) {
if (err instanceof RangeError) {
size *= 2; // buffer size auto expansion -> retry
} else {
throw err;
}
}
}
return view.subarray(0, offset);
}
function _recursiveMessagePackEncoder(view, offset, size, mix, depth) {
if (++depth >= _MAX_DEPTH) {
throw new TypeError("CYCLIC_REFERENCE_ERROR");
}
if (offset > size) {
throw new RangeError("INDEX_IS_OUT_OF_RANGE");
}
if (mix == null) { // null or undefined
view[offset++] = 0xc0;
return offset;
}
if (Array.isArray(mix)) {
return _Array_msgpack(view, offset, size, mix, depth);
}
switch (typeof mix) {
case "boolean": view[offset++] = mix ? 0xc2 : 0xc3; return offset;
case "number": return _Number_msgpack(view, offset, size, mix);
case "string": return _String_msgpack(view, offset, size, mix);
default: return _Object_msgpack(view, offset, size, mix, depth);
}
return offset;
}
function _Object_msgpack(view, offset, size, mix, depth) {
var keys = Object.keys(mix), key, i = 0, iz = keys.length;
if (iz < 16) { // fix map
view[offset++] = 0x80 + iz;
} else if (iz < 0x10000) { // map 16
view.set([0xde, iz >> 8, iz], offset);
offset += 3;
} else if (iz < 0x100000000) { // map 32
view.set([0xdf, iz >> 24, iz >> 16, iz >> 8, iz], offset);
offset += 5;
}
for (; i < iz; ++i) { // uupaa-looper
key = keys[i];
offset = _String_msgpack(view, offset, size, key);
offset = _recursiveMessagePackEncoder(view, offset, size,
mix[key], depth);
}
return offset;
}
function _Array_msgpack(view, offset, size, mix, depth) {
var i = 0, iz = mix.length;
if (iz < 16) { // fix array
view[offset++] = 0x90 + iz;
} else if (iz < 0x10000) { // array 16
view.set([0xdc, iz >> 8, iz], offset);
offset += 3;
} else if (iz < 0x100000000) { // array 32
view.set([0xdd, iz >> 24, iz >> 16, iz >> 8, iz], offset);
offset += 5;
}
for (; i < iz; ++i) {
offset = _recursiveMessagePackEncoder(view, offset, size, mix[i], depth);
}
return offset;
}
function _Number_msgpack(view, offset, size, mix) {
var high, low,
sign, exp, frac;
if (mix !== mix) { // quiet NaN
view.set([0xcb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], offset);
offset += 9;
} else if (mix === Infinity) { // positive infinity
view.set([0xcb, 0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], offset);
offset += 9;
} else if (Math.floor(mix) === mix) { // int or uint
if (mix < 0) { // --- negative ---
if (mix >= -32) { // fixnum -> [111xxxxx]
view[offset++] = 0xe0 + mix + 32;
} else if (mix > -0x80) { // int 8 -> [0xd0, value]
view[offset++] = 0xd0;
view[offset++] = mix + 0x100;
} else if (mix > -0x8000) { // int 16 -> [0xd1, value x 2]
mix += 0x10000;
view.set([0xd1, mix >> 8, mix], offset);
offset += 3;
} else if (mix > -0x80000000) { // int 32 -> [0xd2, value x 4]
mix += 0x100000000;
view.set([0xd2, mix >> 24, mix >> 16, mix >> 8, mix], offset);
offset += 5;
} else { // int 64 -> [0xd3, value x 8]
high = Math.floor(mix / 0x100000000);
low = mix & 0xffffffff;
view.set([0xd3, high >> 24, high >> 16, high >> 8, high,
low >> 24, low >> 16, low >> 8, low], offset);
offset += 9;
}
} else { // --- positive ---
if (mix < 0x80) { // fixnum -> [value]
view[offset++] = mix;
} else if (mix < 0x100) { // uint 8 -> [0xcc, value]
view[offset++] = 0xcc;
view[offset++] = mix;
} else if (mix < 0x10000) { // uint 16 -> [0xcd, value x 2]
view.set([0xcd, mix >> 8, mix], offset);
offset += 3;
} else if (mix < 0x100000000) { // uint 32 -> [0xce, value x 4]
view.set([0xce, mix >> 24, mix >> 16, mix >> 8, mix], offset);
offset += 5;
} else { // uint 64 -> [0xcf, value x 8]
high = Math.floor(mix / 0x100000000);
low = mix & 0xffffffff;
view.set([0xcf, high >> 24, high >> 16, high >> 8, high,
low >> 24, low >> 16, low >> 8, low], offset);
offset += 9;
}
}
} else { // --- double ---
// THX!! @edvakf
// http://javascript.g.hatena.ne.jp/edvakf/20101128/1291000731
sign = mix < 0;
sign && (mix *= -1);
// add offset 1023 to ensure positive
exp = ((Math.log(mix) / 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 = mix * Math.pow(2, 52 + 1023 - exp);
// S+-Exp(11)--++-----------------Fraction(52bits)-----------------------+
// || || |
// v+----------++--------------------------------------------------------+
// 00000000|00000000|00000000|00000000|00000000|00000000|00000000|00000000
// 6 5 55 4 4 3 2 1 8 0
// 3 6 21 8 0 2 4 6
//
// +----------high(32bits)-----------+ +----------low(32bits)------------+
// | | | |
// +---------------------------------+ +---------------------------------+
// 3 2 21 1 8 0
// 1 4 09 6
low = frac & 0xffffffff;
sign && (exp |= 0x800);
high = ((frac / 0x100000000) & 0xfffff) | (exp << 20);
view.set([0xcb, high >> 24, high >> 16, high >> 8, high,
low >> 24, low >> 16, low >> 8, low], offset);
offset += 9;
}
return offset;
}
function _String_msgpack(view, offset, size, mix) {
var ary = _utf8_encode(mix), size = ary.length;
if (size < 32) { // fix raw
view[offset++] = 0xa0 + size;
} else if (size < 0x10000) { // raw 16
view.set([0xda, size >> 8, size], offset);
offset += 3;
} else if (size < 0x100000000) { // raw 32
view.set([0xdb, size >> 24, size >> 16, size >> 8, size], offset);
offset += 5;
}
view.set(ary, offset);
offset += ary.length;
return offset;
}
// ---------------------------------------------------------
function msgpack_unpack(view) { // @arg Uint8Array:
// @ret Mix:
// @desc: msgpack.unpack
_idx = -1;
return _recursiveMessagePackDecoder(view);
}
function _recursiveMessagePackDecoder(view) {
var size, num = 0,
sign, exp, frac,
obj, key, ary,
type = view[++_idx];
if (type >= 0xe0) { // Negative FixNum (111x xxxx) (-32 ~ -1)
return type - 0x100;
}
if (type < 0xc0) {
if (type < 0x80) { // Positive FixNum (0xxx xxxx) (0 ~ 127)
return type;
}
if (type < 0x90) { // FixMap (1000 xxxx)
num = type - 0x80;
type = 0x80;
} else if (type < 0xa0) { // FixArray (1001 xxxx)
num = type - 0x90;
type = 0x90;
} else if (type < 0xc0) { // FixRaw (101x xxxx)
num = type - 0xa0;
type = 0xa0;
}
}
switch (type) {
case 0xc0: return null;
case 0xc2: return false;
case 0xc3: return true;
case 0xca: // float
num = view[++_idx] * 0x1000000 + (view[++_idx] << 16) +
(view[++_idx] << 8) + view[++_idx];
sign = num > 0x7fffffff; // 1bit
exp = (num >> 23) & 0xff; // 8bits
frac = num & 0x7fffff; // 23bits
if (!num || num === 0x80000000) { // 0.0 or -0.0
return 0;
}
if (exp === 0xff) { // NaN or Infinity
return frac ? NaN : Infinity;
}
return (sign ? -1 : 1) *
(frac | 0x800000) * Math.pow(2, exp - 127 - 23); // 127: bias
case 0xcb: // double
num = view[++_idx] * 0x1000000 + (view[++_idx] << 16) +
(view[++_idx] << 8) + view[++_idx];
sign = num > 0x7fffffff; // 1bit
exp = (num >> 20) & 0x7ff; // 11bits
frac = num & 0xfffff; // 52bits - 32bits (high word)
if (!num || num === 0x80000000) { // 0.0 or -0.0
_idx += 4;
return 0;
}
if (exp === 0x7ff) { // NaN or Infinity
_idx += 4;
return frac ? NaN : Infinity;
}
num = view[++_idx] * 0x1000000 + (view[++_idx] << 16) +
(view[++_idx] << 8) + view[++_idx];
return (sign ? -1 : 1) *
((frac | 0x100000) * Math.pow(2, exp - 1023 - 20) // 1023: bias
+ num * Math.pow(2, exp - 1023 - 52));
// 0xcf: uint64, 0xce: uint32, 0xcd: uint16, 0xcc: uint8
case 0xcf: num = view[++_idx] * 0x1000000 + (view[++_idx] << 16) +
(view[++_idx] << 8) + view[++_idx];
return num * 0x100000000 +
view[++_idx] * 0x1000000 + (view[++_idx] << 16) +
(view[++_idx] << 8) + view[++_idx];
case 0xce: num += view[++_idx] * 0x1000000 + (view[++_idx] << 16);
case 0xcd: num += view[++_idx] << 8;
case 0xcc: return num + view[++_idx];
// 0xd3: int64, 0xd2: int32, 0xd1: int16, 0xd0: int8
case 0xd3: num = view[++_idx];
if (num & 0x80) { // sign -> avoid overflow
return ((num ^ 0xff) * 0x100000000000000 +
(view[++_idx] ^ 0xff) * 0x1000000000000 +
(view[++_idx] ^ 0xff) * 0x10000000000 +
(view[++_idx] ^ 0xff) * 0x100000000 +
(view[++_idx] ^ 0xff) * 0x1000000 +
(view[++_idx] ^ 0xff) * 0x10000 +
(view[++_idx] ^ 0xff) * 0x100 +
(view[++_idx] ^ 0xff) + 1) * -1;
}
return num * 0x100000000000000 +
view[++_idx] * 0x1000000000000 +
view[++_idx] * 0x10000000000 +
view[++_idx] * 0x100000000 +
view[++_idx] * 0x1000000 +
view[++_idx] * 0x10000 +
view[++_idx] * 0x100 +
view[++_idx];
case 0xd2: num = view[++_idx] * 0x1000000 + (view[++_idx] << 16) +
(view[++_idx] << 8) + view[++_idx];
return num < 0x80000000 ? num : num - 0x100000000; // 0x80000000 * 2
case 0xd1: num = (view[++_idx] << 8) + view[++_idx];
return num < 0x8000 ? num : num - 0x10000; // 0x8000 * 2
case 0xd0: num = view[++_idx];
return num < 0x80 ? num : num - 0x100; // 0x80 * 2
// 0xdb: raw32, 0xda: raw16, 0xa0: raw ( string )
case 0xdb: num += view[++_idx] * 0x1000000 + (view[++_idx] << 16);
case 0xda: num += (view[++_idx] << 8) + view[++_idx];
case 0xa0: _idx += num;
return _toString( _utf8_decode(view, _idx - num + 1, num) );
// 0xdf: map32, 0xde: map16, 0x80: map
case 0xdf: num += view[++_idx] * 0x1000000 + (view[++_idx] << 16);
case 0xde: num += (view[++_idx] << 8) + view[++_idx];
case 0x80: obj = {};
while (num--) {
// make key/value pair
size = view[++_idx] - 0xa0;
key = _toString( _utf8_decode(view, _idx + 1, size) );
_idx += size;
obj[key] = _recursiveMessagePackDecoder(view);
}
return obj;
// 0xdd: array32, 0xdc: array16, 0x90: array
case 0xdd: num += view[++_idx] * 0x1000000 + (view[++_idx] << 16);
case 0xdc: num += (view[++_idx] << 8) + view[++_idx];
case 0x90: ary = [];
while (num--) {
ary.push( _recursiveMessagePackDecoder(view) );
}
return ary;
}
throw new TypeError("UNKNOWN_TYPE");
return;
}
function _toString(data) {
var rv = [], i = 0, iz = data.length, bulkSize = 10240;
// avoid String.fromCharCode.apply(null, BigArray) exception
for (; i < iz; i += bulkSize) {
rv.push( String.fromCharCode.apply(null, data.slice(i, i + bulkSize)) );
}
return rv.join("");
}
function _utf8_encode(ary) {
var rv = [], c, i = 0, iz = ary.length;
for (; i < iz; ++i) {
c = ary.charCodeAt(i);
if (c < 0x80) { // ASCII(0x00 ~ 0x7f)
rv.push(c & 0x7f);
} else if (c < 0x0800) {
rv.push(((c >> 6) & 0x1f) | 0xc0, (c & 0x3f) | 0x80);
} else if (c < 0x10000) {
rv.push(((c >> 12) & 0x0f) | 0xe0,
((c >> 6) & 0x3f) | 0x80, (c & 0x3f) | 0x80);
}
}
return rv;
}
function _utf8_decode(view, offset, size) {
var rv = [], c, i = offset, iz = offset + size;
for (; i < iz; ) {
c = view[i++]; // lead byte
rv.push(c < 0x80 ? c : // ASCII(0x00 ~ 0x7f)
c < 0xe0 ? ((c & 0x1f) << 6 | (ary[i++] & 0x3f)) :
((c & 0x0f) << 12 | (ary[i++] & 0x3f) << 6
| (ary[i++] & 0x3f)));
}
return rv;
}
})(this);
@uupaa
Copy link
Author

uupaa commented Nov 18, 2012

a = msgpack.pack({ a: [1,2,3, { b: 4, c: "hoge" }, "abc"] }); // ->
[129, 161, 97, 149, 1, 2, 3, 130, 161, 98, 4, 161, 99, 164, 104, 111, 103, 101, 163, 97, 98, 99]

msgpack.unpack(a); // -> { a: [1,2,3, { b: 4, c: "hoge" }, "abc"] }

@dynamitecoolguy
Copy link

function _utf8_decode()内のary[i++] ですが、これはview[i++]の誤りですか..?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment