| 1 | /* Copyright 2015-present Facebook, Inc.
|
|---|
| 2 | * Licensed under the Apache License, Version 2.0 */
|
|---|
| 3 |
|
|---|
| 4 | var EE = require('events').EventEmitter;
|
|---|
| 5 | var util = require('util');
|
|---|
| 6 | var os = require('os');
|
|---|
| 7 | var assert = require('assert');
|
|---|
| 8 | var Int64 = require('node-int64');
|
|---|
| 9 |
|
|---|
| 10 | // BSER uses the local endianness to reduce byte swapping overheads
|
|---|
| 11 | // (the protocol is expressly local IPC only). We need to tell node
|
|---|
| 12 | // to use the native endianness when reading various native values.
|
|---|
| 13 | var isBigEndian = os.endianness() == 'BE';
|
|---|
| 14 |
|
|---|
| 15 | // Find the next power-of-2 >= size
|
|---|
| 16 | function nextPow2(size) {
|
|---|
| 17 | return Math.pow(2, Math.ceil(Math.log(size) / Math.LN2));
|
|---|
| 18 | }
|
|---|
| 19 |
|
|---|
| 20 | // Expandable buffer that we can provide a size hint for
|
|---|
| 21 | function Accumulator(initsize) {
|
|---|
| 22 | this.buf = Buffer.alloc(nextPow2(initsize || 8192));
|
|---|
| 23 | this.readOffset = 0;
|
|---|
| 24 | this.writeOffset = 0;
|
|---|
| 25 | }
|
|---|
| 26 | // For testing
|
|---|
| 27 | exports.Accumulator = Accumulator
|
|---|
| 28 |
|
|---|
| 29 | // How much we can write into this buffer without allocating
|
|---|
| 30 | Accumulator.prototype.writeAvail = function() {
|
|---|
| 31 | return this.buf.length - this.writeOffset;
|
|---|
| 32 | }
|
|---|
| 33 |
|
|---|
| 34 | // How much we can read
|
|---|
| 35 | Accumulator.prototype.readAvail = function() {
|
|---|
| 36 | return this.writeOffset - this.readOffset;
|
|---|
| 37 | }
|
|---|
| 38 |
|
|---|
| 39 | // Ensure that we have enough space for size bytes
|
|---|
| 40 | Accumulator.prototype.reserve = function(size) {
|
|---|
| 41 | if (size < this.writeAvail()) {
|
|---|
| 42 | return;
|
|---|
| 43 | }
|
|---|
| 44 |
|
|---|
| 45 | // If we can make room by shunting down, do so
|
|---|
| 46 | if (this.readOffset > 0) {
|
|---|
| 47 | this.buf.copy(this.buf, 0, this.readOffset, this.writeOffset);
|
|---|
| 48 | this.writeOffset -= this.readOffset;
|
|---|
| 49 | this.readOffset = 0;
|
|---|
| 50 | }
|
|---|
| 51 |
|
|---|
| 52 | // If we made enough room, no need to allocate more
|
|---|
| 53 | if (size < this.writeAvail()) {
|
|---|
| 54 | return;
|
|---|
| 55 | }
|
|---|
| 56 |
|
|---|
| 57 | // Allocate a replacement and copy it in
|
|---|
| 58 | var buf = Buffer.alloc(nextPow2(this.buf.length + size - this.writeAvail()));
|
|---|
| 59 | this.buf.copy(buf);
|
|---|
| 60 | this.buf = buf;
|
|---|
| 61 | }
|
|---|
| 62 |
|
|---|
| 63 | // Append buffer or string. Will resize as needed
|
|---|
| 64 | Accumulator.prototype.append = function(buf) {
|
|---|
| 65 | if (Buffer.isBuffer(buf)) {
|
|---|
| 66 | this.reserve(buf.length);
|
|---|
| 67 | buf.copy(this.buf, this.writeOffset, 0, buf.length);
|
|---|
| 68 | this.writeOffset += buf.length;
|
|---|
| 69 | } else {
|
|---|
| 70 | var size = Buffer.byteLength(buf);
|
|---|
| 71 | this.reserve(size);
|
|---|
| 72 | this.buf.write(buf, this.writeOffset);
|
|---|
| 73 | this.writeOffset += size;
|
|---|
| 74 | }
|
|---|
| 75 | }
|
|---|
| 76 |
|
|---|
| 77 | Accumulator.prototype.assertReadableSize = function(size) {
|
|---|
| 78 | if (this.readAvail() < size) {
|
|---|
| 79 | throw new Error("wanted to read " + size +
|
|---|
| 80 | " bytes but only have " + this.readAvail());
|
|---|
| 81 | }
|
|---|
| 82 | }
|
|---|
| 83 |
|
|---|
| 84 | Accumulator.prototype.peekString = function(size) {
|
|---|
| 85 | this.assertReadableSize(size);
|
|---|
| 86 | return this.buf.toString('utf-8', this.readOffset, this.readOffset + size);
|
|---|
| 87 | }
|
|---|
| 88 |
|
|---|
| 89 | Accumulator.prototype.readString = function(size) {
|
|---|
| 90 | var str = this.peekString(size);
|
|---|
| 91 | this.readOffset += size;
|
|---|
| 92 | return str;
|
|---|
| 93 | }
|
|---|
| 94 |
|
|---|
| 95 | Accumulator.prototype.peekInt = function(size) {
|
|---|
| 96 | this.assertReadableSize(size);
|
|---|
| 97 | switch (size) {
|
|---|
| 98 | case 1:
|
|---|
| 99 | return this.buf.readInt8(this.readOffset, size);
|
|---|
| 100 | case 2:
|
|---|
| 101 | return isBigEndian ?
|
|---|
| 102 | this.buf.readInt16BE(this.readOffset, size) :
|
|---|
| 103 | this.buf.readInt16LE(this.readOffset, size);
|
|---|
| 104 | case 4:
|
|---|
| 105 | return isBigEndian ?
|
|---|
| 106 | this.buf.readInt32BE(this.readOffset, size) :
|
|---|
| 107 | this.buf.readInt32LE(this.readOffset, size);
|
|---|
| 108 | case 8:
|
|---|
| 109 | var big = this.buf.slice(this.readOffset, this.readOffset + 8);
|
|---|
| 110 | if (isBigEndian) {
|
|---|
| 111 | // On a big endian system we can simply pass the buffer directly
|
|---|
| 112 | return new Int64(big);
|
|---|
| 113 | }
|
|---|
| 114 | // Otherwise we need to byteswap
|
|---|
| 115 | return new Int64(byteswap64(big));
|
|---|
| 116 | default:
|
|---|
| 117 | throw new Error("invalid integer size " + size);
|
|---|
| 118 | }
|
|---|
| 119 | }
|
|---|
| 120 |
|
|---|
| 121 | Accumulator.prototype.readInt = function(bytes) {
|
|---|
| 122 | var ival = this.peekInt(bytes);
|
|---|
| 123 | if (ival instanceof Int64 && isFinite(ival.valueOf())) {
|
|---|
| 124 | ival = ival.valueOf();
|
|---|
| 125 | }
|
|---|
| 126 | this.readOffset += bytes;
|
|---|
| 127 | return ival;
|
|---|
| 128 | }
|
|---|
| 129 |
|
|---|
| 130 | Accumulator.prototype.peekDouble = function() {
|
|---|
| 131 | this.assertReadableSize(8);
|
|---|
| 132 | return isBigEndian ?
|
|---|
| 133 | this.buf.readDoubleBE(this.readOffset) :
|
|---|
| 134 | this.buf.readDoubleLE(this.readOffset);
|
|---|
| 135 | }
|
|---|
| 136 |
|
|---|
| 137 | Accumulator.prototype.readDouble = function() {
|
|---|
| 138 | var dval = this.peekDouble();
|
|---|
| 139 | this.readOffset += 8;
|
|---|
| 140 | return dval;
|
|---|
| 141 | }
|
|---|
| 142 |
|
|---|
| 143 | Accumulator.prototype.readAdvance = function(size) {
|
|---|
| 144 | if (size > 0) {
|
|---|
| 145 | this.assertReadableSize(size);
|
|---|
| 146 | } else if (size < 0 && this.readOffset + size < 0) {
|
|---|
| 147 | throw new Error("advance with negative offset " + size +
|
|---|
| 148 | " would seek off the start of the buffer");
|
|---|
| 149 | }
|
|---|
| 150 | this.readOffset += size;
|
|---|
| 151 | }
|
|---|
| 152 |
|
|---|
| 153 | Accumulator.prototype.writeByte = function(value) {
|
|---|
| 154 | this.reserve(1);
|
|---|
| 155 | this.buf.writeInt8(value, this.writeOffset);
|
|---|
| 156 | ++this.writeOffset;
|
|---|
| 157 | }
|
|---|
| 158 |
|
|---|
| 159 | Accumulator.prototype.writeInt = function(value, size) {
|
|---|
| 160 | this.reserve(size);
|
|---|
| 161 | switch (size) {
|
|---|
| 162 | case 1:
|
|---|
| 163 | this.buf.writeInt8(value, this.writeOffset);
|
|---|
| 164 | break;
|
|---|
| 165 | case 2:
|
|---|
| 166 | if (isBigEndian) {
|
|---|
| 167 | this.buf.writeInt16BE(value, this.writeOffset);
|
|---|
| 168 | } else {
|
|---|
| 169 | this.buf.writeInt16LE(value, this.writeOffset);
|
|---|
| 170 | }
|
|---|
| 171 | break;
|
|---|
| 172 | case 4:
|
|---|
| 173 | if (isBigEndian) {
|
|---|
| 174 | this.buf.writeInt32BE(value, this.writeOffset);
|
|---|
| 175 | } else {
|
|---|
| 176 | this.buf.writeInt32LE(value, this.writeOffset);
|
|---|
| 177 | }
|
|---|
| 178 | break;
|
|---|
| 179 | default:
|
|---|
| 180 | throw new Error("unsupported integer size " + size);
|
|---|
| 181 | }
|
|---|
| 182 | this.writeOffset += size;
|
|---|
| 183 | }
|
|---|
| 184 |
|
|---|
| 185 | Accumulator.prototype.writeDouble = function(value) {
|
|---|
| 186 | this.reserve(8);
|
|---|
| 187 | if (isBigEndian) {
|
|---|
| 188 | this.buf.writeDoubleBE(value, this.writeOffset);
|
|---|
| 189 | } else {
|
|---|
| 190 | this.buf.writeDoubleLE(value, this.writeOffset);
|
|---|
| 191 | }
|
|---|
| 192 | this.writeOffset += 8;
|
|---|
| 193 | }
|
|---|
| 194 |
|
|---|
| 195 | var BSER_ARRAY = 0x00;
|
|---|
| 196 | var BSER_OBJECT = 0x01;
|
|---|
| 197 | var BSER_STRING = 0x02;
|
|---|
| 198 | var BSER_INT8 = 0x03;
|
|---|
| 199 | var BSER_INT16 = 0x04;
|
|---|
| 200 | var BSER_INT32 = 0x05;
|
|---|
| 201 | var BSER_INT64 = 0x06;
|
|---|
| 202 | var BSER_REAL = 0x07;
|
|---|
| 203 | var BSER_TRUE = 0x08;
|
|---|
| 204 | var BSER_FALSE = 0x09;
|
|---|
| 205 | var BSER_NULL = 0x0a;
|
|---|
| 206 | var BSER_TEMPLATE = 0x0b;
|
|---|
| 207 | var BSER_SKIP = 0x0c;
|
|---|
| 208 |
|
|---|
| 209 | var ST_NEED_PDU = 0; // Need to read and decode PDU length
|
|---|
| 210 | var ST_FILL_PDU = 1; // Know the length, need to read whole content
|
|---|
| 211 |
|
|---|
| 212 | var MAX_INT8 = 127;
|
|---|
| 213 | var MAX_INT16 = 32767;
|
|---|
| 214 | var MAX_INT32 = 2147483647;
|
|---|
| 215 |
|
|---|
| 216 | function BunserBuf() {
|
|---|
| 217 | EE.call(this);
|
|---|
| 218 | this.buf = new Accumulator();
|
|---|
| 219 | this.state = ST_NEED_PDU;
|
|---|
| 220 | }
|
|---|
| 221 | util.inherits(BunserBuf, EE);
|
|---|
| 222 | exports.BunserBuf = BunserBuf;
|
|---|
| 223 |
|
|---|
| 224 | BunserBuf.prototype.append = function(buf, synchronous) {
|
|---|
| 225 | if (synchronous) {
|
|---|
| 226 | this.buf.append(buf);
|
|---|
| 227 | return this.process(synchronous);
|
|---|
| 228 | }
|
|---|
| 229 |
|
|---|
| 230 | try {
|
|---|
| 231 | this.buf.append(buf);
|
|---|
| 232 | } catch (err) {
|
|---|
| 233 | this.emit('error', err);
|
|---|
| 234 | return;
|
|---|
| 235 | }
|
|---|
| 236 | // Arrange to decode later. This allows the consuming
|
|---|
| 237 | // application to make progress with other work in the
|
|---|
| 238 | // case that we have a lot of subscription updates coming
|
|---|
| 239 | // in from a large tree.
|
|---|
| 240 | this.processLater();
|
|---|
| 241 | }
|
|---|
| 242 |
|
|---|
| 243 | BunserBuf.prototype.processLater = function() {
|
|---|
| 244 | var self = this;
|
|---|
| 245 | process.nextTick(function() {
|
|---|
| 246 | try {
|
|---|
| 247 | self.process(false);
|
|---|
| 248 | } catch (err) {
|
|---|
| 249 | self.emit('error', err);
|
|---|
| 250 | }
|
|---|
| 251 | });
|
|---|
| 252 | }
|
|---|
| 253 |
|
|---|
| 254 | // Do something with the buffer to advance our state.
|
|---|
| 255 | // If we're running synchronously we'll return either
|
|---|
| 256 | // the value we've decoded or undefined if we don't
|
|---|
| 257 | // yet have enought data.
|
|---|
| 258 | // If we're running asynchronously, we'll emit the value
|
|---|
| 259 | // when it becomes ready and schedule another invocation
|
|---|
| 260 | // of process on the next tick if we still have data we
|
|---|
| 261 | // can process.
|
|---|
| 262 | BunserBuf.prototype.process = function(synchronous) {
|
|---|
| 263 | if (this.state == ST_NEED_PDU) {
|
|---|
| 264 | if (this.buf.readAvail() < 2) {
|
|---|
| 265 | return;
|
|---|
| 266 | }
|
|---|
| 267 | // Validate BSER header
|
|---|
| 268 | this.expectCode(0);
|
|---|
| 269 | this.expectCode(1);
|
|---|
| 270 | this.pduLen = this.decodeInt(true /* relaxed */);
|
|---|
| 271 | if (this.pduLen === false) {
|
|---|
| 272 | // Need more data, walk backwards
|
|---|
| 273 | this.buf.readAdvance(-2);
|
|---|
| 274 | return;
|
|---|
| 275 | }
|
|---|
| 276 | // Ensure that we have a big enough buffer to read the rest of the PDU
|
|---|
| 277 | this.buf.reserve(this.pduLen);
|
|---|
| 278 | this.state = ST_FILL_PDU;
|
|---|
| 279 | }
|
|---|
| 280 |
|
|---|
| 281 | if (this.state == ST_FILL_PDU) {
|
|---|
| 282 | if (this.buf.readAvail() < this.pduLen) {
|
|---|
| 283 | // Need more data
|
|---|
| 284 | return;
|
|---|
| 285 | }
|
|---|
| 286 |
|
|---|
| 287 | // We have enough to decode it
|
|---|
| 288 | var val = this.decodeAny();
|
|---|
| 289 | if (synchronous) {
|
|---|
| 290 | return val;
|
|---|
| 291 | }
|
|---|
| 292 | this.emit('value', val);
|
|---|
| 293 | this.state = ST_NEED_PDU;
|
|---|
| 294 | }
|
|---|
| 295 |
|
|---|
| 296 | if (!synchronous && this.buf.readAvail() > 0) {
|
|---|
| 297 | this.processLater();
|
|---|
| 298 | }
|
|---|
| 299 | }
|
|---|
| 300 |
|
|---|
| 301 | BunserBuf.prototype.raise = function(reason) {
|
|---|
| 302 | throw new Error(reason + ", in Buffer of length " +
|
|---|
| 303 | this.buf.buf.length + " (" + this.buf.readAvail() +
|
|---|
| 304 | " readable) at offset " + this.buf.readOffset + " buffer: " +
|
|---|
| 305 | JSON.stringify(this.buf.buf.slice(
|
|---|
| 306 | this.buf.readOffset, this.buf.readOffset + 32).toJSON()));
|
|---|
| 307 | }
|
|---|
| 308 |
|
|---|
| 309 | BunserBuf.prototype.expectCode = function(expected) {
|
|---|
| 310 | var code = this.buf.readInt(1);
|
|---|
| 311 | if (code != expected) {
|
|---|
| 312 | this.raise("expected bser opcode " + expected + " but got " + code);
|
|---|
| 313 | }
|
|---|
| 314 | }
|
|---|
| 315 |
|
|---|
| 316 | BunserBuf.prototype.decodeAny = function() {
|
|---|
| 317 | var code = this.buf.peekInt(1);
|
|---|
| 318 | switch (code) {
|
|---|
| 319 | case BSER_INT8:
|
|---|
| 320 | case BSER_INT16:
|
|---|
| 321 | case BSER_INT32:
|
|---|
| 322 | case BSER_INT64:
|
|---|
| 323 | return this.decodeInt();
|
|---|
| 324 | case BSER_REAL:
|
|---|
| 325 | this.buf.readAdvance(1);
|
|---|
| 326 | return this.buf.readDouble();
|
|---|
| 327 | case BSER_TRUE:
|
|---|
| 328 | this.buf.readAdvance(1);
|
|---|
| 329 | return true;
|
|---|
| 330 | case BSER_FALSE:
|
|---|
| 331 | this.buf.readAdvance(1);
|
|---|
| 332 | return false;
|
|---|
| 333 | case BSER_NULL:
|
|---|
| 334 | this.buf.readAdvance(1);
|
|---|
| 335 | return null;
|
|---|
| 336 | case BSER_STRING:
|
|---|
| 337 | return this.decodeString();
|
|---|
| 338 | case BSER_ARRAY:
|
|---|
| 339 | return this.decodeArray();
|
|---|
| 340 | case BSER_OBJECT:
|
|---|
| 341 | return this.decodeObject();
|
|---|
| 342 | case BSER_TEMPLATE:
|
|---|
| 343 | return this.decodeTemplate();
|
|---|
| 344 | default:
|
|---|
| 345 | this.raise("unhandled bser opcode " + code);
|
|---|
| 346 | }
|
|---|
| 347 | }
|
|---|
| 348 |
|
|---|
| 349 | BunserBuf.prototype.decodeArray = function() {
|
|---|
| 350 | this.expectCode(BSER_ARRAY);
|
|---|
| 351 | var nitems = this.decodeInt();
|
|---|
| 352 | var arr = [];
|
|---|
| 353 | for (var i = 0; i < nitems; ++i) {
|
|---|
| 354 | arr.push(this.decodeAny());
|
|---|
| 355 | }
|
|---|
| 356 | return arr;
|
|---|
| 357 | }
|
|---|
| 358 |
|
|---|
| 359 | BunserBuf.prototype.decodeObject = function() {
|
|---|
| 360 | this.expectCode(BSER_OBJECT);
|
|---|
| 361 | var nitems = this.decodeInt();
|
|---|
| 362 | var res = {};
|
|---|
| 363 | for (var i = 0; i < nitems; ++i) {
|
|---|
| 364 | var key = this.decodeString();
|
|---|
| 365 | var val = this.decodeAny();
|
|---|
| 366 | res[key] = val;
|
|---|
| 367 | }
|
|---|
| 368 | return res;
|
|---|
| 369 | }
|
|---|
| 370 |
|
|---|
| 371 | BunserBuf.prototype.decodeTemplate = function() {
|
|---|
| 372 | this.expectCode(BSER_TEMPLATE);
|
|---|
| 373 | var keys = this.decodeArray();
|
|---|
| 374 | var nitems = this.decodeInt();
|
|---|
| 375 | var arr = [];
|
|---|
| 376 | for (var i = 0; i < nitems; ++i) {
|
|---|
| 377 | var obj = {};
|
|---|
| 378 | for (var keyidx = 0; keyidx < keys.length; ++keyidx) {
|
|---|
| 379 | if (this.buf.peekInt(1) == BSER_SKIP) {
|
|---|
| 380 | this.buf.readAdvance(1);
|
|---|
| 381 | continue;
|
|---|
| 382 | }
|
|---|
| 383 | var val = this.decodeAny();
|
|---|
| 384 | obj[keys[keyidx]] = val;
|
|---|
| 385 | }
|
|---|
| 386 | arr.push(obj);
|
|---|
| 387 | }
|
|---|
| 388 | return arr;
|
|---|
| 389 | }
|
|---|
| 390 |
|
|---|
| 391 | BunserBuf.prototype.decodeString = function() {
|
|---|
| 392 | this.expectCode(BSER_STRING);
|
|---|
| 393 | var len = this.decodeInt();
|
|---|
| 394 | return this.buf.readString(len);
|
|---|
| 395 | }
|
|---|
| 396 |
|
|---|
| 397 | // This is unusual compared to the other decode functions in that
|
|---|
| 398 | // we may not have enough data available to satisfy the read, and
|
|---|
| 399 | // we don't want to throw. This is only true when we're reading
|
|---|
| 400 | // the PDU length from the PDU header; we'll set relaxSizeAsserts
|
|---|
| 401 | // in that case.
|
|---|
| 402 | BunserBuf.prototype.decodeInt = function(relaxSizeAsserts) {
|
|---|
| 403 | if (relaxSizeAsserts && (this.buf.readAvail() < 1)) {
|
|---|
| 404 | return false;
|
|---|
| 405 | } else {
|
|---|
| 406 | this.buf.assertReadableSize(1);
|
|---|
| 407 | }
|
|---|
| 408 | var code = this.buf.peekInt(1);
|
|---|
| 409 | var size = 0;
|
|---|
| 410 | switch (code) {
|
|---|
| 411 | case BSER_INT8:
|
|---|
| 412 | size = 1;
|
|---|
| 413 | break;
|
|---|
| 414 | case BSER_INT16:
|
|---|
| 415 | size = 2;
|
|---|
| 416 | break;
|
|---|
| 417 | case BSER_INT32:
|
|---|
| 418 | size = 4;
|
|---|
| 419 | break;
|
|---|
| 420 | case BSER_INT64:
|
|---|
| 421 | size = 8;
|
|---|
| 422 | break;
|
|---|
| 423 | default:
|
|---|
| 424 | this.raise("invalid bser int encoding " + code);
|
|---|
| 425 | }
|
|---|
| 426 |
|
|---|
| 427 | if (relaxSizeAsserts && (this.buf.readAvail() < 1 + size)) {
|
|---|
| 428 | return false;
|
|---|
| 429 | }
|
|---|
| 430 | this.buf.readAdvance(1);
|
|---|
| 431 | return this.buf.readInt(size);
|
|---|
| 432 | }
|
|---|
| 433 |
|
|---|
| 434 | // synchronously BSER decode a string and return the value
|
|---|
| 435 | function loadFromBuffer(input) {
|
|---|
| 436 | var buf = new BunserBuf();
|
|---|
| 437 | var result = buf.append(input, true);
|
|---|
| 438 | if (buf.buf.readAvail()) {
|
|---|
| 439 | throw Error(
|
|---|
| 440 | 'excess data found after input buffer, use BunserBuf instead');
|
|---|
| 441 | }
|
|---|
| 442 | if (typeof result === 'undefined') {
|
|---|
| 443 | throw Error(
|
|---|
| 444 | 'no bser found in string and no error raised!?');
|
|---|
| 445 | }
|
|---|
| 446 | return result;
|
|---|
| 447 | }
|
|---|
| 448 | exports.loadFromBuffer = loadFromBuffer
|
|---|
| 449 |
|
|---|
| 450 | // Byteswap an arbitrary buffer, flipping from one endian
|
|---|
| 451 | // to the other, returning a new buffer with the resultant data
|
|---|
| 452 | function byteswap64(buf) {
|
|---|
| 453 | var swap = Buffer.alloc(buf.length);
|
|---|
| 454 | for (var i = 0; i < buf.length; i++) {
|
|---|
| 455 | swap[i] = buf[buf.length -1 - i];
|
|---|
| 456 | }
|
|---|
| 457 | return swap;
|
|---|
| 458 | }
|
|---|
| 459 |
|
|---|
| 460 | function dump_int64(buf, val) {
|
|---|
| 461 | // Get the raw bytes. The Int64 buffer is big endian
|
|---|
| 462 | var be = val.toBuffer();
|
|---|
| 463 |
|
|---|
| 464 | if (isBigEndian) {
|
|---|
| 465 | // We're a big endian system, so the buffer is exactly how we
|
|---|
| 466 | // want it to be
|
|---|
| 467 | buf.writeByte(BSER_INT64);
|
|---|
| 468 | buf.append(be);
|
|---|
| 469 | return;
|
|---|
| 470 | }
|
|---|
| 471 | // We need to byte swap to get the correct representation
|
|---|
| 472 | var le = byteswap64(be);
|
|---|
| 473 | buf.writeByte(BSER_INT64);
|
|---|
| 474 | buf.append(le);
|
|---|
| 475 | }
|
|---|
| 476 |
|
|---|
| 477 | function dump_int(buf, val) {
|
|---|
| 478 | var abs = Math.abs(val);
|
|---|
| 479 | if (abs <= MAX_INT8) {
|
|---|
| 480 | buf.writeByte(BSER_INT8);
|
|---|
| 481 | buf.writeInt(val, 1);
|
|---|
| 482 | } else if (abs <= MAX_INT16) {
|
|---|
| 483 | buf.writeByte(BSER_INT16);
|
|---|
| 484 | buf.writeInt(val, 2);
|
|---|
| 485 | } else if (abs <= MAX_INT32) {
|
|---|
| 486 | buf.writeByte(BSER_INT32);
|
|---|
| 487 | buf.writeInt(val, 4);
|
|---|
| 488 | } else {
|
|---|
| 489 | dump_int64(buf, new Int64(val));
|
|---|
| 490 | }
|
|---|
| 491 | }
|
|---|
| 492 |
|
|---|
| 493 | function dump_any(buf, val) {
|
|---|
| 494 | switch (typeof(val)) {
|
|---|
| 495 | case 'number':
|
|---|
| 496 | // check if it is an integer or a float
|
|---|
| 497 | if (isFinite(val) && Math.floor(val) === val) {
|
|---|
| 498 | dump_int(buf, val);
|
|---|
| 499 | } else {
|
|---|
| 500 | buf.writeByte(BSER_REAL);
|
|---|
| 501 | buf.writeDouble(val);
|
|---|
| 502 | }
|
|---|
| 503 | return;
|
|---|
| 504 | case 'string':
|
|---|
| 505 | buf.writeByte(BSER_STRING);
|
|---|
| 506 | dump_int(buf, Buffer.byteLength(val));
|
|---|
| 507 | buf.append(val);
|
|---|
| 508 | return;
|
|---|
| 509 | case 'boolean':
|
|---|
| 510 | buf.writeByte(val ? BSER_TRUE : BSER_FALSE);
|
|---|
| 511 | return;
|
|---|
| 512 | case 'object':
|
|---|
| 513 | if (val === null) {
|
|---|
| 514 | buf.writeByte(BSER_NULL);
|
|---|
| 515 | return;
|
|---|
| 516 | }
|
|---|
| 517 | if (val instanceof Int64) {
|
|---|
| 518 | dump_int64(buf, val);
|
|---|
| 519 | return;
|
|---|
| 520 | }
|
|---|
| 521 | if (Array.isArray(val)) {
|
|---|
| 522 | buf.writeByte(BSER_ARRAY);
|
|---|
| 523 | dump_int(buf, val.length);
|
|---|
| 524 | for (var i = 0; i < val.length; ++i) {
|
|---|
| 525 | dump_any(buf, val[i]);
|
|---|
| 526 | }
|
|---|
| 527 | return;
|
|---|
| 528 | }
|
|---|
| 529 | buf.writeByte(BSER_OBJECT);
|
|---|
| 530 | var keys = Object.keys(val);
|
|---|
| 531 |
|
|---|
| 532 | // First pass to compute number of defined keys
|
|---|
| 533 | var num_keys = keys.length;
|
|---|
| 534 | for (var i = 0; i < keys.length; ++i) {
|
|---|
| 535 | var key = keys[i];
|
|---|
| 536 | var v = val[key];
|
|---|
| 537 | if (typeof(v) == 'undefined') {
|
|---|
| 538 | num_keys--;
|
|---|
| 539 | }
|
|---|
| 540 | }
|
|---|
| 541 | dump_int(buf, num_keys);
|
|---|
| 542 | for (var i = 0; i < keys.length; ++i) {
|
|---|
| 543 | var key = keys[i];
|
|---|
| 544 | var v = val[key];
|
|---|
| 545 | if (typeof(v) == 'undefined') {
|
|---|
| 546 | // Don't include it
|
|---|
| 547 | continue;
|
|---|
| 548 | }
|
|---|
| 549 | dump_any(buf, key);
|
|---|
| 550 | try {
|
|---|
| 551 | dump_any(buf, v);
|
|---|
| 552 | } catch (e) {
|
|---|
| 553 | throw new Error(
|
|---|
| 554 | e.message + ' (while serializing object property with name `' +
|
|---|
| 555 | key + "')");
|
|---|
| 556 | }
|
|---|
| 557 | }
|
|---|
| 558 | return;
|
|---|
| 559 |
|
|---|
| 560 | default:
|
|---|
| 561 | throw new Error('cannot serialize type ' + typeof(val) + ' to BSER');
|
|---|
| 562 | }
|
|---|
| 563 | }
|
|---|
| 564 |
|
|---|
| 565 | // BSER encode value and return a buffer of the contents
|
|---|
| 566 | function dumpToBuffer(val) {
|
|---|
| 567 | var buf = new Accumulator();
|
|---|
| 568 | // Build out the header
|
|---|
| 569 | buf.writeByte(0);
|
|---|
| 570 | buf.writeByte(1);
|
|---|
| 571 | // Reserve room for an int32 to hold our PDU length
|
|---|
| 572 | buf.writeByte(BSER_INT32);
|
|---|
| 573 | buf.writeInt(0, 4); // We'll come back and fill this in at the end
|
|---|
| 574 |
|
|---|
| 575 | dump_any(buf, val);
|
|---|
| 576 |
|
|---|
| 577 | // Compute PDU length
|
|---|
| 578 | var off = buf.writeOffset;
|
|---|
| 579 | var len = off - 7 /* the header length */;
|
|---|
| 580 | buf.writeOffset = 3; // The length value to fill in
|
|---|
| 581 | buf.writeInt(len, 4); // write the length in the space we reserved
|
|---|
| 582 | buf.writeOffset = off;
|
|---|
| 583 |
|
|---|
| 584 | return buf.buf.slice(0, off);
|
|---|
| 585 | }
|
|---|
| 586 | exports.dumpToBuffer = dumpToBuffer
|
|---|