diff options
Diffstat (limited to 'node_modules/bser/index.js')
-rw-r--r-- | node_modules/bser/index.js | 586 |
1 files changed, 586 insertions, 0 deletions
diff --git a/node_modules/bser/index.js b/node_modules/bser/index.js new file mode 100644 index 0000000..4ae14d3 --- /dev/null +++ b/node_modules/bser/index.js @@ -0,0 +1,586 @@ +/* Copyright 2015-present Facebook, Inc. + * Licensed under the Apache License, Version 2.0 */ + +var EE = require('events').EventEmitter; +var util = require('util'); +var os = require('os'); +var assert = require('assert'); +var Int64 = require('node-int64'); + +// BSER uses the local endianness to reduce byte swapping overheads +// (the protocol is expressly local IPC only). We need to tell node +// to use the native endianness when reading various native values. +var isBigEndian = os.endianness() == 'BE'; + +// Find the next power-of-2 >= size +function nextPow2(size) { + return Math.pow(2, Math.ceil(Math.log(size) / Math.LN2)); +} + +// Expandable buffer that we can provide a size hint for +function Accumulator(initsize) { + this.buf = Buffer.alloc(nextPow2(initsize || 8192)); + this.readOffset = 0; + this.writeOffset = 0; +} +// For testing +exports.Accumulator = Accumulator + +// How much we can write into this buffer without allocating +Accumulator.prototype.writeAvail = function() { + return this.buf.length - this.writeOffset; +} + +// How much we can read +Accumulator.prototype.readAvail = function() { + return this.writeOffset - this.readOffset; +} + +// Ensure that we have enough space for size bytes +Accumulator.prototype.reserve = function(size) { + if (size < this.writeAvail()) { + return; + } + + // If we can make room by shunting down, do so + if (this.readOffset > 0) { + this.buf.copy(this.buf, 0, this.readOffset, this.writeOffset); + this.writeOffset -= this.readOffset; + this.readOffset = 0; + } + + // If we made enough room, no need to allocate more + if (size < this.writeAvail()) { + return; + } + + // Allocate a replacement and copy it in + var buf = Buffer.alloc(nextPow2(this.buf.length + size - this.writeAvail())); + this.buf.copy(buf); + this.buf = buf; +} + +// Append buffer or string. Will resize as needed +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) { + // On a big endian system we can simply pass the buffer directly + return new Int64(big); + } + // Otherwise we need to byteswap + 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; // Need to read and decode PDU length +var ST_FILL_PDU = 1; // Know the length, need to read whole content + +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; + } + // Arrange to decode later. This allows the consuming + // application to make progress with other work in the + // case that we have a lot of subscription updates coming + // in from a large tree. + this.processLater(); +} + +BunserBuf.prototype.processLater = function() { + var self = this; + process.nextTick(function() { + try { + self.process(false); + } catch (err) { + self.emit('error', err); + } + }); +} + +// Do something with the buffer to advance our state. +// If we're running synchronously we'll return either +// the value we've decoded or undefined if we don't +// yet have enought data. +// If we're running asynchronously, we'll emit the value +// when it becomes ready and schedule another invocation +// of process on the next tick if we still have data we +// can process. +BunserBuf.prototype.process = function(synchronous) { + if (this.state == ST_NEED_PDU) { + if (this.buf.readAvail() < 2) { + return; + } + // Validate BSER header + this.expectCode(0); + this.expectCode(1); + this.pduLen = this.decodeInt(true /* relaxed */); + if (this.pduLen === false) { + // Need more data, walk backwards + this.buf.readAdvance(-2); + return; + } + // Ensure that we have a big enough buffer to read the rest of the PDU + this.buf.reserve(this.pduLen); + this.state = ST_FILL_PDU; + } + + if (this.state == ST_FILL_PDU) { + if (this.buf.readAvail() < this.pduLen) { + // Need more data + return; + } + + // We have enough to decode it + 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); +} + +// This is unusual compared to the other decode functions in that +// we may not have enough data available to satisfy the read, and +// we don't want to throw. This is only true when we're reading +// the PDU length from the PDU header; we'll set relaxSizeAsserts +// in that case. +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); +} + +// synchronously BSER decode a string and return the value +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 + +// Byteswap an arbitrary buffer, flipping from one endian +// to the other, returning a new buffer with the resultant data +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) { + // Get the raw bytes. The Int64 buffer is big endian + var be = val.toBuffer(); + + if (isBigEndian) { + // We're a big endian system, so the buffer is exactly how we + // want it to be + buf.writeByte(BSER_INT64); + buf.append(be); + return; + } + // We need to byte swap to get the correct representation + 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': + // check if it is an integer or a float + 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); + + // First pass to compute number of defined keys + 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') { + // Don't include it + 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'); + } +} + +// BSER encode value and return a buffer of the contents +function dumpToBuffer(val) { + var buf = new Accumulator(); + // Build out the header + buf.writeByte(0); + buf.writeByte(1); + // Reserve room for an int32 to hold our PDU length + buf.writeByte(BSER_INT32); + buf.writeInt(0, 4); // We'll come back and fill this in at the end + + dump_any(buf, val); + + // Compute PDU length + var off = buf.writeOffset; + var len = off - 7 /* the header length */; + buf.writeOffset = 3; // The length value to fill in + buf.writeInt(len, 4); // write the length in the space we reserved + buf.writeOffset = off; + + return buf.buf.slice(0, off); +} +exports.dumpToBuffer = dumpToBuffer |