587 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			587 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | /* 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 |