var BufferCursor = require('buffercursor'); var BufferBuilder = require('./bufferbuilder'); BinaryPack = { unpack: function(data){ var unpacker = new BinaryPack.Unpacker(data); return unpacker.unpack(); }, pack: function(data){ var packer = new BinaryPack.Packer(); var buffer = packer.pack(data); return buffer; } }; BinaryPack.Unpacker = function(buffer){ this.cursor = new BufferCursor(buffer); this.length = buffer.length; } BinaryPack.Unpacker.prototype.unpack = function(){ var type = this.cursor.readUInt8(); 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.readraw(size); } else if ((size = type ^ 0xb0) <= 0x0f){ return this.readstring(size); } else if ((size = type ^ 0x90) <= 0x0f){ return this.readarray(size); } else if ((size = type ^ 0x80) <= 0x0f){ return this.readmap(size); } switch(type){ case 0xc0: return null; case 0xc1: return undefined; case 0xc2: return false; case 0xc3: return true; case 0xca: return this.cursor.readFloatBE(); case 0xcb: return this.cursor.readDoubleBE(); case 0xcc: return this.cursor.readUInt8(); case 0xcd: return this.cursor.readUInt16BE(); case 0xce: return this.cursor.readUInt32BE(); case 0xcf: return this.readUInt64(); case 0xd0: return this.cursor.readInt8(); case 0xd1: return this.cursor.readInt16BE(); case 0xd2: return this.cursor.readInt32BE(); case 0xd3: return this.readInt64(); case 0xd4: return undefined; case 0xd5: return undefined; case 0xd6: return undefined; case 0xd7: return undefined; case 0xd8: size = this.cursor.readUInt16BE(); return this.readstring(size); case 0xd9: size = this.cursor.readUInt32BE(); return this.readstring(size); case 0xda: size = this.cursor.readUInt16BE(); return this.readraw(size); case 0xdb: size = this.cursor.readUInt32BE(); return this.readraw(size); case 0xdc: size = this.cursor.readUInt16BE(); return this.readarray(size); case 0xdd: size = this.cursor.readUInt32BE(); return this.readarray(size); case 0xde: size = this.cursor.readUInt16BE(); return this.readmap(size); case 0xdf: size = this.cursor.readUInt32BE(); return this.readmap(size); } } BinaryPack.Unpacker.prototype.readUInt64 = function(){ var bytes = this.cursor.slice(8); return ((((((bytes[0] * 256 + bytes[1]) * 256 + bytes[2]) * 256 + bytes[3]) * 256 + bytes[4]) * 256 + bytes[5]) * 256 + bytes[6]) * 256 + bytes[7]; } BinaryPack.Unpacker.prototype.readInt64 = function(){ var uint64 = this.readInt64(); return (uint64 < Math.pow(2, 63) ) ? uint64 : uint64 - Math.pow(2, 64); } BinaryPack.Unpacker.prototype.readraw = function(size){ return this.cursor.slice(size).buffer; } BinaryPack.Unpacker.prototype.readstring = function(size){ return this.cursor.toString('utf8', size); } BinaryPack.Unpacker.prototype.readarray = function(size){ var objects = new Array(size); for(var i = 0; i < size ; i++){ objects[i] = this.unpack(); } return objects; } BinaryPack.Unpacker.prototype.readmap = function(size){ var map = {}; for(var i = 0; i < size ; i++){ var key = this.unpack(); var value = this.unpack(); map[key] = value; } return map; } BinaryPack.Packer = function(){ this.bufferBuilder = new BufferBuilder(); } BinaryPack.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, 2); } else if (value === false){ this.bufferBuilder.append(0xc2, 2); } } else if (type == 'undefined'){ this.bufferBuilder.append(0xc0, 2); } else if (type == 'object'){ if (value === null){ this.bufferBuilder.append(0xc0, 2); } else { var constructor = value.constructor; if (constructor == Array){ this.pack_array(value); } else if (Buffer.isBuffer(value)) { this.pack_bin(value); } 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(), 1); } else { throw new Error('Type "' + constructor.toString() + '" not yet supported'); } } } else { throw new Error('Type "' + type + '" not yet supported'); } return this.bufferBuilder.getBuffer(); } BinaryPack.Packer.prototype.pack_bin = function(blob){ var length = blob.length || blob.byteLength || blob.size; if (length <= 0x0f){ this.bufferBuilder.append(0xa0 + length, 2); } else if (length <= 0xffff){ this.bufferBuilder.append(0xda, 2) ; this.bufferBuilder.append(length, 3); } else if (length <= 0xffffffff){ this.bufferBuilder.append(0xdb, 2); this.bufferBuilder.append(length, 4); } else{ throw new Error('Invalid length'); return; } this.bufferBuilder.append(blob, 1); } BinaryPack.Packer.prototype.pack_string = function(str){ var length = Buffer.byteLength(str); if (length <= 0x0f){ this.bufferBuilder.append(0xb0 + length, 2); } else if (length <= 0xffff){ this.bufferBuilder.append(0xd8, 2) ; this.bufferBuilder.append(length, 3); } else if (length <= 0xffffffff){ this.bufferBuilder.append(0xd9, 2); this.bufferBuilder.append(length, 4); } else{ throw new Error('Invalid length'); return; } this.bufferBuilder.append(str, 0); } BinaryPack.Packer.prototype.pack_array = function(ary){ var length = ary.length; if (length <= 0x0f){ this.bufferBuilder.append(0x90 + length, 2); } else if (length <= 0xffff){ this.bufferBuilder.append(0xdc, 2) this.bufferBuilder.append(length, 3); } else if (length <= 0xffffffff){ this.bufferBuilder.append(0xdd, 2); this.bufferBuilder.append(length, 4); } else{ throw new Error('Invalid length'); } for(var i = 0; i < length ; i++){ this.pack(ary[i]); } } BinaryPack.Packer.prototype.pack_integer = function(num){ if ( -0x20 <= num && num <= 0x7f){ this.bufferBuilder.append(num & 0xff, 2); } else if (0x00 <= num && num <= 0xff){ this.bufferBuilder.append(0xcc, 2); this.bufferBuilder.append(num, 2); } else if (-0x80 <= num && num <= 0x7f){ this.bufferBuilder.append(0xd0, 2); this.bufferBuilder.append(num, 5); } else if ( 0x0000 <= num && num <= 0xffff){ this.bufferBuilder.append(0xcd, 2); this.bufferBuilder.append(num, 3); } else if (-0x8000 <= num && num <= 0x7fff){ this.bufferBuilder.append(0xd1, 2); this.bufferBuilder.append(num, 6); } else if ( 0x00000000 <= num && num <= 0xffffffff){ this.bufferBuilder.append(0xce, 2); this.bufferBuilder.append(num, 4); } else if (-0x80000000 <= num && num <= 0x7fffffff){ this.bufferBuilder.append(0xd2, 2); this.bufferBuilder.append(num, 7); } else if (-0x8000000000000000 <= num && num <= 0x7FFFFFFFFFFFFFFF){ this.bufferBuilder.append(0xd3, 2); this.pack_int64(num); } else if (0x0000000000000000 <= num && num <= 0xFFFFFFFFFFFFFFFF){ this.bufferBuilder.append(0xcf); this.pack_uint64(num); } else{ throw new Error('Invalid integer'); } } BinaryPack.Packer.prototype.pack_double = function(num){ this.bufferBuilder.append(0xcb, 2); this.bufferBuilder.append(num, 9); } BinaryPack.Packer.prototype.pack_object = function(obj){ var keys = Object.keys(obj); var length = keys.length; if (length <= 0x0f){ this.bufferBuilder.append(0x80 + length, 2); } else if (length <= 0xffff){ this.bufferBuilder.append(0xde, 2); this.bufferBuilder.append(length, 3); } else if (length <= 0xffffffff){ this.bufferBuilder.append(0xdf, 2); this.bufferBuilder.append(length, 4); } else{ throw new Error('Invalid length'); } for(var prop in obj){ if (obj.hasOwnProperty(prop)){ this.pack(prop); this.pack(obj[prop]); } } } BinaryPack.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, 2); this.bufferBuilder.append((high & 0x00ff0000) >>> 16, 2); this.bufferBuilder.append((high & 0x0000ff00) >>> 8, 2); this.bufferBuilder.append((high & 0x000000ff), 2); this.bufferBuilder.append((low & 0xff000000) >>> 24, 2); this.bufferBuilder.append((low & 0x00ff0000) >>> 16, 2); this.bufferBuilder.append((low & 0x0000ff00) >>> 8, 2); this.bufferBuilder.append((low & 0x000000ff), 2); } BinaryPack.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, 2); this.bufferBuilder.append((high & 0x00ff0000) >>> 16, 2); this.bufferBuilder.append((high & 0x0000ff00) >>> 8, 2); this.bufferBuilder.append((high & 0x000000ff), 2); this.bufferBuilder.append((low & 0xff000000) >>> 24, 2); this.bufferBuilder.append((low & 0x00ff0000) >>> 16, 2); this.bufferBuilder.append((low & 0x0000ff00) >>> 8, 2); this.bufferBuilder.append((low & 0x000000ff), 2); } module.exports = BinaryPack;