Skip to content

Commit 922792a

Browse files
authored
perf: refactor bit operations into helper functions (#652)
1 parent 4d69b12 commit 922792a

File tree

6 files changed

+211
-276
lines changed

6 files changed

+211
-276
lines changed

src/bson.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { BSONRegExp } from './regexp';
1616
import { BSONSymbol } from './symbol';
1717
import { Timestamp } from './timestamp';
1818
import { ByteUtils } from './utils/byte_utils';
19+
import { NumberUtils } from './utils/number_utils';
1920
export type { UUIDExtended, BinaryExtended, BinaryExtendedLegacy, BinarySequence } from './binary';
2021
export type { CodeExtended } from './code';
2122
export type { DBRefLike } from './db_ref';
@@ -232,11 +233,7 @@ export function deserializeStream(
232233
// Loop over all documents
233234
for (let i = 0; i < numberOfDocuments; i++) {
234235
// Find size of the document
235-
const size =
236-
bufferData[index] |
237-
(bufferData[index + 1] << 8) |
238-
(bufferData[index + 2] << 16) |
239-
(bufferData[index + 3] << 24);
236+
const size = NumberUtils.getInt32LE(bufferData, index);
240237
// Update options with index
241238
internalOptions.index = index;
242239
// Parse the document at this point

src/objectid.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { BSONValue } from './bson_value';
22
import { BSONError } from './error';
33
import { type InspectFn, defaultInspect } from './parser/utils';
44
import { ByteUtils } from './utils/byte_utils';
5+
import { NumberUtils } from './utils/number_utils';
56

67
// Regular expression that checks for hex value
78
const checkForHexRegExp = new RegExp('^[0-9a-fA-F]{24}$');
@@ -179,13 +180,7 @@ export class ObjectId extends BSONValue {
179180
const buffer = ByteUtils.allocate(12);
180181

181182
// 4-byte timestamp
182-
buffer[3] = time;
183-
time >>>= 8;
184-
buffer[2] = time;
185-
time >>>= 8;
186-
buffer[1] = time;
187-
time >>>= 8;
188-
buffer[0] = time;
183+
NumberUtils.setInt32BE(buffer, 0, time);
189184

190185
// set PROCESS_UNIQUE if yet not initialized
191186
if (PROCESS_UNIQUE === null) {
@@ -265,11 +260,7 @@ export class ObjectId extends BSONValue {
265260
/** Returns the generation date (accurate up to the second) that this ID was generated. */
266261
getTimestamp(): Date {
267262
const timestamp = new Date();
268-
const time =
269-
this.buffer[3] +
270-
this.buffer[2] * (1 << 8) +
271-
this.buffer[1] * (1 << 16) +
272-
this.buffer[0] * (1 << 24);
263+
const time = NumberUtils.getUint32BE(this.buffer, 0);
273264
timestamp.setTime(Math.floor(time) * 1000);
274265
return timestamp;
275266
}
@@ -288,13 +279,7 @@ export class ObjectId extends BSONValue {
288279
const buffer = ByteUtils.allocate(12);
289280
for (let i = 11; i >= 4; i--) buffer[i] = 0;
290281
// Encode time into first 4 bytes
291-
buffer[3] = time;
292-
time >>>= 8;
293-
buffer[2] = time;
294-
time >>>= 8;
295-
buffer[1] = time;
296-
time >>>= 8;
297-
buffer[0] = time;
282+
NumberUtils.setInt32BE(buffer, 0, time);
298283
// Return the new objectId
299284
return new ObjectId(buffer);
300285
}

src/parser/deserializer.ts

Lines changed: 45 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { BSONRegExp } from '../regexp';
1515
import { BSONSymbol } from '../symbol';
1616
import { Timestamp } from '../timestamp';
1717
import { ByteUtils } from '../utils/byte_utils';
18+
import { NumberUtils } from '../utils/number_utils';
1819
import { validateUtf8 } from '../validate_utf8';
1920

2021
/** @public */
@@ -91,11 +92,7 @@ export function internalDeserialize(
9192
options = options == null ? {} : options;
9293
const index = options && options.index ? options.index : 0;
9394
// Read the document size
94-
const size =
95-
buffer[index] |
96-
(buffer[index + 1] << 8) |
97-
(buffer[index + 2] << 16) |
98-
(buffer[index + 3] << 24);
95+
const size = NumberUtils.getInt32LE(buffer, index);
9996

10097
if (size < 5) {
10198
throw new BSONError(`bson size must be >= 5, is ${size}`);
@@ -128,9 +125,6 @@ export function internalDeserialize(
128125

129126
const allowedDBRefKeys = /^\$ref$|^\$id$|^\$db$/;
130127

131-
const FLOAT_READ = new Float64Array(1);
132-
const FLOAT_WRITE_BYTES = new Uint8Array(FLOAT_READ.buffer, 0, 8);
133-
134128
function deserializeObject(
135129
buffer: Uint8Array,
136130
index: number,
@@ -207,8 +201,8 @@ function deserializeObject(
207201
if (buffer.length < 5) throw new BSONError('corrupt bson message < 5 bytes long');
208202

209203
// Read the document size
210-
const size =
211-
buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24);
204+
const size = NumberUtils.getInt32LE(buffer, index);
205+
index += 4;
212206

213207
// Ensure buffer is valid size
214208
if (size < 5 || size > buffer.length) throw new BSONError('corrupt bson message');
@@ -258,11 +252,8 @@ function deserializeObject(
258252
index = i + 1;
259253

260254
if (elementType === constants.BSON_DATA_STRING) {
261-
const stringSize =
262-
buffer[index++] |
263-
(buffer[index++] << 8) |
264-
(buffer[index++] << 16) |
265-
(buffer[index++] << 24);
255+
const stringSize = NumberUtils.getInt32LE(buffer, index);
256+
index += 4;
266257
if (
267258
stringSize <= 0 ||
268259
stringSize > buffer.length - index ||
@@ -278,37 +269,19 @@ function deserializeObject(
278269
value = new ObjectId(oid);
279270
index = index + 12;
280271
} else if (elementType === constants.BSON_DATA_INT && promoteValues === false) {
281-
value = new Int32(
282-
buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24)
283-
);
272+
value = new Int32(NumberUtils.getInt32LE(buffer, index));
273+
index += 4;
284274
} else if (elementType === constants.BSON_DATA_INT) {
285-
value =
286-
buffer[index++] |
287-
(buffer[index++] << 8) |
288-
(buffer[index++] << 16) |
289-
(buffer[index++] << 24);
275+
value = NumberUtils.getInt32LE(buffer, index);
276+
index += 4;
290277
} else if (elementType === constants.BSON_DATA_NUMBER) {
291-
FLOAT_WRITE_BYTES[0] = buffer[index++];
292-
FLOAT_WRITE_BYTES[1] = buffer[index++];
293-
FLOAT_WRITE_BYTES[2] = buffer[index++];
294-
FLOAT_WRITE_BYTES[3] = buffer[index++];
295-
FLOAT_WRITE_BYTES[4] = buffer[index++];
296-
FLOAT_WRITE_BYTES[5] = buffer[index++];
297-
FLOAT_WRITE_BYTES[6] = buffer[index++];
298-
FLOAT_WRITE_BYTES[7] = buffer[index++];
299-
value = FLOAT_READ[0];
278+
value = NumberUtils.getFloat64LE(buffer, index);
279+
index += 8;
300280
if (promoteValues === false) value = new Double(value);
301281
} else if (elementType === constants.BSON_DATA_DATE) {
302-
const lowBits =
303-
buffer[index++] |
304-
(buffer[index++] << 8) |
305-
(buffer[index++] << 16) |
306-
(buffer[index++] << 24);
307-
const highBits =
308-
buffer[index++] |
309-
(buffer[index++] << 8) |
310-
(buffer[index++] << 16) |
311-
(buffer[index++] << 24);
282+
const lowBits = NumberUtils.getInt32LE(buffer, index);
283+
const highBits = NumberUtils.getInt32LE(buffer, index + 4);
284+
index += 8;
312285

313286
value = new Date(new Long(lowBits, highBits).toNumber());
314287
} else if (elementType === constants.BSON_DATA_BOOLEAN) {
@@ -317,11 +290,8 @@ function deserializeObject(
317290
value = buffer[index++] === 1;
318291
} else if (elementType === constants.BSON_DATA_OBJECT) {
319292
const _index = index;
320-
const objectSize =
321-
buffer[index] |
322-
(buffer[index + 1] << 8) |
323-
(buffer[index + 2] << 16) |
324-
(buffer[index + 3] << 24);
293+
const objectSize = NumberUtils.getInt32LE(buffer, index);
294+
325295
if (objectSize <= 0 || objectSize > buffer.length - index)
326296
throw new BSONError('bad embedded document length in bson');
327297

@@ -339,11 +309,7 @@ function deserializeObject(
339309
index = index + objectSize;
340310
} else if (elementType === constants.BSON_DATA_ARRAY) {
341311
const _index = index;
342-
const objectSize =
343-
buffer[index] |
344-
(buffer[index + 1] << 8) |
345-
(buffer[index + 2] << 16) |
346-
(buffer[index + 3] << 24);
312+
const objectSize = NumberUtils.getInt32LE(buffer, index);
347313
let arrayOptions: DeserializeOptions = options;
348314

349315
// Stop index
@@ -368,32 +334,14 @@ function deserializeObject(
368334
value = null;
369335
} else if (elementType === constants.BSON_DATA_LONG) {
370336
if (useBigInt64) {
371-
const lo =
372-
buffer[index] +
373-
buffer[index + 1] * 2 ** 8 +
374-
buffer[index + 2] * 2 ** 16 +
375-
buffer[index + 3] * 2 ** 24;
376-
const hi =
377-
buffer[index + 4] +
378-
buffer[index + 5] * 2 ** 8 +
379-
buffer[index + 6] * 2 ** 16 +
380-
(buffer[index + 7] << 24); // Overflow
381-
382-
/* eslint-disable-next-line no-restricted-globals -- This is allowed here as useBigInt64=true */
383-
value = (BigInt(hi) << BigInt(32)) + BigInt(lo);
337+
value = NumberUtils.getBigInt64LE(buffer, index);
384338
index += 8;
385339
} else {
386340
// Unpack the low and high bits
387-
const lowBits =
388-
buffer[index++] |
389-
(buffer[index++] << 8) |
390-
(buffer[index++] << 16) |
391-
(buffer[index++] << 24);
392-
const highBits =
393-
buffer[index++] |
394-
(buffer[index++] << 8) |
395-
(buffer[index++] << 16) |
396-
(buffer[index++] << 24);
341+
const lowBits = NumberUtils.getInt32LE(buffer, index);
342+
const highBits = NumberUtils.getInt32LE(buffer, index + 4);
343+
index += 8;
344+
397345
const long = new Long(lowBits, highBits);
398346
// Promote the long if possible
399347
if (promoteLongs && promoteValues === true) {
@@ -415,11 +363,8 @@ function deserializeObject(
415363
// Assign the new Decimal128 value
416364
value = new Decimal128(bytes);
417365
} else if (elementType === constants.BSON_DATA_BINARY) {
418-
let binarySize =
419-
buffer[index++] |
420-
(buffer[index++] << 8) |
421-
(buffer[index++] << 16) |
422-
(buffer[index++] << 24);
366+
let binarySize = NumberUtils.getInt32LE(buffer, index);
367+
index += 4;
423368
const totalBinarySize = binarySize;
424369
const subType = buffer[index++];
425370

@@ -434,11 +379,8 @@ function deserializeObject(
434379
if (buffer['slice'] != null) {
435380
// If we have subtype 2 skip the 4 bytes for the size
436381
if (subType === Binary.SUBTYPE_BYTE_ARRAY) {
437-
binarySize =
438-
buffer[index++] |
439-
(buffer[index++] << 8) |
440-
(buffer[index++] << 16) |
441-
(buffer[index++] << 24);
382+
binarySize = NumberUtils.getInt32LE(buffer, index);
383+
index += 4;
442384
if (binarySize < 0)
443385
throw new BSONError('Negative binary type element size found for subtype 0x02');
444386
if (binarySize > totalBinarySize - 4)
@@ -459,11 +401,8 @@ function deserializeObject(
459401
const _buffer = ByteUtils.allocate(binarySize);
460402
// If we have subtype 2 skip the 4 bytes for the size
461403
if (subType === Binary.SUBTYPE_BYTE_ARRAY) {
462-
binarySize =
463-
buffer[index++] |
464-
(buffer[index++] << 8) |
465-
(buffer[index++] << 16) |
466-
(buffer[index++] << 24);
404+
binarySize = NumberUtils.getInt32LE(buffer, index);
405+
index += 4;
467406
if (binarySize < 0)
468407
throw new BSONError('Negative binary type element size found for subtype 0x02');
469408
if (binarySize > totalBinarySize - 4)
@@ -562,11 +501,8 @@ function deserializeObject(
562501
// Set the object
563502
value = new BSONRegExp(source, regExpOptions);
564503
} else if (elementType === constants.BSON_DATA_SYMBOL) {
565-
const stringSize =
566-
buffer[index++] |
567-
(buffer[index++] << 8) |
568-
(buffer[index++] << 16) |
569-
(buffer[index++] << 24);
504+
const stringSize = NumberUtils.getInt32LE(buffer, index);
505+
index += 4;
570506
if (
571507
stringSize <= 0 ||
572508
stringSize > buffer.length - index ||
@@ -578,31 +514,18 @@ function deserializeObject(
578514
value = promoteValues ? symbol : new BSONSymbol(symbol);
579515
index = index + stringSize;
580516
} else if (elementType === constants.BSON_DATA_TIMESTAMP) {
581-
// We intentionally **do not** use bit shifting here
582-
// Bit shifting in javascript coerces numbers to **signed** int32s
583-
// We need to keep i, and t unsigned
584-
const i =
585-
buffer[index++] +
586-
buffer[index++] * (1 << 8) +
587-
buffer[index++] * (1 << 16) +
588-
buffer[index++] * (1 << 24);
589-
const t =
590-
buffer[index++] +
591-
buffer[index++] * (1 << 8) +
592-
buffer[index++] * (1 << 16) +
593-
buffer[index++] * (1 << 24);
594-
595-
value = new Timestamp({ i, t });
517+
value = new Timestamp({
518+
i: NumberUtils.getUint32LE(buffer, index),
519+
t: NumberUtils.getUint32LE(buffer, index + 4)
520+
});
521+
index += 8;
596522
} else if (elementType === constants.BSON_DATA_MIN_KEY) {
597523
value = new MinKey();
598524
} else if (elementType === constants.BSON_DATA_MAX_KEY) {
599525
value = new MaxKey();
600526
} else if (elementType === constants.BSON_DATA_CODE) {
601-
const stringSize =
602-
buffer[index++] |
603-
(buffer[index++] << 8) |
604-
(buffer[index++] << 16) |
605-
(buffer[index++] << 24);
527+
const stringSize = NumberUtils.getInt32LE(buffer, index);
528+
index += 4;
606529
if (
607530
stringSize <= 0 ||
608531
stringSize > buffer.length - index ||
@@ -622,23 +545,17 @@ function deserializeObject(
622545
// Update parse index position
623546
index = index + stringSize;
624547
} else if (elementType === constants.BSON_DATA_CODE_W_SCOPE) {
625-
const totalSize =
626-
buffer[index++] |
627-
(buffer[index++] << 8) |
628-
(buffer[index++] << 16) |
629-
(buffer[index++] << 24);
548+
const totalSize = NumberUtils.getInt32LE(buffer, index);
549+
index += 4;
630550

631551
// Element cannot be shorter than totalSize + stringSize + documentSize + terminator
632552
if (totalSize < 4 + 4 + 4 + 1) {
633553
throw new BSONError('code_w_scope total size shorter minimum expected length');
634554
}
635555

636556
// Get the code string size
637-
const stringSize =
638-
buffer[index++] |
639-
(buffer[index++] << 8) |
640-
(buffer[index++] << 16) |
641-
(buffer[index++] << 24);
557+
const stringSize = NumberUtils.getInt32LE(buffer, index);
558+
index += 4;
642559
// Check if we have a valid string
643560
if (
644561
stringSize <= 0 ||
@@ -660,11 +577,7 @@ function deserializeObject(
660577
// Parse the element
661578
const _index = index;
662579
// Decode the size of the object document
663-
const objectSize =
664-
buffer[index] |
665-
(buffer[index + 1] << 8) |
666-
(buffer[index + 2] << 16) |
667-
(buffer[index + 3] << 24);
580+
const objectSize = NumberUtils.getInt32LE(buffer, index);
668581
// Decode the scope object
669582
const scopeObject = deserializeObject(buffer, _index, options, false);
670583
// Adjust the index
@@ -683,11 +596,8 @@ function deserializeObject(
683596
value = new Code(functionString, scopeObject);
684597
} else if (elementType === constants.BSON_DATA_DBPOINTER) {
685598
// Get the code string size
686-
const stringSize =
687-
buffer[index++] |
688-
(buffer[index++] << 8) |
689-
(buffer[index++] << 16) |
690-
(buffer[index++] << 24);
599+
const stringSize = NumberUtils.getInt32LE(buffer, index);
600+
index += 4;
691601
// Check if we have a valid string
692602
if (
693603
stringSize <= 0 ||

0 commit comments

Comments
 (0)