bson 6.3.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,8 @@ import { ObjectId } from '../objectid';
14
14
  import { BSONRegExp } from '../regexp';
15
15
  import { BSONSymbol } from '../symbol';
16
16
  import { Timestamp } from '../timestamp';
17
- import { BSONDataView, ByteUtils } from '../utils/byte_utils';
17
+ import { ByteUtils } from '../utils/byte_utils';
18
+ import { NumberUtils } from '../utils/number_utils';
18
19
  import { validateUtf8 } from '../validate_utf8';
19
20
 
20
21
  /** @public */
@@ -91,11 +92,7 @@ export function internalDeserialize(
91
92
  options = options == null ? {} : options;
92
93
  const index = options && options.index ? options.index : 0;
93
94
  // 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);
99
96
 
100
97
  if (size < 5) {
101
98
  throw new BSONError(`bson size must be >= 5, is ${size}`);
@@ -164,7 +161,7 @@ function deserializeObject(
164
161
  // Reflects utf-8 validation setting regardless of global or specific key validation
165
162
  let validationSetting: boolean;
166
163
  // Set of keys either to enable or disable validation on
167
- const utf8KeysSet = new Set();
164
+ let utf8KeysSet;
168
165
 
169
166
  // Check for boolean uniformity and empty validation option
170
167
  const utf8ValidatedKeys = validation.utf8;
@@ -190,6 +187,8 @@ function deserializeObject(
190
187
 
191
188
  // Add keys to set that will either be validated or not based on validationSetting
192
189
  if (!globalUTFValidation) {
190
+ utf8KeysSet = new Set();
191
+
193
192
  for (const key of Object.keys(utf8ValidatedKeys)) {
194
193
  utf8KeysSet.add(key);
195
194
  }
@@ -202,8 +201,8 @@ function deserializeObject(
202
201
  if (buffer.length < 5) throw new BSONError('corrupt bson message < 5 bytes long');
203
202
 
204
203
  // Read the document size
205
- const size =
206
- buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24);
204
+ const size = NumberUtils.getInt32LE(buffer, index);
205
+ index += 4;
207
206
 
208
207
  // Ensure buffer is valid size
209
208
  if (size < 5 || size > buffer.length) throw new BSONError('corrupt bson message');
@@ -217,7 +216,6 @@ function deserializeObject(
217
216
  let isPossibleDBRef = isArray ? false : null;
218
217
 
219
218
  // While we have more left data left keep parsing
220
- const dataview = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
221
219
  while (!done) {
222
220
  // Read the type
223
221
  const elementType = buffer[index++];
@@ -240,7 +238,7 @@ function deserializeObject(
240
238
 
241
239
  // shouldValidateKey is true if the key should be validated, false otherwise
242
240
  let shouldValidateKey = true;
243
- if (globalUTFValidation || utf8KeysSet.has(name)) {
241
+ if (globalUTFValidation || utf8KeysSet?.has(name)) {
244
242
  shouldValidateKey = validationSetting;
245
243
  } else {
246
244
  shouldValidateKey = !validationSetting;
@@ -254,11 +252,8 @@ function deserializeObject(
254
252
  index = i + 1;
255
253
 
256
254
  if (elementType === constants.BSON_DATA_STRING) {
257
- const stringSize =
258
- buffer[index++] |
259
- (buffer[index++] << 8) |
260
- (buffer[index++] << 16) |
261
- (buffer[index++] << 24);
255
+ const stringSize = NumberUtils.getInt32LE(buffer, index);
256
+ index += 4;
262
257
  if (
263
258
  stringSize <= 0 ||
264
259
  stringSize > buffer.length - index ||
@@ -269,37 +264,25 @@ function deserializeObject(
269
264
  value = ByteUtils.toUTF8(buffer, index, index + stringSize - 1, shouldValidateKey);
270
265
  index = index + stringSize;
271
266
  } else if (elementType === constants.BSON_DATA_OID) {
272
- const oid = ByteUtils.allocate(12);
273
- oid.set(buffer.subarray(index, index + 12));
267
+ const oid = ByteUtils.allocateUnsafe(12);
268
+ for (let i = 0; i < 12; i++) oid[i] = buffer[index + i];
274
269
  value = new ObjectId(oid);
275
270
  index = index + 12;
276
271
  } else if (elementType === constants.BSON_DATA_INT && promoteValues === false) {
277
- value = new Int32(
278
- buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24)
279
- );
272
+ value = new Int32(NumberUtils.getInt32LE(buffer, index));
273
+ index += 4;
280
274
  } else if (elementType === constants.BSON_DATA_INT) {
281
- value =
282
- buffer[index++] |
283
- (buffer[index++] << 8) |
284
- (buffer[index++] << 16) |
285
- (buffer[index++] << 24);
286
- } else if (elementType === constants.BSON_DATA_NUMBER && promoteValues === false) {
287
- value = new Double(dataview.getFloat64(index, true));
288
- index = index + 8;
275
+ value = NumberUtils.getInt32LE(buffer, index);
276
+ index += 4;
289
277
  } else if (elementType === constants.BSON_DATA_NUMBER) {
290
- value = dataview.getFloat64(index, true);
291
- index = index + 8;
278
+ value = NumberUtils.getFloat64LE(buffer, index);
279
+ index += 8;
280
+ if (promoteValues === false) value = new Double(value);
292
281
  } else if (elementType === constants.BSON_DATA_DATE) {
293
- const lowBits =
294
- buffer[index++] |
295
- (buffer[index++] << 8) |
296
- (buffer[index++] << 16) |
297
- (buffer[index++] << 24);
298
- const highBits =
299
- buffer[index++] |
300
- (buffer[index++] << 8) |
301
- (buffer[index++] << 16) |
302
- (buffer[index++] << 24);
282
+ const lowBits = NumberUtils.getInt32LE(buffer, index);
283
+ const highBits = NumberUtils.getInt32LE(buffer, index + 4);
284
+ index += 8;
285
+
303
286
  value = new Date(new Long(lowBits, highBits).toNumber());
304
287
  } else if (elementType === constants.BSON_DATA_BOOLEAN) {
305
288
  if (buffer[index] !== 0 && buffer[index] !== 1)
@@ -307,11 +290,8 @@ function deserializeObject(
307
290
  value = buffer[index++] === 1;
308
291
  } else if (elementType === constants.BSON_DATA_OBJECT) {
309
292
  const _index = index;
310
- const objectSize =
311
- buffer[index] |
312
- (buffer[index + 1] << 8) |
313
- (buffer[index + 2] << 16) |
314
- (buffer[index + 3] << 24);
293
+ const objectSize = NumberUtils.getInt32LE(buffer, index);
294
+
315
295
  if (objectSize <= 0 || objectSize > buffer.length - index)
316
296
  throw new BSONError('bad embedded document length in bson');
317
297
 
@@ -329,11 +309,7 @@ function deserializeObject(
329
309
  index = index + objectSize;
330
310
  } else if (elementType === constants.BSON_DATA_ARRAY) {
331
311
  const _index = index;
332
- const objectSize =
333
- buffer[index] |
334
- (buffer[index + 1] << 8) |
335
- (buffer[index + 2] << 16) |
336
- (buffer[index + 3] << 24);
312
+ const objectSize = NumberUtils.getInt32LE(buffer, index);
337
313
  let arrayOptions: DeserializeOptions = options;
338
314
 
339
315
  // Stop index
@@ -357,46 +333,38 @@ function deserializeObject(
357
333
  } else if (elementType === constants.BSON_DATA_NULL) {
358
334
  value = null;
359
335
  } else if (elementType === constants.BSON_DATA_LONG) {
360
- // Unpack the low and high bits
361
- const dataview = BSONDataView.fromUint8Array(buffer.subarray(index, index + 8));
362
-
363
- const lowBits =
364
- buffer[index++] |
365
- (buffer[index++] << 8) |
366
- (buffer[index++] << 16) |
367
- (buffer[index++] << 24);
368
- const highBits =
369
- buffer[index++] |
370
- (buffer[index++] << 8) |
371
- (buffer[index++] << 16) |
372
- (buffer[index++] << 24);
373
- const long = new Long(lowBits, highBits);
374
336
  if (useBigInt64) {
375
- value = dataview.getBigInt64(0, true);
376
- } else if (promoteLongs && promoteValues === true) {
377
- // Promote the long if possible
378
- value =
379
- long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG)
380
- ? long.toNumber()
381
- : long;
337
+ value = NumberUtils.getBigInt64LE(buffer, index);
338
+ index += 8;
382
339
  } else {
383
- value = long;
340
+ // Unpack the low and high bits
341
+ const lowBits = NumberUtils.getInt32LE(buffer, index);
342
+ const highBits = NumberUtils.getInt32LE(buffer, index + 4);
343
+ index += 8;
344
+
345
+ const long = new Long(lowBits, highBits);
346
+ // Promote the long if possible
347
+ if (promoteLongs && promoteValues === true) {
348
+ value =
349
+ long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG)
350
+ ? long.toNumber()
351
+ : long;
352
+ } else {
353
+ value = long;
354
+ }
384
355
  }
385
356
  } else if (elementType === constants.BSON_DATA_DECIMAL128) {
386
357
  // Buffer to contain the decimal bytes
387
- const bytes = ByteUtils.allocate(16);
358
+ const bytes = ByteUtils.allocateUnsafe(16);
388
359
  // Copy the next 16 bytes into the bytes buffer
389
- bytes.set(buffer.subarray(index, index + 16), 0);
360
+ for (let i = 0; i < 16; i++) bytes[i] = buffer[index + i];
390
361
  // Update index
391
362
  index = index + 16;
392
363
  // Assign the new Decimal128 value
393
364
  value = new Decimal128(bytes);
394
365
  } else if (elementType === constants.BSON_DATA_BINARY) {
395
- let binarySize =
396
- buffer[index++] |
397
- (buffer[index++] << 8) |
398
- (buffer[index++] << 16) |
399
- (buffer[index++] << 24);
366
+ let binarySize = NumberUtils.getInt32LE(buffer, index);
367
+ index += 4;
400
368
  const totalBinarySize = binarySize;
401
369
  const subType = buffer[index++];
402
370
 
@@ -411,11 +379,8 @@ function deserializeObject(
411
379
  if (buffer['slice'] != null) {
412
380
  // If we have subtype 2 skip the 4 bytes for the size
413
381
  if (subType === Binary.SUBTYPE_BYTE_ARRAY) {
414
- binarySize =
415
- buffer[index++] |
416
- (buffer[index++] << 8) |
417
- (buffer[index++] << 16) |
418
- (buffer[index++] << 24);
382
+ binarySize = NumberUtils.getInt32LE(buffer, index);
383
+ index += 4;
419
384
  if (binarySize < 0)
420
385
  throw new BSONError('Negative binary type element size found for subtype 0x02');
421
386
  if (binarySize > totalBinarySize - 4)
@@ -433,14 +398,10 @@ function deserializeObject(
433
398
  }
434
399
  }
435
400
  } else {
436
- const _buffer = ByteUtils.allocate(binarySize);
437
401
  // If we have subtype 2 skip the 4 bytes for the size
438
402
  if (subType === Binary.SUBTYPE_BYTE_ARRAY) {
439
- binarySize =
440
- buffer[index++] |
441
- (buffer[index++] << 8) |
442
- (buffer[index++] << 16) |
443
- (buffer[index++] << 24);
403
+ binarySize = NumberUtils.getInt32LE(buffer, index);
404
+ index += 4;
444
405
  if (binarySize < 0)
445
406
  throw new BSONError('Negative binary type element size found for subtype 0x02');
446
407
  if (binarySize > totalBinarySize - 4)
@@ -449,13 +410,12 @@ function deserializeObject(
449
410
  throw new BSONError('Binary type with subtype 0x02 contains too short binary size');
450
411
  }
451
412
 
452
- // Copy the data
453
- for (i = 0; i < binarySize; i++) {
454
- _buffer[i] = buffer[index + i];
455
- }
456
-
457
413
  if (promoteBuffers && promoteValues) {
458
- value = _buffer;
414
+ value = ByteUtils.allocateUnsafe(binarySize);
415
+ // Copy the data
416
+ for (i = 0; i < binarySize; i++) {
417
+ value[i] = buffer[index + i];
418
+ }
459
419
  } else {
460
420
  value = new Binary(buffer.slice(index, index + binarySize), subType);
461
421
  if (subType === constants.BSON_BINARY_SUBTYPE_UUID_NEW && UUID.isValid(value)) {
@@ -539,11 +499,8 @@ function deserializeObject(
539
499
  // Set the object
540
500
  value = new BSONRegExp(source, regExpOptions);
541
501
  } else if (elementType === constants.BSON_DATA_SYMBOL) {
542
- const stringSize =
543
- buffer[index++] |
544
- (buffer[index++] << 8) |
545
- (buffer[index++] << 16) |
546
- (buffer[index++] << 24);
502
+ const stringSize = NumberUtils.getInt32LE(buffer, index);
503
+ index += 4;
547
504
  if (
548
505
  stringSize <= 0 ||
549
506
  stringSize > buffer.length - index ||
@@ -555,31 +512,18 @@ function deserializeObject(
555
512
  value = promoteValues ? symbol : new BSONSymbol(symbol);
556
513
  index = index + stringSize;
557
514
  } else if (elementType === constants.BSON_DATA_TIMESTAMP) {
558
- // We intentionally **do not** use bit shifting here
559
- // Bit shifting in javascript coerces numbers to **signed** int32s
560
- // We need to keep i, and t unsigned
561
- const i =
562
- buffer[index++] +
563
- buffer[index++] * (1 << 8) +
564
- buffer[index++] * (1 << 16) +
565
- buffer[index++] * (1 << 24);
566
- const t =
567
- buffer[index++] +
568
- buffer[index++] * (1 << 8) +
569
- buffer[index++] * (1 << 16) +
570
- buffer[index++] * (1 << 24);
571
-
572
- value = new Timestamp({ i, t });
515
+ value = new Timestamp({
516
+ i: NumberUtils.getUint32LE(buffer, index),
517
+ t: NumberUtils.getUint32LE(buffer, index + 4)
518
+ });
519
+ index += 8;
573
520
  } else if (elementType === constants.BSON_DATA_MIN_KEY) {
574
521
  value = new MinKey();
575
522
  } else if (elementType === constants.BSON_DATA_MAX_KEY) {
576
523
  value = new MaxKey();
577
524
  } else if (elementType === constants.BSON_DATA_CODE) {
578
- const stringSize =
579
- buffer[index++] |
580
- (buffer[index++] << 8) |
581
- (buffer[index++] << 16) |
582
- (buffer[index++] << 24);
525
+ const stringSize = NumberUtils.getInt32LE(buffer, index);
526
+ index += 4;
583
527
  if (
584
528
  stringSize <= 0 ||
585
529
  stringSize > buffer.length - index ||
@@ -599,11 +543,8 @@ function deserializeObject(
599
543
  // Update parse index position
600
544
  index = index + stringSize;
601
545
  } else if (elementType === constants.BSON_DATA_CODE_W_SCOPE) {
602
- const totalSize =
603
- buffer[index++] |
604
- (buffer[index++] << 8) |
605
- (buffer[index++] << 16) |
606
- (buffer[index++] << 24);
546
+ const totalSize = NumberUtils.getInt32LE(buffer, index);
547
+ index += 4;
607
548
 
608
549
  // Element cannot be shorter than totalSize + stringSize + documentSize + terminator
609
550
  if (totalSize < 4 + 4 + 4 + 1) {
@@ -611,11 +552,8 @@ function deserializeObject(
611
552
  }
612
553
 
613
554
  // Get the code string size
614
- const stringSize =
615
- buffer[index++] |
616
- (buffer[index++] << 8) |
617
- (buffer[index++] << 16) |
618
- (buffer[index++] << 24);
555
+ const stringSize = NumberUtils.getInt32LE(buffer, index);
556
+ index += 4;
619
557
  // Check if we have a valid string
620
558
  if (
621
559
  stringSize <= 0 ||
@@ -637,11 +575,7 @@ function deserializeObject(
637
575
  // Parse the element
638
576
  const _index = index;
639
577
  // Decode the size of the object document
640
- const objectSize =
641
- buffer[index] |
642
- (buffer[index + 1] << 8) |
643
- (buffer[index + 2] << 16) |
644
- (buffer[index + 3] << 24);
578
+ const objectSize = NumberUtils.getInt32LE(buffer, index);
645
579
  // Decode the scope object
646
580
  const scopeObject = deserializeObject(buffer, _index, options, false);
647
581
  // Adjust the index
@@ -660,11 +594,8 @@ function deserializeObject(
660
594
  value = new Code(functionString, scopeObject);
661
595
  } else if (elementType === constants.BSON_DATA_DBPOINTER) {
662
596
  // Get the code string size
663
- const stringSize =
664
- buffer[index++] |
665
- (buffer[index++] << 8) |
666
- (buffer[index++] << 16) |
667
- (buffer[index++] << 24);
597
+ const stringSize = NumberUtils.getInt32LE(buffer, index);
598
+ index += 4;
668
599
  // Check if we have a valid string
669
600
  if (
670
601
  stringSize <= 0 ||
@@ -683,8 +614,8 @@ function deserializeObject(
683
614
  index = index + stringSize;
684
615
 
685
616
  // Read the oid
686
- const oidBuffer = ByteUtils.allocate(12);
687
- oidBuffer.set(buffer.subarray(index, index + 12), 0);
617
+ const oidBuffer = ByteUtils.allocateUnsafe(12);
618
+ for (let i = 0; i < 12; i++) oidBuffer[i] = buffer[index + i];
688
619
  const oid = new ObjectId(oidBuffer);
689
620
 
690
621
  // Update the index
@@ -0,0 +1,28 @@
1
+ import { type BSONError, BSONOffsetError } from '../../error';
2
+ import { type BSONElement, parseToElements } from './parse_to_elements';
3
+ /**
4
+ * @experimental
5
+ * @public
6
+ *
7
+ * A new set of BSON APIs that are currently experimental and not intended for production use.
8
+ */
9
+ export type OnDemand = {
10
+ BSONOffsetError: {
11
+ new (message: string, offset: number): BSONOffsetError;
12
+ isBSONError(value: unknown): value is BSONError;
13
+ };
14
+ parseToElements: (this: void, bytes: Uint8Array, startOffset?: number) => Iterable<BSONElement>;
15
+ };
16
+
17
+ /**
18
+ * @experimental
19
+ * @public
20
+ */
21
+ const onDemand: OnDemand = Object.create(null);
22
+
23
+ onDemand.parseToElements = parseToElements;
24
+ onDemand.BSONOffsetError = BSONOffsetError;
25
+
26
+ Object.freeze(onDemand);
27
+
28
+ export { onDemand };
@@ -0,0 +1,174 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */
2
+ import { BSONOffsetError } from '../../error';
3
+
4
+ /**
5
+ * @internal
6
+ *
7
+ * @remarks
8
+ * - This enum is const so the code we produce will inline the numbers
9
+ * - `minKey` is set to 255 so unsigned comparisons succeed
10
+ * - Modify with caution, double check the bundle contains literals
11
+ */
12
+ const enum t {
13
+ double = 1,
14
+ string = 2,
15
+ object = 3,
16
+ array = 4,
17
+ binData = 5,
18
+ undefined = 6,
19
+ objectId = 7,
20
+ bool = 8,
21
+ date = 9,
22
+ null = 10,
23
+ regex = 11,
24
+ dbPointer = 12,
25
+ javascript = 13,
26
+ symbol = 14,
27
+ javascriptWithScope = 15,
28
+ int = 16,
29
+ timestamp = 17,
30
+ long = 18,
31
+ decimal = 19,
32
+ minKey = 255,
33
+ maxKey = 127
34
+ }
35
+
36
+ /**
37
+ * @public
38
+ * @experimental
39
+ */
40
+ export type BSONElement = [
41
+ type: number,
42
+ nameOffset: number,
43
+ nameLength: number,
44
+ offset: number,
45
+ length: number
46
+ ];
47
+
48
+ /** Parses a int32 little-endian at offset, throws if it is negative */
49
+ function getSize(source: Uint8Array, offset: number): number {
50
+ if (source[offset + 3] > 127) {
51
+ throw new BSONOffsetError('BSON size cannot be negative', offset);
52
+ }
53
+ return (
54
+ source[offset] |
55
+ (source[offset + 1] << 8) |
56
+ (source[offset + 2] << 16) |
57
+ (source[offset + 3] << 24)
58
+ );
59
+ }
60
+
61
+ /**
62
+ * Searches for null terminator of a BSON element's value (Never the document null terminator)
63
+ * **Does not** bounds check since this should **ONLY** be used within parseToElements which has asserted that `bytes` ends with a `0x00`.
64
+ * So this will at most iterate to the document's terminator and error if that is the offset reached.
65
+ */
66
+ function findNull(bytes: Uint8Array, offset: number): number {
67
+ let nullTerminatorOffset = offset;
68
+
69
+ for (; bytes[nullTerminatorOffset] !== 0x00; nullTerminatorOffset++);
70
+
71
+ if (nullTerminatorOffset === bytes.length - 1) {
72
+ // We reached the null terminator of the document, not a value's
73
+ throw new BSONOffsetError('Null terminator not found', offset);
74
+ }
75
+
76
+ return nullTerminatorOffset;
77
+ }
78
+
79
+ /**
80
+ * @public
81
+ * @experimental
82
+ */
83
+ export function parseToElements(bytes: Uint8Array, startOffset = 0): Iterable<BSONElement> {
84
+ if (bytes.length < 5) {
85
+ throw new BSONOffsetError(
86
+ `Input must be at least 5 bytes, got ${bytes.length} bytes`,
87
+ startOffset
88
+ );
89
+ }
90
+
91
+ const documentSize = getSize(bytes, startOffset);
92
+
93
+ if (documentSize > bytes.length - startOffset) {
94
+ throw new BSONOffsetError(
95
+ `Parsed documentSize (${documentSize} bytes) does not match input length (${bytes.length} bytes)`,
96
+ startOffset
97
+ );
98
+ }
99
+
100
+ if (bytes[startOffset + documentSize - 1] !== 0x00) {
101
+ throw new BSONOffsetError('BSON documents must end in 0x00', startOffset + documentSize);
102
+ }
103
+
104
+ const elements: BSONElement[] = [];
105
+ let offset = startOffset + 4;
106
+
107
+ while (offset <= documentSize + startOffset) {
108
+ const type = bytes[offset];
109
+ offset += 1;
110
+
111
+ if (type === 0) {
112
+ if (offset - startOffset !== documentSize) {
113
+ throw new BSONOffsetError(`Invalid 0x00 type byte`, offset);
114
+ }
115
+ break;
116
+ }
117
+
118
+ const nameOffset = offset;
119
+ const nameLength = findNull(bytes, offset) - nameOffset;
120
+ offset += nameLength + 1;
121
+
122
+ let length: number;
123
+
124
+ if (type === t.double || type === t.long || type === t.date || type === t.timestamp) {
125
+ length = 8;
126
+ } else if (type === t.int) {
127
+ length = 4;
128
+ } else if (type === t.objectId) {
129
+ length = 12;
130
+ } else if (type === t.decimal) {
131
+ length = 16;
132
+ } else if (type === t.bool) {
133
+ length = 1;
134
+ } else if (type === t.null || type === t.undefined || type === t.maxKey || type === t.minKey) {
135
+ length = 0;
136
+ }
137
+ // Needs a size calculation
138
+ else if (type === t.regex) {
139
+ length = findNull(bytes, findNull(bytes, offset) + 1) + 1 - offset;
140
+ } else if (type === t.object || type === t.array || type === t.javascriptWithScope) {
141
+ length = getSize(bytes, offset);
142
+ } else if (
143
+ type === t.string ||
144
+ type === t.binData ||
145
+ type === t.dbPointer ||
146
+ type === t.javascript ||
147
+ type === t.symbol
148
+ ) {
149
+ length = getSize(bytes, offset) + 4;
150
+ if (type === t.binData) {
151
+ // binary subtype
152
+ length += 1;
153
+ }
154
+ if (type === t.dbPointer) {
155
+ // dbPointer's objectId
156
+ length += 12;
157
+ }
158
+ } else {
159
+ throw new BSONOffsetError(
160
+ `Invalid 0x${type.toString(16).padStart(2, '0')} type byte`,
161
+ offset
162
+ );
163
+ }
164
+
165
+ if (length > documentSize) {
166
+ throw new BSONOffsetError('value reports length larger than document', offset);
167
+ }
168
+
169
+ elements.push([type, nameOffset, nameLength, offset, length]);
170
+ offset += length;
171
+ }
172
+
173
+ return elements;
174
+ }