source: frontend/node_modules/bser/index.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 15.2 KB
Line 
1/* Copyright 2015-present Facebook, Inc.
2 * Licensed under the Apache License, Version 2.0 */
3
4var EE = require('events').EventEmitter;
5var util = require('util');
6var os = require('os');
7var assert = require('assert');
8var 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.
13var isBigEndian = os.endianness() == 'BE';
14
15// Find the next power-of-2 >= size
16function 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
21function Accumulator(initsize) {
22 this.buf = Buffer.alloc(nextPow2(initsize || 8192));
23 this.readOffset = 0;
24 this.writeOffset = 0;
25}
26// For testing
27exports.Accumulator = Accumulator
28
29// How much we can write into this buffer without allocating
30Accumulator.prototype.writeAvail = function() {
31 return this.buf.length - this.writeOffset;
32}
33
34// How much we can read
35Accumulator.prototype.readAvail = function() {
36 return this.writeOffset - this.readOffset;
37}
38
39// Ensure that we have enough space for size bytes
40Accumulator.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
64Accumulator.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
77Accumulator.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
84Accumulator.prototype.peekString = function(size) {
85 this.assertReadableSize(size);
86 return this.buf.toString('utf-8', this.readOffset, this.readOffset + size);
87}
88
89Accumulator.prototype.readString = function(size) {
90 var str = this.peekString(size);
91 this.readOffset += size;
92 return str;
93}
94
95Accumulator.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
121Accumulator.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
130Accumulator.prototype.peekDouble = function() {
131 this.assertReadableSize(8);
132 return isBigEndian ?
133 this.buf.readDoubleBE(this.readOffset) :
134 this.buf.readDoubleLE(this.readOffset);
135}
136
137Accumulator.prototype.readDouble = function() {
138 var dval = this.peekDouble();
139 this.readOffset += 8;
140 return dval;
141}
142
143Accumulator.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
153Accumulator.prototype.writeByte = function(value) {
154 this.reserve(1);
155 this.buf.writeInt8(value, this.writeOffset);
156 ++this.writeOffset;
157}
158
159Accumulator.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
185Accumulator.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
195var BSER_ARRAY = 0x00;
196var BSER_OBJECT = 0x01;
197var BSER_STRING = 0x02;
198var BSER_INT8 = 0x03;
199var BSER_INT16 = 0x04;
200var BSER_INT32 = 0x05;
201var BSER_INT64 = 0x06;
202var BSER_REAL = 0x07;
203var BSER_TRUE = 0x08;
204var BSER_FALSE = 0x09;
205var BSER_NULL = 0x0a;
206var BSER_TEMPLATE = 0x0b;
207var BSER_SKIP = 0x0c;
208
209var ST_NEED_PDU = 0; // Need to read and decode PDU length
210var ST_FILL_PDU = 1; // Know the length, need to read whole content
211
212var MAX_INT8 = 127;
213var MAX_INT16 = 32767;
214var MAX_INT32 = 2147483647;
215
216function BunserBuf() {
217 EE.call(this);
218 this.buf = new Accumulator();
219 this.state = ST_NEED_PDU;
220}
221util.inherits(BunserBuf, EE);
222exports.BunserBuf = BunserBuf;
223
224BunserBuf.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
243BunserBuf.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.
262BunserBuf.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
301BunserBuf.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
309BunserBuf.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
316BunserBuf.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
349BunserBuf.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
359BunserBuf.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
371BunserBuf.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
391BunserBuf.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.
402BunserBuf.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
435function 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}
448exports.loadFromBuffer = loadFromBuffer
449
450// Byteswap an arbitrary buffer, flipping from one endian
451// to the other, returning a new buffer with the resultant data
452function 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
460function 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
477function 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
493function 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
566function 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}
586exports.dumpToBuffer = dumpToBuffer
Note: See TracBrowser for help on using the repository browser.