bson 6.2.0 → 6.4.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.
@@ -12,6 +12,7 @@ import type { MinKey } from '../min_key';
12
12
  import type { ObjectId } from '../objectid';
13
13
  import type { BSONRegExp } from '../regexp';
14
14
  import { ByteUtils } from '../utils/byte_utils';
15
+ import { NumberUtils } from '../utils/number_utils';
15
16
  import { isAnyArrayBuffer, isDate, isMap, isRegExp, isUint8Array } from './utils';
16
17
 
17
18
  /** @public */
@@ -61,10 +62,7 @@ function serializeString(buffer: Uint8Array, key: string, value: string, index:
61
62
  // Write the string
62
63
  const size = ByteUtils.encodeUTF8Into(buffer, value, index + 4);
63
64
  // Write the size of the string to buffer
64
- buffer[index + 3] = ((size + 1) >> 24) & 0xff;
65
- buffer[index + 2] = ((size + 1) >> 16) & 0xff;
66
- buffer[index + 1] = ((size + 1) >> 8) & 0xff;
67
- buffer[index] = (size + 1) & 0xff;
65
+ NumberUtils.setInt32LE(buffer, index, size + 1);
68
66
  // Update index
69
67
  index = index + 4 + size;
70
68
  // Write zero
@@ -72,10 +70,6 @@ function serializeString(buffer: Uint8Array, key: string, value: string, index:
72
70
  return index;
73
71
  }
74
72
 
75
- const NUMBER_SPACE = new DataView(new ArrayBuffer(8), 0, 8);
76
- const FOUR_BYTE_VIEW_ON_NUMBER = new Uint8Array(NUMBER_SPACE.buffer, 0, 4);
77
- const EIGHT_BYTE_VIEW_ON_NUMBER = new Uint8Array(NUMBER_SPACE.buffer, 0, 8);
78
-
79
73
  function serializeNumber(buffer: Uint8Array, key: string, value: number, index: number) {
80
74
  const isNegativeZero = Object.is(value, -0);
81
75
 
@@ -87,23 +81,17 @@ function serializeNumber(buffer: Uint8Array, key: string, value: number, index:
87
81
  ? constants.BSON_DATA_INT
88
82
  : constants.BSON_DATA_NUMBER;
89
83
 
90
- if (type === constants.BSON_DATA_INT) {
91
- NUMBER_SPACE.setInt32(0, value, true);
92
- } else {
93
- NUMBER_SPACE.setFloat64(0, value, true);
94
- }
95
-
96
- const bytes =
97
- type === constants.BSON_DATA_INT ? FOUR_BYTE_VIEW_ON_NUMBER : EIGHT_BYTE_VIEW_ON_NUMBER;
98
-
99
84
  buffer[index++] = type;
100
85
 
101
86
  const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
102
87
  index = index + numberOfWrittenBytes;
103
88
  buffer[index++] = 0x00;
104
89
 
105
- buffer.set(bytes, index);
106
- index += bytes.byteLength;
90
+ if (type === constants.BSON_DATA_INT) {
91
+ index += NumberUtils.setInt32LE(buffer, index, value);
92
+ } else {
93
+ index += NumberUtils.setFloat64LE(buffer, index, value);
94
+ }
107
95
 
108
96
  return index;
109
97
  }
@@ -115,10 +103,9 @@ function serializeBigInt(buffer: Uint8Array, key: string, value: bigint, index:
115
103
  // Encode the name
116
104
  index += numberOfWrittenBytes;
117
105
  buffer[index++] = 0;
118
- NUMBER_SPACE.setBigInt64(0, value, true);
119
- // Write BigInt value
120
- buffer.set(EIGHT_BYTE_VIEW_ON_NUMBER, index);
121
- index += EIGHT_BYTE_VIEW_ON_NUMBER.byteLength;
106
+
107
+ index += NumberUtils.setBigInt64LE(buffer, index, value);
108
+
122
109
  return index;
123
110
  }
124
111
 
@@ -162,15 +149,9 @@ function serializeDate(buffer: Uint8Array, key: string, value: Date, index: numb
162
149
  const lowBits = dateInMilis.getLowBits();
163
150
  const highBits = dateInMilis.getHighBits();
164
151
  // Encode low bits
165
- buffer[index++] = lowBits & 0xff;
166
- buffer[index++] = (lowBits >> 8) & 0xff;
167
- buffer[index++] = (lowBits >> 16) & 0xff;
168
- buffer[index++] = (lowBits >> 24) & 0xff;
152
+ index += NumberUtils.setInt32LE(buffer, index, lowBits);
169
153
  // Encode high bits
170
- buffer[index++] = highBits & 0xff;
171
- buffer[index++] = (highBits >> 8) & 0xff;
172
- buffer[index++] = (highBits >> 16) & 0xff;
173
- buffer[index++] = (highBits >> 24) & 0xff;
154
+ index += NumberUtils.setInt32LE(buffer, index, highBits);
174
155
  return index;
175
156
  }
176
157
 
@@ -256,16 +237,7 @@ function serializeObjectId(buffer: Uint8Array, key: string, value: ObjectId, ind
256
237
  index = index + numberOfWrittenBytes;
257
238
  buffer[index++] = 0;
258
239
 
259
- // Write the objectId into the shared buffer
260
- const idValue = value.id;
261
-
262
- if (isUint8Array(idValue)) {
263
- for (let i = 0; i < 12; i++) {
264
- buffer[index++] = idValue[i];
265
- }
266
- } else {
267
- throw new BSONError('object [' + JSON.stringify(value) + '] is not a valid ObjectId');
268
- }
240
+ index += value.serializeInto(buffer, index);
269
241
 
270
242
  // Adjust index
271
243
  return index;
@@ -282,14 +254,15 @@ function serializeBuffer(buffer: Uint8Array, key: string, value: Uint8Array, ind
282
254
  // Get size of the buffer (current write point)
283
255
  const size = value.length;
284
256
  // Write the size of the string to buffer
285
- buffer[index++] = size & 0xff;
286
- buffer[index++] = (size >> 8) & 0xff;
287
- buffer[index++] = (size >> 16) & 0xff;
288
- buffer[index++] = (size >> 24) & 0xff;
257
+ index += NumberUtils.setInt32LE(buffer, index, size);
289
258
  // Write the default subtype
290
259
  buffer[index++] = constants.BSON_BINARY_SUBTYPE_DEFAULT;
291
260
  // Copy the content form the binary field to the buffer
292
- buffer.set(value, index);
261
+ if (size <= 16) {
262
+ for (let i = 0; i < size; i++) buffer[index + i] = value[i];
263
+ } else {
264
+ buffer.set(value, index);
265
+ }
293
266
  // Adjust the index
294
267
  index = index + size;
295
268
  return index;
@@ -343,7 +316,7 @@ function serializeDecimal128(buffer: Uint8Array, key: string, value: Decimal128,
343
316
  index = index + numberOfWrittenBytes;
344
317
  buffer[index++] = 0;
345
318
  // Write the data from the value
346
- buffer.set(value.bytes.subarray(0, 16), index);
319
+ for (let i = 0; i < 16; i++) buffer[index + i] = value.bytes[i];
347
320
  return index + 16;
348
321
  }
349
322
 
@@ -360,15 +333,9 @@ function serializeLong(buffer: Uint8Array, key: string, value: Long, index: numb
360
333
  const lowBits = value.getLowBits();
361
334
  const highBits = value.getHighBits();
362
335
  // Encode low bits
363
- buffer[index++] = lowBits & 0xff;
364
- buffer[index++] = (lowBits >> 8) & 0xff;
365
- buffer[index++] = (lowBits >> 16) & 0xff;
366
- buffer[index++] = (lowBits >> 24) & 0xff;
336
+ index += NumberUtils.setInt32LE(buffer, index, lowBits);
367
337
  // Encode high bits
368
- buffer[index++] = highBits & 0xff;
369
- buffer[index++] = (highBits >> 8) & 0xff;
370
- buffer[index++] = (highBits >> 16) & 0xff;
371
- buffer[index++] = (highBits >> 24) & 0xff;
338
+ index += NumberUtils.setInt32LE(buffer, index, highBits);
372
339
  return index;
373
340
  }
374
341
 
@@ -382,10 +349,7 @@ function serializeInt32(buffer: Uint8Array, key: string, value: Int32 | number,
382
349
  index = index + numberOfWrittenBytes;
383
350
  buffer[index++] = 0;
384
351
  // Write the int value
385
- buffer[index++] = value & 0xff;
386
- buffer[index++] = (value >> 8) & 0xff;
387
- buffer[index++] = (value >> 16) & 0xff;
388
- buffer[index++] = (value >> 24) & 0xff;
352
+ index += NumberUtils.setInt32LE(buffer, index, value);
389
353
  return index;
390
354
  }
391
355
 
@@ -401,11 +365,8 @@ function serializeDouble(buffer: Uint8Array, key: string, value: Double, index:
401
365
  buffer[index++] = 0;
402
366
 
403
367
  // Write float
404
- NUMBER_SPACE.setFloat64(0, value.value, true);
405
- buffer.set(EIGHT_BYTE_VIEW_ON_NUMBER, index);
368
+ index += NumberUtils.setFloat64LE(buffer, index, value.value);
406
369
 
407
- // Adjust index
408
- index = index + 8;
409
370
  return index;
410
371
  }
411
372
 
@@ -422,10 +383,7 @@ function serializeFunction(buffer: Uint8Array, key: string, value: Function, ind
422
383
  // Write the string
423
384
  const size = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1;
424
385
  // Write the size of the string to buffer
425
- buffer[index] = size & 0xff;
426
- buffer[index + 1] = (size >> 8) & 0xff;
427
- buffer[index + 2] = (size >> 16) & 0xff;
428
- buffer[index + 3] = (size >> 24) & 0xff;
386
+ NumberUtils.setInt32LE(buffer, index, size);
429
387
  // Update index
430
388
  index = index + 4 + size - 1;
431
389
  // Write zero
@@ -464,10 +422,7 @@ function serializeCode(
464
422
  // Write string into buffer
465
423
  const codeSize = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1;
466
424
  // Write the size of the string to buffer
467
- buffer[index] = codeSize & 0xff;
468
- buffer[index + 1] = (codeSize >> 8) & 0xff;
469
- buffer[index + 2] = (codeSize >> 16) & 0xff;
470
- buffer[index + 3] = (codeSize >> 24) & 0xff;
425
+ NumberUtils.setInt32LE(buffer, index, codeSize);
471
426
  // Write end 0
472
427
  buffer[index + 4 + codeSize - 1] = 0;
473
428
  // Write the
@@ -490,10 +445,7 @@ function serializeCode(
490
445
  const totalSize = endIndex - startIndex;
491
446
 
492
447
  // Write the total size of the object
493
- buffer[startIndex++] = totalSize & 0xff;
494
- buffer[startIndex++] = (totalSize >> 8) & 0xff;
495
- buffer[startIndex++] = (totalSize >> 16) & 0xff;
496
- buffer[startIndex++] = (totalSize >> 24) & 0xff;
448
+ startIndex += NumberUtils.setInt32LE(buffer, startIndex, totalSize);
497
449
  // Write trailing zero
498
450
  buffer[index++] = 0;
499
451
  } else {
@@ -508,10 +460,7 @@ function serializeCode(
508
460
  // Write the string
509
461
  const size = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1;
510
462
  // Write the size of the string to buffer
511
- buffer[index] = size & 0xff;
512
- buffer[index + 1] = (size >> 8) & 0xff;
513
- buffer[index + 2] = (size >> 16) & 0xff;
514
- buffer[index + 3] = (size >> 24) & 0xff;
463
+ NumberUtils.setInt32LE(buffer, index, size);
515
464
  // Update index
516
465
  index = index + 4 + size - 1;
517
466
  // Write zero
@@ -536,24 +485,21 @@ function serializeBinary(buffer: Uint8Array, key: string, value: Binary, index:
536
485
  // Add the deprecated 02 type 4 bytes of size to total
537
486
  if (value.sub_type === Binary.SUBTYPE_BYTE_ARRAY) size = size + 4;
538
487
  // Write the size of the string to buffer
539
- buffer[index++] = size & 0xff;
540
- buffer[index++] = (size >> 8) & 0xff;
541
- buffer[index++] = (size >> 16) & 0xff;
542
- buffer[index++] = (size >> 24) & 0xff;
488
+ index += NumberUtils.setInt32LE(buffer, index, size);
543
489
  // Write the subtype to the buffer
544
490
  buffer[index++] = value.sub_type;
545
491
 
546
492
  // If we have binary type 2 the 4 first bytes are the size
547
493
  if (value.sub_type === Binary.SUBTYPE_BYTE_ARRAY) {
548
494
  size = size - 4;
549
- buffer[index++] = size & 0xff;
550
- buffer[index++] = (size >> 8) & 0xff;
551
- buffer[index++] = (size >> 16) & 0xff;
552
- buffer[index++] = (size >> 24) & 0xff;
495
+ index += NumberUtils.setInt32LE(buffer, index, size);
553
496
  }
554
497
 
555
- // Write the data to the object
556
- buffer.set(data, index);
498
+ if (size <= 16) {
499
+ for (let i = 0; i < size; i++) buffer[index + i] = data[i];
500
+ } else {
501
+ buffer.set(data, index);
502
+ }
557
503
  // Adjust the index
558
504
  index = index + value.position;
559
505
  return index;
@@ -570,14 +516,11 @@ function serializeSymbol(buffer: Uint8Array, key: string, value: BSONSymbol, ind
570
516
  // Write the string
571
517
  const size = ByteUtils.encodeUTF8Into(buffer, value.value, index + 4) + 1;
572
518
  // Write the size of the string to buffer
573
- buffer[index] = size & 0xff;
574
- buffer[index + 1] = (size >> 8) & 0xff;
575
- buffer[index + 2] = (size >> 16) & 0xff;
576
- buffer[index + 3] = (size >> 24) & 0xff;
519
+ NumberUtils.setInt32LE(buffer, index, size);
577
520
  // Update index
578
521
  index = index + 4 + size - 1;
579
522
  // Write zero
580
- buffer[index++] = 0x00;
523
+ buffer[index++] = 0;
581
524
  return index;
582
525
  }
583
526
 
@@ -624,10 +567,7 @@ function serializeDBRef(
624
567
  // Calculate object size
625
568
  const size = endIndex - startIndex;
626
569
  // Write the size
627
- buffer[startIndex++] = size & 0xff;
628
- buffer[startIndex++] = (size >> 8) & 0xff;
629
- buffer[startIndex++] = (size >> 16) & 0xff;
630
- buffer[startIndex++] = (size >> 24) & 0xff;
570
+ startIndex += NumberUtils.setInt32LE(buffer, index, size);
631
571
  // Set index
632
572
  return endIndex;
633
573
  }
@@ -799,7 +739,7 @@ export function serializeInto(
799
739
  if (checkKeys) {
800
740
  if ('$' === key[0]) {
801
741
  throw new BSONError('key ' + key + " must not start with '$'");
802
- } else if (~key.indexOf('.')) {
742
+ } else if (key.includes('.')) {
803
743
  throw new BSONError('key ' + key + " must not contain '.'");
804
744
  }
805
745
  }
@@ -907,7 +847,7 @@ export function serializeInto(
907
847
  if (checkKeys) {
908
848
  if ('$' === key[0]) {
909
849
  throw new BSONError('key ' + key + " must not start with '$'");
910
- } else if (~key.indexOf('.')) {
850
+ } else if (key.includes('.')) {
911
851
  throw new BSONError('key ' + key + " must not contain '.'");
912
852
  }
913
853
  }
@@ -997,9 +937,6 @@ export function serializeInto(
997
937
  // Final size
998
938
  const size = index - startingIndex;
999
939
  // Write the size of the object
1000
- buffer[startingIndex++] = size & 0xff;
1001
- buffer[startingIndex++] = (size >> 8) & 0xff;
1002
- buffer[startingIndex++] = (size >> 16) & 0xff;
1003
- buffer[startingIndex++] = (size >> 24) & 0xff;
940
+ startingIndex += NumberUtils.setInt32LE(buffer, startingIndex, size);
1004
941
  return index;
1005
942
  }
@@ -7,6 +7,8 @@ export type ByteUtils = {
7
7
  toLocalBufferType(buffer: Uint8Array | ArrayBufferView | ArrayBuffer): Uint8Array;
8
8
  /** Create empty space of size */
9
9
  allocate: (size: number) => Uint8Array;
10
+ /** Create empty space of size, use pooled memory when available */
11
+ allocateUnsafe: (size: number) => Uint8Array;
10
12
  /** Check if two Uint8Arrays are deep equal */
11
13
  equals: (a: Uint8Array, b: Uint8Array) => boolean;
12
14
  /** Check if two Uint8Arrays are deep equal */
@@ -23,10 +25,8 @@ export type ByteUtils = {
23
25
  fromHex: (hex: string) => Uint8Array;
24
26
  /** Create a lowercase hex string from bytes */
25
27
  toHex: (buffer: Uint8Array) => string;
26
- /** Create a Uint8Array containing utf8 code units from a string */
27
- fromUTF8: (text: string) => Uint8Array;
28
- /** Create a string from utf8 code units */
29
- toUTF8: (buffer: Uint8Array, start: number, end: number) => string;
28
+ /** Create a string from utf8 code units, fatal=true will throw an error if UTF-8 bytes are invalid, fatal=false will insert replacement characters */
29
+ toUTF8: (buffer: Uint8Array, start: number, end: number, fatal: boolean) => string;
30
30
  /** Get the utf8 code unit count from a string if it were to be transformed to utf8 */
31
31
  utf8ByteLength: (input: string) => number;
32
32
  /** Encode UTF8 bytes generated from `source` string into `destination` at byteOffset. Returns the number of bytes encoded. */
@@ -53,9 +53,3 @@ const hasGlobalBuffer = typeof Buffer === 'function' && Buffer.prototype?._isBuf
53
53
  * @internal
54
54
  */
55
55
  export const ByteUtils: ByteUtils = hasGlobalBuffer ? nodeJsByteUtils : webByteUtils;
56
-
57
- export class BSONDataView extends DataView {
58
- static fromUint8Array(input: Uint8Array) {
59
- return new DataView(input.buffer, input.byteOffset, input.byteLength);
60
- }
61
- }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * This function is an optimization for small basic latin strings.
3
+ * @internal
4
+ * @remarks
5
+ * ### Important characteristics:
6
+ * - If the uint8array or distance between start and end is 0 this function returns an empty string
7
+ * - If the byteLength of the string is 1, 2, or 3 we invoke String.fromCharCode and manually offset into the buffer
8
+ * - If the byteLength of the string is less than or equal to 20 an array of bytes is built and `String.fromCharCode.apply` is called with the result
9
+ * - If any byte exceeds 128 this function returns null
10
+ *
11
+ * @param uint8array - A sequence of bytes that may contain basic latin characters
12
+ * @param start - The start index from which to search the uint8array
13
+ * @param end - The index to stop searching the uint8array
14
+ * @returns string if all bytes are within the basic latin range, otherwise null
15
+ */
16
+ export function tryReadBasicLatin(
17
+ uint8array: Uint8Array,
18
+ start: number,
19
+ end: number
20
+ ): string | null {
21
+ if (uint8array.length === 0) {
22
+ return '';
23
+ }
24
+
25
+ const stringByteLength = end - start;
26
+ if (stringByteLength === 0) {
27
+ return '';
28
+ }
29
+
30
+ if (stringByteLength > 20) {
31
+ return null;
32
+ }
33
+
34
+ if (stringByteLength === 1 && uint8array[start] < 128) {
35
+ return String.fromCharCode(uint8array[start]);
36
+ }
37
+
38
+ if (stringByteLength === 2 && uint8array[start] < 128 && uint8array[start + 1] < 128) {
39
+ return String.fromCharCode(uint8array[start]) + String.fromCharCode(uint8array[start + 1]);
40
+ }
41
+
42
+ if (
43
+ stringByteLength === 3 &&
44
+ uint8array[start] < 128 &&
45
+ uint8array[start + 1] < 128 &&
46
+ uint8array[start + 2] < 128
47
+ ) {
48
+ return (
49
+ String.fromCharCode(uint8array[start]) +
50
+ String.fromCharCode(uint8array[start + 1]) +
51
+ String.fromCharCode(uint8array[start + 2])
52
+ );
53
+ }
54
+
55
+ const latinBytes = [];
56
+ for (let i = start; i < end; i++) {
57
+ const byte = uint8array[i];
58
+ if (byte > 127) {
59
+ return null;
60
+ }
61
+ latinBytes.push(byte);
62
+ }
63
+
64
+ return String.fromCharCode(...latinBytes);
65
+ }
66
+
67
+ /**
68
+ * This function is an optimization for writing small basic latin strings.
69
+ * @internal
70
+ * @remarks
71
+ * ### Important characteristics:
72
+ * - If the string length is 0 return 0, do not perform any work
73
+ * - If a string is longer than 25 code units return null
74
+ * - If any code unit exceeds 128 this function returns null
75
+ *
76
+ * @param destination - The uint8array to serialize the string to
77
+ * @param source - The string to turn into UTF-8 bytes if it fits in the basic latin range
78
+ * @param offset - The position in the destination to begin writing bytes to
79
+ * @returns the number of bytes written to destination if all code units are below 128, otherwise null
80
+ */
81
+ export function tryWriteBasicLatin(
82
+ destination: Uint8Array,
83
+ source: string,
84
+ offset: number
85
+ ): number | null {
86
+ if (source.length === 0) return 0;
87
+
88
+ if (source.length > 25) return null;
89
+
90
+ if (destination.length - offset < source.length) return null;
91
+
92
+ for (
93
+ let charOffset = 0, destinationOffset = offset;
94
+ charOffset < source.length;
95
+ charOffset++, destinationOffset++
96
+ ) {
97
+ const char = source.charCodeAt(charOffset);
98
+ if (char > 127) return null;
99
+
100
+ destination[destinationOffset] = char;
101
+ }
102
+
103
+ return source.length;
104
+ }
@@ -1,4 +1,6 @@
1
1
  import { BSONError } from '../error';
2
+ import { validateUtf8 } from '../validate_utf8';
3
+ import { tryReadBasicLatin, tryWriteBasicLatin } from './latin';
2
4
 
3
5
  type NodeJsEncoding = 'base64' | 'hex' | 'utf8' | 'binary';
4
6
  type NodeJsBuffer = ArrayBufferView &
@@ -10,6 +12,7 @@ type NodeJsBuffer = ArrayBufferView &
10
12
  };
11
13
  type NodeJsBufferConstructor = Omit<Uint8ArrayConstructor, 'from'> & {
12
14
  alloc: (size: number) => NodeJsBuffer;
15
+ allocUnsafe: (size: number) => NodeJsBuffer;
13
16
  from(array: number[]): NodeJsBuffer;
14
17
  from(array: Uint8Array): NodeJsBuffer;
15
18
  from(array: ArrayBuffer): NodeJsBuffer;
@@ -87,6 +90,10 @@ export const nodeJsByteUtils = {
87
90
  return Buffer.alloc(size);
88
91
  },
89
92
 
93
+ allocateUnsafe(size: number): NodeJsBuffer {
94
+ return Buffer.allocUnsafe(size);
95
+ },
96
+
90
97
  equals(a: Uint8Array, b: Uint8Array): boolean {
91
98
  return nodeJsByteUtils.toLocalBufferType(a).equals(b);
92
99
  },
@@ -121,12 +128,25 @@ export const nodeJsByteUtils = {
121
128
  return nodeJsByteUtils.toLocalBufferType(buffer).toString('hex');
122
129
  },
123
130
 
124
- fromUTF8(text: string): NodeJsBuffer {
125
- return Buffer.from(text, 'utf8');
126
- },
131
+ toUTF8(buffer: Uint8Array, start: number, end: number, fatal: boolean): string {
132
+ const basicLatin = end - start <= 20 ? tryReadBasicLatin(buffer, start, end) : null;
133
+ if (basicLatin != null) {
134
+ return basicLatin;
135
+ }
127
136
 
128
- toUTF8(buffer: Uint8Array, start: number, end: number): string {
129
- return nodeJsByteUtils.toLocalBufferType(buffer).toString('utf8', start, end);
137
+ const string = nodeJsByteUtils.toLocalBufferType(buffer).toString('utf8', start, end);
138
+ if (fatal) {
139
+ // TODO(NODE-4930): Insufficiently strict BSON UTF8 validation
140
+ for (let i = 0; i < string.length; i++) {
141
+ if (string.charCodeAt(i) === 0xfffd) {
142
+ if (!validateUtf8(buffer, start, end)) {
143
+ throw new BSONError('Invalid UTF-8 string in BSON document');
144
+ }
145
+ break;
146
+ }
147
+ }
148
+ }
149
+ return string;
130
150
  },
131
151
 
132
152
  utf8ByteLength(input: string): number {
@@ -134,6 +154,11 @@ export const nodeJsByteUtils = {
134
154
  },
135
155
 
136
156
  encodeUTF8Into(buffer: Uint8Array, source: string, byteOffset: number): number {
157
+ const latinBytesWritten = tryWriteBasicLatin(buffer, source, byteOffset);
158
+ if (latinBytesWritten != null) {
159
+ return latinBytesWritten;
160
+ }
161
+
137
162
  return nodeJsByteUtils.toLocalBufferType(buffer).write(source, byteOffset, undefined, 'utf8');
138
163
  },
139
164
 
@@ -0,0 +1,135 @@
1
+ const FLOAT = new Float64Array(1);
2
+ const FLOAT_BYTES = new Uint8Array(FLOAT.buffer, 0, 8);
3
+
4
+ /**
5
+ * Number parsing and serializing utilities.
6
+ *
7
+ * @internal
8
+ */
9
+ export const NumberUtils = {
10
+ /** Reads a little-endian 32-bit integer from source */
11
+ getInt32LE(source: Uint8Array, offset: number): number {
12
+ return (
13
+ source[offset] |
14
+ (source[offset + 1] << 8) |
15
+ (source[offset + 2] << 16) |
16
+ (source[offset + 3] << 24)
17
+ );
18
+ },
19
+
20
+ /** Reads a little-endian 32-bit unsigned integer from source */
21
+ getUint32LE(source: Uint8Array, offset: number): number {
22
+ return (
23
+ source[offset] +
24
+ source[offset + 1] * 256 +
25
+ source[offset + 2] * 65536 +
26
+ source[offset + 3] * 16777216
27
+ );
28
+ },
29
+
30
+ /** Reads a big-endian 32-bit integer from source */
31
+ getUint32BE(source: Uint8Array, offset: number): number {
32
+ return (
33
+ source[offset + 3] +
34
+ source[offset + 2] * 256 +
35
+ source[offset + 1] * 65536 +
36
+ source[offset] * 16777216
37
+ );
38
+ },
39
+
40
+ /** Reads a little-endian 64-bit integer from source */
41
+ getBigInt64LE(source: Uint8Array, offset: number): bigint {
42
+ const lo = NumberUtils.getUint32LE(source, offset);
43
+ const hi = NumberUtils.getUint32LE(source, offset + 4);
44
+
45
+ /*
46
+ eslint-disable-next-line no-restricted-globals
47
+ -- This is allowed since this helper should not be called unless bigint features are enabled
48
+ */
49
+ return (BigInt(hi) << BigInt(32)) + BigInt(lo);
50
+ },
51
+
52
+ /** Reads a little-endian 64-bit float from source */
53
+ getFloat64LE(source: Uint8Array, offset: number): number {
54
+ FLOAT_BYTES[0] = source[offset];
55
+ FLOAT_BYTES[1] = source[offset + 1];
56
+ FLOAT_BYTES[2] = source[offset + 2];
57
+ FLOAT_BYTES[3] = source[offset + 3];
58
+ FLOAT_BYTES[4] = source[offset + 4];
59
+ FLOAT_BYTES[5] = source[offset + 5];
60
+ FLOAT_BYTES[6] = source[offset + 6];
61
+ FLOAT_BYTES[7] = source[offset + 7];
62
+ return FLOAT[0];
63
+ },
64
+
65
+ /** Writes a big-endian 32-bit integer to destination, can be signed or unsigned */
66
+ setInt32BE(destination: Uint8Array, offset: number, value: number): 4 {
67
+ destination[offset + 3] = value;
68
+ value >>>= 8;
69
+ destination[offset + 2] = value;
70
+ value >>>= 8;
71
+ destination[offset + 1] = value;
72
+ value >>>= 8;
73
+ destination[offset] = value;
74
+ return 4;
75
+ },
76
+
77
+ /** Writes a little-endian 32-bit integer to destination, can be signed or unsigned */
78
+ setInt32LE(destination: Uint8Array, offset: number, value: number): 4 {
79
+ destination[offset] = value;
80
+ value >>>= 8;
81
+ destination[offset + 1] = value;
82
+ value >>>= 8;
83
+ destination[offset + 2] = value;
84
+ value >>>= 8;
85
+ destination[offset + 3] = value;
86
+ return 4;
87
+ },
88
+
89
+ /** Write a little-endian 64-bit integer to source */
90
+ setBigInt64LE(destination: Uint8Array, offset: number, value: bigint): 8 {
91
+ /* eslint-disable-next-line no-restricted-globals -- This is allowed here as useBigInt64=true */
92
+ const mask32bits = BigInt(0xffff_ffff);
93
+
94
+ /** lower 32 bits */
95
+ let lo = Number(value & mask32bits);
96
+ destination[offset] = lo;
97
+ lo >>= 8;
98
+ destination[offset + 1] = lo;
99
+ lo >>= 8;
100
+ destination[offset + 2] = lo;
101
+ lo >>= 8;
102
+ destination[offset + 3] = lo;
103
+
104
+ /*
105
+ eslint-disable-next-line no-restricted-globals
106
+ -- This is allowed here as useBigInt64=true
107
+
108
+ upper 32 bits
109
+ */
110
+ let hi = Number((value >> BigInt(32)) & mask32bits);
111
+ destination[offset + 4] = hi;
112
+ hi >>= 8;
113
+ destination[offset + 5] = hi;
114
+ hi >>= 8;
115
+ destination[offset + 6] = hi;
116
+ hi >>= 8;
117
+ destination[offset + 7] = hi;
118
+
119
+ return 8;
120
+ },
121
+
122
+ /** Writes a little-endian 64-bit float to destination */
123
+ setFloat64LE(destination: Uint8Array, offset: number, value: number): 8 {
124
+ FLOAT[0] = value;
125
+ destination[offset] = FLOAT_BYTES[0];
126
+ destination[offset + 1] = FLOAT_BYTES[1];
127
+ destination[offset + 2] = FLOAT_BYTES[2];
128
+ destination[offset + 3] = FLOAT_BYTES[3];
129
+ destination[offset + 4] = FLOAT_BYTES[4];
130
+ destination[offset + 5] = FLOAT_BYTES[5];
131
+ destination[offset + 6] = FLOAT_BYTES[6];
132
+ destination[offset + 7] = FLOAT_BYTES[7];
133
+ return 8;
134
+ }
135
+ };