|
|
|
|
|
|
|
|
|
|
|
var EE = require('events').EventEmitter; |
|
|
var util = require('util'); |
|
|
var os = require('os'); |
|
|
var assert = require('assert'); |
|
|
var Int64 = require('node-int64'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var isBigEndian = os.endianness() == 'BE'; |
|
|
|
|
|
|
|
|
function nextPow2(size) { |
|
|
return Math.pow(2, Math.ceil(Math.log(size) / Math.LN2)); |
|
|
} |
|
|
|
|
|
|
|
|
function Accumulator(initsize) { |
|
|
this.buf = Buffer.alloc(nextPow2(initsize || 8192)); |
|
|
this.readOffset = 0; |
|
|
this.writeOffset = 0; |
|
|
} |
|
|
|
|
|
exports.Accumulator = Accumulator |
|
|
|
|
|
|
|
|
Accumulator.prototype.writeAvail = function() { |
|
|
return this.buf.length - this.writeOffset; |
|
|
} |
|
|
|
|
|
|
|
|
Accumulator.prototype.readAvail = function() { |
|
|
return this.writeOffset - this.readOffset; |
|
|
} |
|
|
|
|
|
|
|
|
Accumulator.prototype.reserve = function(size) { |
|
|
if (size < this.writeAvail()) { |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
if (this.readOffset > 0) { |
|
|
this.buf.copy(this.buf, 0, this.readOffset, this.writeOffset); |
|
|
this.writeOffset -= this.readOffset; |
|
|
this.readOffset = 0; |
|
|
} |
|
|
|
|
|
|
|
|
if (size < this.writeAvail()) { |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
var buf = Buffer.alloc(nextPow2(this.buf.length + size - this.writeAvail())); |
|
|
this.buf.copy(buf); |
|
|
this.buf = buf; |
|
|
} |
|
|
|
|
|
|
|
|
Accumulator.prototype.append = function(buf) { |
|
|
if (Buffer.isBuffer(buf)) { |
|
|
this.reserve(buf.length); |
|
|
buf.copy(this.buf, this.writeOffset, 0, buf.length); |
|
|
this.writeOffset += buf.length; |
|
|
} else { |
|
|
var size = Buffer.byteLength(buf); |
|
|
this.reserve(size); |
|
|
this.buf.write(buf, this.writeOffset); |
|
|
this.writeOffset += size; |
|
|
} |
|
|
} |
|
|
|
|
|
Accumulator.prototype.assertReadableSize = function(size) { |
|
|
if (this.readAvail() < size) { |
|
|
throw new Error("wanted to read " + size + |
|
|
" bytes but only have " + this.readAvail()); |
|
|
} |
|
|
} |
|
|
|
|
|
Accumulator.prototype.peekString = function(size) { |
|
|
this.assertReadableSize(size); |
|
|
return this.buf.toString('utf-8', this.readOffset, this.readOffset + size); |
|
|
} |
|
|
|
|
|
Accumulator.prototype.readString = function(size) { |
|
|
var str = this.peekString(size); |
|
|
this.readOffset += size; |
|
|
return str; |
|
|
} |
|
|
|
|
|
Accumulator.prototype.peekInt = function(size) { |
|
|
this.assertReadableSize(size); |
|
|
switch (size) { |
|
|
case 1: |
|
|
return this.buf.readInt8(this.readOffset, size); |
|
|
case 2: |
|
|
return isBigEndian ? |
|
|
this.buf.readInt16BE(this.readOffset, size) : |
|
|
this.buf.readInt16LE(this.readOffset, size); |
|
|
case 4: |
|
|
return isBigEndian ? |
|
|
this.buf.readInt32BE(this.readOffset, size) : |
|
|
this.buf.readInt32LE(this.readOffset, size); |
|
|
case 8: |
|
|
var big = this.buf.slice(this.readOffset, this.readOffset + 8); |
|
|
if (isBigEndian) { |
|
|
|
|
|
return new Int64(big); |
|
|
} |
|
|
|
|
|
return new Int64(byteswap64(big)); |
|
|
default: |
|
|
throw new Error("invalid integer size " + size); |
|
|
} |
|
|
} |
|
|
|
|
|
Accumulator.prototype.readInt = function(bytes) { |
|
|
var ival = this.peekInt(bytes); |
|
|
if (ival instanceof Int64 && isFinite(ival.valueOf())) { |
|
|
ival = ival.valueOf(); |
|
|
} |
|
|
this.readOffset += bytes; |
|
|
return ival; |
|
|
} |
|
|
|
|
|
Accumulator.prototype.peekDouble = function() { |
|
|
this.assertReadableSize(8); |
|
|
return isBigEndian ? |
|
|
this.buf.readDoubleBE(this.readOffset) : |
|
|
this.buf.readDoubleLE(this.readOffset); |
|
|
} |
|
|
|
|
|
Accumulator.prototype.readDouble = function() { |
|
|
var dval = this.peekDouble(); |
|
|
this.readOffset += 8; |
|
|
return dval; |
|
|
} |
|
|
|
|
|
Accumulator.prototype.readAdvance = function(size) { |
|
|
if (size > 0) { |
|
|
this.assertReadableSize(size); |
|
|
} else if (size < 0 && this.readOffset + size < 0) { |
|
|
throw new Error("advance with negative offset " + size + |
|
|
" would seek off the start of the buffer"); |
|
|
} |
|
|
this.readOffset += size; |
|
|
} |
|
|
|
|
|
Accumulator.prototype.writeByte = function(value) { |
|
|
this.reserve(1); |
|
|
this.buf.writeInt8(value, this.writeOffset); |
|
|
++this.writeOffset; |
|
|
} |
|
|
|
|
|
Accumulator.prototype.writeInt = function(value, size) { |
|
|
this.reserve(size); |
|
|
switch (size) { |
|
|
case 1: |
|
|
this.buf.writeInt8(value, this.writeOffset); |
|
|
break; |
|
|
case 2: |
|
|
if (isBigEndian) { |
|
|
this.buf.writeInt16BE(value, this.writeOffset); |
|
|
} else { |
|
|
this.buf.writeInt16LE(value, this.writeOffset); |
|
|
} |
|
|
break; |
|
|
case 4: |
|
|
if (isBigEndian) { |
|
|
this.buf.writeInt32BE(value, this.writeOffset); |
|
|
} else { |
|
|
this.buf.writeInt32LE(value, this.writeOffset); |
|
|
} |
|
|
break; |
|
|
default: |
|
|
throw new Error("unsupported integer size " + size); |
|
|
} |
|
|
this.writeOffset += size; |
|
|
} |
|
|
|
|
|
Accumulator.prototype.writeDouble = function(value) { |
|
|
this.reserve(8); |
|
|
if (isBigEndian) { |
|
|
this.buf.writeDoubleBE(value, this.writeOffset); |
|
|
} else { |
|
|
this.buf.writeDoubleLE(value, this.writeOffset); |
|
|
} |
|
|
this.writeOffset += 8; |
|
|
} |
|
|
|
|
|
var BSER_ARRAY = 0x00; |
|
|
var BSER_OBJECT = 0x01; |
|
|
var BSER_STRING = 0x02; |
|
|
var BSER_INT8 = 0x03; |
|
|
var BSER_INT16 = 0x04; |
|
|
var BSER_INT32 = 0x05; |
|
|
var BSER_INT64 = 0x06; |
|
|
var BSER_REAL = 0x07; |
|
|
var BSER_TRUE = 0x08; |
|
|
var BSER_FALSE = 0x09; |
|
|
var BSER_NULL = 0x0a; |
|
|
var BSER_TEMPLATE = 0x0b; |
|
|
var BSER_SKIP = 0x0c; |
|
|
|
|
|
var ST_NEED_PDU = 0; |
|
|
var ST_FILL_PDU = 1; |
|
|
|
|
|
var MAX_INT8 = 127; |
|
|
var MAX_INT16 = 32767; |
|
|
var MAX_INT32 = 2147483647; |
|
|
|
|
|
function BunserBuf() { |
|
|
EE.call(this); |
|
|
this.buf = new Accumulator(); |
|
|
this.state = ST_NEED_PDU; |
|
|
} |
|
|
util.inherits(BunserBuf, EE); |
|
|
exports.BunserBuf = BunserBuf; |
|
|
|
|
|
BunserBuf.prototype.append = function(buf, synchronous) { |
|
|
if (synchronous) { |
|
|
this.buf.append(buf); |
|
|
return this.process(synchronous); |
|
|
} |
|
|
|
|
|
try { |
|
|
this.buf.append(buf); |
|
|
} catch (err) { |
|
|
this.emit('error', err); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.processLater(); |
|
|
} |
|
|
|
|
|
BunserBuf.prototype.processLater = function() { |
|
|
var self = this; |
|
|
process.nextTick(function() { |
|
|
try { |
|
|
self.process(false); |
|
|
} catch (err) { |
|
|
self.emit('error', err); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BunserBuf.prototype.process = function(synchronous) { |
|
|
if (this.state == ST_NEED_PDU) { |
|
|
if (this.buf.readAvail() < 2) { |
|
|
return; |
|
|
} |
|
|
|
|
|
this.expectCode(0); |
|
|
this.expectCode(1); |
|
|
this.pduLen = this.decodeInt(true ); |
|
|
if (this.pduLen === false) { |
|
|
|
|
|
this.buf.readAdvance(-2); |
|
|
return; |
|
|
} |
|
|
|
|
|
this.buf.reserve(this.pduLen); |
|
|
this.state = ST_FILL_PDU; |
|
|
} |
|
|
|
|
|
if (this.state == ST_FILL_PDU) { |
|
|
if (this.buf.readAvail() < this.pduLen) { |
|
|
|
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
var val = this.decodeAny(); |
|
|
if (synchronous) { |
|
|
return val; |
|
|
} |
|
|
this.emit('value', val); |
|
|
this.state = ST_NEED_PDU; |
|
|
} |
|
|
|
|
|
if (!synchronous && this.buf.readAvail() > 0) { |
|
|
this.processLater(); |
|
|
} |
|
|
} |
|
|
|
|
|
BunserBuf.prototype.raise = function(reason) { |
|
|
throw new Error(reason + ", in Buffer of length " + |
|
|
this.buf.buf.length + " (" + this.buf.readAvail() + |
|
|
" readable) at offset " + this.buf.readOffset + " buffer: " + |
|
|
JSON.stringify(this.buf.buf.slice( |
|
|
this.buf.readOffset, this.buf.readOffset + 32).toJSON())); |
|
|
} |
|
|
|
|
|
BunserBuf.prototype.expectCode = function(expected) { |
|
|
var code = this.buf.readInt(1); |
|
|
if (code != expected) { |
|
|
this.raise("expected bser opcode " + expected + " but got " + code); |
|
|
} |
|
|
} |
|
|
|
|
|
BunserBuf.prototype.decodeAny = function() { |
|
|
var code = this.buf.peekInt(1); |
|
|
switch (code) { |
|
|
case BSER_INT8: |
|
|
case BSER_INT16: |
|
|
case BSER_INT32: |
|
|
case BSER_INT64: |
|
|
return this.decodeInt(); |
|
|
case BSER_REAL: |
|
|
this.buf.readAdvance(1); |
|
|
return this.buf.readDouble(); |
|
|
case BSER_TRUE: |
|
|
this.buf.readAdvance(1); |
|
|
return true; |
|
|
case BSER_FALSE: |
|
|
this.buf.readAdvance(1); |
|
|
return false; |
|
|
case BSER_NULL: |
|
|
this.buf.readAdvance(1); |
|
|
return null; |
|
|
case BSER_STRING: |
|
|
return this.decodeString(); |
|
|
case BSER_ARRAY: |
|
|
return this.decodeArray(); |
|
|
case BSER_OBJECT: |
|
|
return this.decodeObject(); |
|
|
case BSER_TEMPLATE: |
|
|
return this.decodeTemplate(); |
|
|
default: |
|
|
this.raise("unhandled bser opcode " + code); |
|
|
} |
|
|
} |
|
|
|
|
|
BunserBuf.prototype.decodeArray = function() { |
|
|
this.expectCode(BSER_ARRAY); |
|
|
var nitems = this.decodeInt(); |
|
|
var arr = []; |
|
|
for (var i = 0; i < nitems; ++i) { |
|
|
arr.push(this.decodeAny()); |
|
|
} |
|
|
return arr; |
|
|
} |
|
|
|
|
|
BunserBuf.prototype.decodeObject = function() { |
|
|
this.expectCode(BSER_OBJECT); |
|
|
var nitems = this.decodeInt(); |
|
|
var res = {}; |
|
|
for (var i = 0; i < nitems; ++i) { |
|
|
var key = this.decodeString(); |
|
|
var val = this.decodeAny(); |
|
|
res[key] = val; |
|
|
} |
|
|
return res; |
|
|
} |
|
|
|
|
|
BunserBuf.prototype.decodeTemplate = function() { |
|
|
this.expectCode(BSER_TEMPLATE); |
|
|
var keys = this.decodeArray(); |
|
|
var nitems = this.decodeInt(); |
|
|
var arr = []; |
|
|
for (var i = 0; i < nitems; ++i) { |
|
|
var obj = {}; |
|
|
for (var keyidx = 0; keyidx < keys.length; ++keyidx) { |
|
|
if (this.buf.peekInt(1) == BSER_SKIP) { |
|
|
this.buf.readAdvance(1); |
|
|
continue; |
|
|
} |
|
|
var val = this.decodeAny(); |
|
|
obj[keys[keyidx]] = val; |
|
|
} |
|
|
arr.push(obj); |
|
|
} |
|
|
return arr; |
|
|
} |
|
|
|
|
|
BunserBuf.prototype.decodeString = function() { |
|
|
this.expectCode(BSER_STRING); |
|
|
var len = this.decodeInt(); |
|
|
return this.buf.readString(len); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BunserBuf.prototype.decodeInt = function(relaxSizeAsserts) { |
|
|
if (relaxSizeAsserts && (this.buf.readAvail() < 1)) { |
|
|
return false; |
|
|
} else { |
|
|
this.buf.assertReadableSize(1); |
|
|
} |
|
|
var code = this.buf.peekInt(1); |
|
|
var size = 0; |
|
|
switch (code) { |
|
|
case BSER_INT8: |
|
|
size = 1; |
|
|
break; |
|
|
case BSER_INT16: |
|
|
size = 2; |
|
|
break; |
|
|
case BSER_INT32: |
|
|
size = 4; |
|
|
break; |
|
|
case BSER_INT64: |
|
|
size = 8; |
|
|
break; |
|
|
default: |
|
|
this.raise("invalid bser int encoding " + code); |
|
|
} |
|
|
|
|
|
if (relaxSizeAsserts && (this.buf.readAvail() < 1 + size)) { |
|
|
return false; |
|
|
} |
|
|
this.buf.readAdvance(1); |
|
|
return this.buf.readInt(size); |
|
|
} |
|
|
|
|
|
|
|
|
function loadFromBuffer(input) { |
|
|
var buf = new BunserBuf(); |
|
|
var result = buf.append(input, true); |
|
|
if (buf.buf.readAvail()) { |
|
|
throw Error( |
|
|
'excess data found after input buffer, use BunserBuf instead'); |
|
|
} |
|
|
if (typeof result === 'undefined') { |
|
|
throw Error( |
|
|
'no bser found in string and no error raised!?'); |
|
|
} |
|
|
return result; |
|
|
} |
|
|
exports.loadFromBuffer = loadFromBuffer |
|
|
|
|
|
|
|
|
|
|
|
function byteswap64(buf) { |
|
|
var swap = Buffer.alloc(buf.length); |
|
|
for (var i = 0; i < buf.length; i++) { |
|
|
swap[i] = buf[buf.length -1 - i]; |
|
|
} |
|
|
return swap; |
|
|
} |
|
|
|
|
|
function dump_int64(buf, val) { |
|
|
|
|
|
var be = val.toBuffer(); |
|
|
|
|
|
if (isBigEndian) { |
|
|
|
|
|
|
|
|
buf.writeByte(BSER_INT64); |
|
|
buf.append(be); |
|
|
return; |
|
|
} |
|
|
|
|
|
var le = byteswap64(be); |
|
|
buf.writeByte(BSER_INT64); |
|
|
buf.append(le); |
|
|
} |
|
|
|
|
|
function dump_int(buf, val) { |
|
|
var abs = Math.abs(val); |
|
|
if (abs <= MAX_INT8) { |
|
|
buf.writeByte(BSER_INT8); |
|
|
buf.writeInt(val, 1); |
|
|
} else if (abs <= MAX_INT16) { |
|
|
buf.writeByte(BSER_INT16); |
|
|
buf.writeInt(val, 2); |
|
|
} else if (abs <= MAX_INT32) { |
|
|
buf.writeByte(BSER_INT32); |
|
|
buf.writeInt(val, 4); |
|
|
} else { |
|
|
dump_int64(buf, new Int64(val)); |
|
|
} |
|
|
} |
|
|
|
|
|
function dump_any(buf, val) { |
|
|
switch (typeof(val)) { |
|
|
case 'number': |
|
|
|
|
|
if (isFinite(val) && Math.floor(val) === val) { |
|
|
dump_int(buf, val); |
|
|
} else { |
|
|
buf.writeByte(BSER_REAL); |
|
|
buf.writeDouble(val); |
|
|
} |
|
|
return; |
|
|
case 'string': |
|
|
buf.writeByte(BSER_STRING); |
|
|
dump_int(buf, Buffer.byteLength(val)); |
|
|
buf.append(val); |
|
|
return; |
|
|
case 'boolean': |
|
|
buf.writeByte(val ? BSER_TRUE : BSER_FALSE); |
|
|
return; |
|
|
case 'object': |
|
|
if (val === null) { |
|
|
buf.writeByte(BSER_NULL); |
|
|
return; |
|
|
} |
|
|
if (val instanceof Int64) { |
|
|
dump_int64(buf, val); |
|
|
return; |
|
|
} |
|
|
if (Array.isArray(val)) { |
|
|
buf.writeByte(BSER_ARRAY); |
|
|
dump_int(buf, val.length); |
|
|
for (var i = 0; i < val.length; ++i) { |
|
|
dump_any(buf, val[i]); |
|
|
} |
|
|
return; |
|
|
} |
|
|
buf.writeByte(BSER_OBJECT); |
|
|
var keys = Object.keys(val); |
|
|
|
|
|
|
|
|
var num_keys = keys.length; |
|
|
for (var i = 0; i < keys.length; ++i) { |
|
|
var key = keys[i]; |
|
|
var v = val[key]; |
|
|
if (typeof(v) == 'undefined') { |
|
|
num_keys--; |
|
|
} |
|
|
} |
|
|
dump_int(buf, num_keys); |
|
|
for (var i = 0; i < keys.length; ++i) { |
|
|
var key = keys[i]; |
|
|
var v = val[key]; |
|
|
if (typeof(v) == 'undefined') { |
|
|
|
|
|
continue; |
|
|
} |
|
|
dump_any(buf, key); |
|
|
try { |
|
|
dump_any(buf, v); |
|
|
} catch (e) { |
|
|
throw new Error( |
|
|
e.message + ' (while serializing object property with name `' + |
|
|
key + "')"); |
|
|
} |
|
|
} |
|
|
return; |
|
|
|
|
|
default: |
|
|
throw new Error('cannot serialize type ' + typeof(val) + ' to BSER'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function dumpToBuffer(val) { |
|
|
var buf = new Accumulator(); |
|
|
|
|
|
buf.writeByte(0); |
|
|
buf.writeByte(1); |
|
|
|
|
|
buf.writeByte(BSER_INT32); |
|
|
buf.writeInt(0, 4); |
|
|
|
|
|
dump_any(buf, val); |
|
|
|
|
|
|
|
|
var off = buf.writeOffset; |
|
|
var len = off - 7 ; |
|
|
buf.writeOffset = 3; |
|
|
buf.writeInt(len, 4); |
|
|
buf.writeOffset = off; |
|
|
|
|
|
return buf.buf.slice(0, off); |
|
|
} |
|
|
exports.dumpToBuffer = dumpToBuffer |
|
|
|