exports.BinaryPack = { unpack: function(data){ var unpacker = new Unpacker(data); return unpacker.unpack(); }, pack: function(data){ var packer = new Packer(); var buffer = packer.pack(data); return buffer; } }; function Unpacker (data){ // Data is ArrayBuffer this.index = 0; this.dataBuffer = data; this.dataView = new Uint8Array(this.dataBuffer); this.length = this.dataBuffer.byteLength; } Unpacker.prototype.unpack = function(){ var type = this.unpack_uint8(); if (type < 0x80){ var positive_fixnum = type; return positive_fixnum; } else if ((type ^ 0xe0) < 0x20){ var negative_fixnum = (type ^ 0xe0) - 0x20; return negative_fixnum; } var size; if ((size = type ^ 0xa0) <= 0x0f){ return this.unpack_raw(size); } else if ((size = type ^ 0xb0) <= 0x0f){ return this.unpack_string(size); } else if ((size = type ^ 0x90) <= 0x0f){ return this.unpack_array(size); } else if ((size = type ^ 0x80) <= 0x0f){ return this.unpack_map(size); } switch(type){ case 0xc0: return null; case 0xc1: return undefined; case 0xc2: return false; case 0xc3: return true; case 0xca: return this.unpack_float(); case 0xcb: return this.unpack_double(); case 0xcc: return this.unpack_uint8(); case 0xcd: return this.unpack_uint16(); case 0xce: return this.unpack_uint32(); case 0xcf: return this.unpack_uint64(); case 0xd0: return this.unpack_int8(); case 0xd1: return this.unpack_int16(); case 0xd2: return this.unpack_int32(); case 0xd3: return this.unpack_int64(); case 0xd4: return undefined; case 0xd5: return undefined; case 0xd6: return undefined; case 0xd7: return undefined; case 0xd8: size = this.unpack_uint16(); return this.unpack_string(size); case 0xd9: size = this.unpack_uint32(); return this.unpack_string(size); case 0xda: size = this.unpack_uint16(); return this.unpack_raw(size); case 0xdb: size = this.unpack_uint32(); return this.unpack_raw(size); case 0xdc: size = this.unpack_uint16(); return this.unpack_array(size); case 0xdd: size = this.unpack_uint32(); return this.unpack_array(size); case 0xde: size = this.unpack_uint16(); return this.unpack_map(size); case 0xdf: size = this.unpack_uint32(); return this.unpack_map(size); } } Unpacker.prototype.unpack_uint8 = function(){ var byte = this.dataView[this.index] & 0xff; this.index++; return byte; }; Unpacker.prototype.unpack_uint16 = function(){ var bytes = this.read(2); var uint16 = ((bytes[0] & 0xff) * 256) + (bytes[1] & 0xff); this.index += 2; return uint16; } Unpacker.prototype.unpack_uint32 = function(){ var bytes = this.read(4); var uint32 = ((bytes[0] * 256 + bytes[1]) * 256 + bytes[2]) * 256 + bytes[3]; this.index += 4; return uint32; } Unpacker.prototype.unpack_uint64 = function(){ var bytes = this.read(8); var uint64 = ((((((bytes[0] * 256 + bytes[1]) * 256 + bytes[2]) * 256 + bytes[3]) * 256 + bytes[4]) * 256 + bytes[5]) * 256 + bytes[6]) * 256 + bytes[7]; this.index += 8; return uint64; } Unpacker.prototype.unpack_int8 = function(){ var uint8 = this.unpack_uint8(); return (uint8 < 0x80 ) ? uint8 : uint8 - (1 << 8); }; Unpacker.prototype.unpack_int16 = function(){ var uint16 = this.unpack_uint16(); return (uint16 < 0x8000 ) ? uint16 : uint16 - (1 << 16); } Unpacker.prototype.unpack_int32 = function(){ var uint32 = this.unpack_uint32(); return (uint32 < Math.pow(2, 31) ) ? uint32 : uint32 - Math.pow(2, 32); } Unpacker.prototype.unpack_int64 = function(){ var uint64 = this.unpack_uint64(); return (uint64 < Math.pow(2, 63) ) ? uint64 : uint64 - Math.pow(2, 64); } Unpacker.prototype.unpack_raw = function(size){ if ( this.length < this.index + size){ throw new Error('BinaryPackFailure: index is out of range' + ' ' + this.index + ' ' + size + ' ' + this.length); } var buf = this.dataBuffer.slice(this.index, this.index + size); this.index += size; //buf = util.bufferToString(buf); return buf; } Unpacker.prototype.unpack_string = function(size){ var bytes = this.read(size); var i = 0, str = '', c, code; while(i < size){ c = bytes[i]; if ( c < 128){ str += String.fromCharCode(c); i++; } else if ((c ^ 0xc0) < 32){ code = ((c ^ 0xc0) << 6) | (bytes[i+1] & 63); str += String.fromCharCode(code); i += 2; } else { code = ((c & 15) << 12) | ((bytes[i+1] & 63) << 6) | (bytes[i+2] & 63); str += String.fromCharCode(code); i += 3; } } this.index += size; return str; } Unpacker.prototype.unpack_array = function(size){ var objects = new Array(size); for(var i = 0; i < size ; i++){ objects[i] = this.unpack(); } return objects; } Unpacker.prototype.unpack_map = function(size){ var map = {}; for(var i = 0; i < size ; i++){ var key = this.unpack(); var value = this.unpack(); map[key] = value; } return map; } Unpacker.prototype.unpack_float = function(){ var uint32 = this.unpack_uint32(); var sign = uint32 >> 31; var exp = ((uint32 >> 23) & 0xff) - 127; var fraction = ( uint32 & 0x7fffff ) | 0x800000; return (sign == 0 ? 1 : -1) * fraction * Math.pow(2, exp - 23); } Unpacker.prototype.unpack_double = function(){ var h32 = this.unpack_uint32(); var l32 = this.unpack_uint32(); var sign = h32 >> 31; var exp = ((h32 >> 20) & 0x7ff) - 1023; var hfrac = ( h32 & 0xfffff ) | 0x100000; var frac = hfrac * Math.pow(2, exp - 20) + l32 * Math.pow(2, exp - 52); return (sign == 0 ? 1 : -1) * frac; } Unpacker.prototype.read = function(length){ var j = this.index; if (j + length <= this.length) { return this.dataView.subarray(j, j + length); } else { throw new Error('BinaryPackFailure: read index out of range'); } } function Packer (){ this.bufferBuilder = new BufferBuilder(); } Packer.prototype.pack = function(value){ var type = typeof(value); if (type == 'string'){ this.pack_string(value); } else if (type == 'number'){ if (Math.floor(value) === value){ this.pack_integer(value); } else{ this.pack_double(value); } } else if (type == 'boolean'){ if (value === true){ this.bufferBuilder.append(0xc3); } else if (value === false){ this.bufferBuilder.append(0xc2); } } else if (type == 'undefined'){ this.bufferBuilder.append(0xc0); } else if (type == 'object'){ if (value === null){ this.bufferBuilder.append(0xc0); } else { var constructor = value.constructor; if (constructor == Array){ this.pack_array(value); } else if (constructor == Blob || constructor == File) { this.pack_bin(value); } else if (constructor == ArrayBuffer) { if(binaryFeatures.useArrayBufferView) { this.pack_bin(new Uint8Array(value)); } else { this.pack_bin(value); } } else if ('BYTES_PER_ELEMENT' in value){ if(binaryFeatures.useArrayBufferView) { this.pack_bin(value); } else { this.pack_bin(value.buffer); } } else if (constructor == Object){ this.pack_object(value); } else if (constructor == Date){ this.pack_string(value.toString()); } else if (typeof value.toBinaryPack == 'function'){ this.bufferBuilder.append(value.toBinaryPack()); } else { throw new Error('Type "' + constructor.toString() + '" not yet supported'); } } } else { throw new Error('Type "' + type + '" not yet supported'); } return this.bufferBuilder.getBuffer(); } Packer.prototype.pack_bin = function(blob){ var length = blob.length || blob.byteLength || blob.size; if (length <= 0x0f){ this.pack_uint8(0xa0 + length); } else if (length <= 0xffff){ this.bufferBuilder.append(0xda) ; this.pack_uint16(length); } else if (length <= 0xffffffff){ this.bufferBuilder.append(0xdb); this.pack_uint32(length); } else{ throw new Error('Invalid length'); return; } this.bufferBuilder.append(blob); } Packer.prototype.pack_string = function(str){ var length = str.length; if (length <= 0x0f){ this.pack_uint8(0xb0 + length); } else if (length <= 0xffff){ this.bufferBuilder.append(0xd8) ; this.pack_uint16(length); } else if (length <= 0xffffffff){ this.bufferBuilder.append(0xd9); this.pack_uint32(length); } else{ throw new Error('Invalid length'); return; } this.bufferBuilder.append(str); } Packer.prototype.pack_array = function(ary){ var length = ary.length; if (length <= 0x0f){ this.pack_uint8(0x90 + length); } else if (length <= 0xffff){ this.bufferBuilder.append(0xdc) this.pack_uint16(length); } else if (length <= 0xffffffff){ this.bufferBuilder.append(0xdd); this.pack_uint32(length); } else{ throw new Error('Invalid length'); } for(var i = 0; i < length ; i++){ this.pack(ary[i]); } } Packer.prototype.pack_integer = function(num){ if ( -0x20 <= num && num <= 0x7f){ this.bufferBuilder.append(num & 0xff); } else if (0x00 <= num && num <= 0xff){ this.bufferBuilder.append(0xcc); this.pack_uint8(num); } else if (-0x80 <= num && num <= 0x7f){ this.bufferBuilder.append(0xd0); this.pack_int8(num); } else if ( 0x0000 <= num && num <= 0xffff){ this.bufferBuilder.append(0xcd); this.pack_uint16(num); } else if (-0x8000 <= num && num <= 0x7fff){ this.bufferBuilder.append(0xd1); this.pack_int16(num); } else if ( 0x00000000 <= num && num <= 0xffffffff){ this.bufferBuilder.append(0xce); this.pack_uint32(num); } else if (-0x80000000 <= num && num <= 0x7fffffff){ this.bufferBuilder.append(0xd2); this.pack_int32(num); } else if (-0x8000000000000000 <= num && num <= 0x7FFFFFFFFFFFFFFF){ this.bufferBuilder.append(0xd3); this.pack_int64(num); } else if (0x0000000000000000 <= num && num <= 0xFFFFFFFFFFFFFFFF){ this.bufferBuilder.append(0xcf); this.pack_uint64(num); } else{ throw new Error('Invalid integer'); } } Packer.prototype.pack_double = function(num){ var sign = 0; if (num < 0){ sign = 1; num = -num; } var exp = Math.floor(Math.log(num) / Math.LN2); var frac0 = num / Math.pow(2, exp) - 1; var frac1 = Math.floor(frac0 * Math.pow(2, 52)); var b32 = Math.pow(2, 32); var h32 = (sign << 31) | ((exp+1023) << 20) | (frac1 / b32) & 0x0fffff; var l32 = frac1 % b32; this.bufferBuilder.append(0xcb); this.pack_int32(h32); this.pack_int32(l32); } Packer.prototype.pack_object = function(obj){ var keys = Object.keys(obj); var length = keys.length; if (length <= 0x0f){ this.pack_uint8(0x80 + length); } else if (length <= 0xffff){ this.bufferBuilder.append(0xde); this.pack_uint16(length); } else if (length <= 0xffffffff){ this.bufferBuilder.append(0xdf); this.pack_uint32(length); } else{ throw new Error('Invalid length'); } for(var prop in obj){ if (obj.hasOwnProperty(prop)){ this.pack(prop); this.pack(obj[prop]); } } } Packer.prototype.pack_uint8 = function(num){ this.bufferBuilder.append(num); } Packer.prototype.pack_uint16 = function(num){ this.bufferBuilder.append(num >> 8); this.bufferBuilder.append(num & 0xff); } Packer.prototype.pack_uint32 = function(num){ var n = num & 0xffffffff; this.bufferBuilder.append((n & 0xff000000) >>> 24); this.bufferBuilder.append((n & 0x00ff0000) >>> 16); this.bufferBuilder.append((n & 0x0000ff00) >>> 8); this.bufferBuilder.append((n & 0x000000ff)); } Packer.prototype.pack_uint64 = function(num){ var high = num / Math.pow(2, 32); var low = num % Math.pow(2, 32); this.bufferBuilder.append((high & 0xff000000) >>> 24); this.bufferBuilder.append((high & 0x00ff0000) >>> 16); this.bufferBuilder.append((high & 0x0000ff00) >>> 8); this.bufferBuilder.append((high & 0x000000ff)); this.bufferBuilder.append((low & 0xff000000) >>> 24); this.bufferBuilder.append((low & 0x00ff0000) >>> 16); this.bufferBuilder.append((low & 0x0000ff00) >>> 8); this.bufferBuilder.append((low & 0x000000ff)); } Packer.prototype.pack_int8 = function(num){ this.bufferBuilder.append(num & 0xff); } Packer.prototype.pack_int16 = function(num){ this.bufferBuilder.append((num & 0xff00) >> 8); this.bufferBuilder.append(num & 0xff); } Packer.prototype.pack_int32 = function(num){ this.bufferBuilder.append((num >>> 24) & 0xff); this.bufferBuilder.append((num & 0x00ff0000) >>> 16); this.bufferBuilder.append((num & 0x0000ff00) >>> 8); this.bufferBuilder.append((num & 0x000000ff)); } Packer.prototype.pack_int64 = function(num){ var high = Math.floor(num / Math.pow(2, 32)); var low = num % Math.pow(2, 32); this.bufferBuilder.append((high & 0xff000000) >>> 24); this.bufferBuilder.append((high & 0x00ff0000) >>> 16); this.bufferBuilder.append((high & 0x0000ff00) >>> 8); this.bufferBuilder.append((high & 0x000000ff)); this.bufferBuilder.append((low & 0xff000000) >>> 24); this.bufferBuilder.append((low & 0x00ff0000) >>> 16); this.bufferBuilder.append((low & 0x0000ff00) >>> 8); this.bufferBuilder.append((low & 0x000000ff)); }