@depup/mongodb 7.1.0-depup.0 → 7.5.0-depup.7

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.
Files changed (114) hide show
  1. package/README.md +5 -5
  2. package/changes.json +18 -0
  3. package/lib/bson.js +32 -5
  4. package/lib/bson.js.map +1 -1
  5. package/lib/change_stream.js +4 -1
  6. package/lib/change_stream.js.map +1 -1
  7. package/lib/client-side-encryption/auto_encrypter.js +19 -10
  8. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  9. package/lib/client-side-encryption/client_encryption.js +5 -6
  10. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  11. package/lib/cmap/auth/aws4.js +4 -4
  12. package/lib/cmap/auth/aws4.js.map +1 -1
  13. package/lib/cmap/auth/gssapi.js +3 -6
  14. package/lib/cmap/auth/gssapi.js.map +1 -1
  15. package/lib/cmap/auth/mongodb_aws.js +3 -2
  16. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  17. package/lib/cmap/auth/mongodb_oidc/azure_machine_workflow.js +3 -3
  18. package/lib/cmap/auth/mongodb_oidc/azure_machine_workflow.js.map +1 -1
  19. package/lib/cmap/auth/mongodb_oidc/gcp_machine_workflow.js +3 -3
  20. package/lib/cmap/auth/mongodb_oidc/gcp_machine_workflow.js.map +1 -1
  21. package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js +3 -3
  22. package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js.map +1 -1
  23. package/lib/cmap/auth/mongodb_oidc/token_machine_workflow.js +3 -3
  24. package/lib/cmap/auth/mongodb_oidc/token_machine_workflow.js.map +1 -1
  25. package/lib/cmap/auth/mongodb_oidc.js +4 -4
  26. package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
  27. package/lib/cmap/auth/plain.js +1 -1
  28. package/lib/cmap/auth/plain.js.map +1 -1
  29. package/lib/cmap/auth/scram.js +58 -46
  30. package/lib/cmap/auth/scram.js.map +1 -1
  31. package/lib/cmap/commands.js +46 -39
  32. package/lib/cmap/commands.js.map +1 -1
  33. package/lib/cmap/connect.js +19 -2
  34. package/lib/cmap/connect.js.map +1 -1
  35. package/lib/cmap/connection.js +5 -2
  36. package/lib/cmap/connection.js.map +1 -1
  37. package/lib/cmap/handshake/client_metadata.js +3 -4
  38. package/lib/cmap/handshake/client_metadata.js.map +1 -1
  39. package/lib/cmap/wire_protocol/compression.js +8 -7
  40. package/lib/cmap/wire_protocol/compression.js.map +1 -1
  41. package/lib/cmap/wire_protocol/constants.js +2 -2
  42. package/lib/cmap/wire_protocol/on_data.js.map +1 -1
  43. package/lib/cmap/wire_protocol/on_demand/document.js +9 -9
  44. package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -1
  45. package/lib/connection_string.js +21 -5
  46. package/lib/connection_string.js.map +1 -1
  47. package/lib/cursor/abstract_cursor.js +0 -1
  48. package/lib/cursor/abstract_cursor.js.map +1 -1
  49. package/lib/cursor/find_cursor.js.map +1 -1
  50. package/lib/gridfs/download.js +2 -1
  51. package/lib/gridfs/download.js.map +1 -1
  52. package/lib/gridfs/upload.js +7 -7
  53. package/lib/gridfs/upload.js.map +1 -1
  54. package/lib/index.js.map +1 -1
  55. package/lib/mongo_client.js +0 -1
  56. package/lib/mongo_client.js.map +1 -1
  57. package/lib/operations/count.js.map +1 -1
  58. package/lib/operations/execute_operation.js +114 -41
  59. package/lib/operations/execute_operation.js.map +1 -1
  60. package/lib/operations/operation.js +1 -0
  61. package/lib/operations/operation.js.map +1 -1
  62. package/lib/runtime_adapters.js +32 -0
  63. package/lib/runtime_adapters.js.map +1 -0
  64. package/lib/sdam/srv_polling.js +1 -1
  65. package/lib/sdam/srv_polling.js.map +1 -1
  66. package/lib/sdam/topology.js +5 -3
  67. package/lib/sdam/topology.js.map +1 -1
  68. package/lib/sessions.js +125 -81
  69. package/lib/sessions.js.map +1 -1
  70. package/lib/utils.js +52 -36
  71. package/lib/utils.js.map +1 -1
  72. package/mongodb.d.ts +96 -38
  73. package/package.json +66 -53
  74. package/src/bson.ts +34 -5
  75. package/src/change_stream.ts +5 -1
  76. package/src/client-side-encryption/auto_encrypter.ts +17 -11
  77. package/src/client-side-encryption/client_encryption.ts +42 -14
  78. package/src/cmap/auth/auth_provider.ts +1 -1
  79. package/src/cmap/auth/aws4.ts +5 -5
  80. package/src/cmap/auth/gssapi.ts +9 -6
  81. package/src/cmap/auth/mongodb_aws.ts +2 -2
  82. package/src/cmap/auth/mongodb_oidc/azure_machine_workflow.ts +1 -1
  83. package/src/cmap/auth/mongodb_oidc/gcp_machine_workflow.ts +1 -1
  84. package/src/cmap/auth/mongodb_oidc/k8s_machine_workflow.ts +1 -1
  85. package/src/cmap/auth/mongodb_oidc/token_machine_workflow.ts +1 -1
  86. package/src/cmap/auth/mongodb_oidc.ts +4 -4
  87. package/src/cmap/auth/plain.ts +2 -2
  88. package/src/cmap/auth/scram.ts +87 -61
  89. package/src/cmap/commands.ts +70 -51
  90. package/src/cmap/connect.ts +21 -1
  91. package/src/cmap/connection.ts +11 -4
  92. package/src/cmap/handshake/client_metadata.ts +6 -6
  93. package/src/cmap/wire_protocol/compression.ts +18 -14
  94. package/src/cmap/wire_protocol/constants.ts +2 -2
  95. package/src/cmap/wire_protocol/on_data.ts +5 -5
  96. package/src/cmap/wire_protocol/on_demand/document.ts +12 -14
  97. package/src/connection_string.ts +29 -11
  98. package/src/cursor/abstract_cursor.ts +0 -1
  99. package/src/cursor/find_cursor.ts +1 -1
  100. package/src/deps.ts +4 -4
  101. package/src/gridfs/download.ts +2 -2
  102. package/src/gridfs/upload.ts +13 -12
  103. package/src/index.ts +2 -0
  104. package/src/mongo_client.ts +28 -1
  105. package/src/operations/client_bulk_write/command_builder.ts +1 -1
  106. package/src/operations/count.ts +1 -2
  107. package/src/operations/execute_operation.ts +146 -45
  108. package/src/operations/operation.ts +8 -0
  109. package/src/runtime_adapters.ts +64 -0
  110. package/src/sdam/srv_polling.ts +1 -1
  111. package/src/sdam/topology.ts +11 -8
  112. package/src/sessions.ts +142 -99
  113. package/src/utils.ts +68 -46
  114. package/tsconfig.json +1 -1
@@ -1,7 +1,6 @@
1
1
  import { saslprep } from '@mongodb-js/saslprep';
2
- import * as crypto from 'crypto';
3
2
 
4
- import { Binary, type Document } from '../../bson';
3
+ import { Binary, ByteUtils, type Document } from '../../bson';
5
4
  import {
6
5
  MongoInvalidArgumentError,
7
6
  MongoMissingCredentialsError,
@@ -65,21 +64,21 @@ function cleanUsername(username: string) {
65
64
  return username.replace('=', '=3D').replace(',', '=2C');
66
65
  }
67
66
 
68
- function clientFirstMessageBare(username: string, nonce: Buffer) {
67
+ function clientFirstMessageBare(username: string, nonce: Uint8Array) {
69
68
  // NOTE: This is done b/c Javascript uses UTF-16, but the server is hashing in UTF-8.
70
69
  // Since the username is not sasl-prep-d, we need to do this here.
71
- return Buffer.concat([
72
- Buffer.from('n=', 'utf8'),
73
- Buffer.from(username, 'utf8'),
74
- Buffer.from(',r=', 'utf8'),
75
- Buffer.from(nonce.toString('base64'), 'utf8')
70
+ return ByteUtils.concat([
71
+ ByteUtils.fromUTF8('n='),
72
+ ByteUtils.fromUTF8(username),
73
+ ByteUtils.fromUTF8(',r='),
74
+ ByteUtils.fromUTF8(ByteUtils.toBase64(nonce))
76
75
  ]);
77
76
  }
78
77
 
79
78
  function makeFirstMessage(
80
79
  cryptoMethod: CryptoMethod,
81
80
  credentials: MongoCredentials,
82
- nonce: Buffer
81
+ nonce: Uint8Array
83
82
  ) {
84
83
  const username = cleanUsername(credentials.username);
85
84
  const mechanism =
@@ -91,7 +90,7 @@ function makeFirstMessage(
91
90
  saslStart: 1,
92
91
  mechanism,
93
92
  payload: new Binary(
94
- Buffer.concat([Buffer.from('n,,', 'utf8'), clientFirstMessageBare(username, nonce)])
93
+ ByteUtils.concat([ByteUtils.fromUTF8('n,,'), clientFirstMessageBare(username, nonce)])
95
94
  ),
96
95
  autoAuthorize: 1,
97
96
  options: { skipEmptyExchange: true }
@@ -136,7 +135,7 @@ async function continueScramConversation(
136
135
  const processedPassword =
137
136
  cryptoMethod === 'sha256' ? saslprep(password) : passwordDigest(username, password);
138
137
 
139
- const payload: Binary = Buffer.isBuffer(response.payload)
138
+ const payload: Binary = ByteUtils.isUint8Array(response.payload)
140
139
  ? new Binary(response.payload)
141
140
  : response.payload;
142
141
 
@@ -157,37 +156,36 @@ async function continueScramConversation(
157
156
 
158
157
  // Set up start of proof
159
158
  const withoutProof = `c=biws,r=${rnonce}`;
160
- const saltedPassword = HI(
159
+ const saltedPassword = await HI(
161
160
  processedPassword,
162
- Buffer.from(salt, 'base64'),
161
+ ByteUtils.fromBase64(salt),
163
162
  iterations,
164
163
  cryptoMethod
165
164
  );
166
165
 
167
- const clientKey = HMAC(cryptoMethod, saltedPassword, 'Client Key');
168
- const serverKey = HMAC(cryptoMethod, saltedPassword, 'Server Key');
169
- const storedKey = H(cryptoMethod, clientKey);
170
- const authMessage = [
171
- clientFirstMessageBare(username, nonce),
172
- payload.toString('utf8'),
173
- withoutProof
174
- ].join(',');
166
+ const clientKey = await HMAC(cryptoMethod, saltedPassword, 'Client Key');
167
+ const serverKey = await HMAC(cryptoMethod, saltedPassword, 'Server Key');
168
+ const storedKey = await H(cryptoMethod, clientKey);
169
+ const firstMessageBytes = clientFirstMessageBare(username, nonce);
170
+ const firstMessage = ByteUtils.toUTF8(firstMessageBytes, 0, firstMessageBytes.length, false);
171
+ const payloadString = ByteUtils.toUTF8(payload.buffer, 0, payload.position, false);
172
+ const authMessage = [firstMessage, payloadString, withoutProof].join(',');
175
173
 
176
- const clientSignature = HMAC(cryptoMethod, storedKey, authMessage);
174
+ const clientSignature = await HMAC(cryptoMethod, storedKey, authMessage);
177
175
  const clientProof = `p=${xor(clientKey, clientSignature)}`;
178
176
  const clientFinal = [withoutProof, clientProof].join(',');
179
177
 
180
- const serverSignature = HMAC(cryptoMethod, serverKey, authMessage);
178
+ const serverSignature = await HMAC(cryptoMethod, serverKey, authMessage);
181
179
  const saslContinueCmd = {
182
180
  saslContinue: 1,
183
181
  conversationId: response.conversationId,
184
- payload: new Binary(Buffer.from(clientFinal))
182
+ payload: new Binary(ByteUtils.fromUTF8(clientFinal))
185
183
  };
186
184
 
187
185
  const r = await connection.command(ns(`${db}.$cmd`), saslContinueCmd, undefined);
188
186
  const parsedResponse = parsePayload(r.payload);
189
187
 
190
- if (!compareDigest(Buffer.from(parsedResponse.v, 'base64'), serverSignature)) {
188
+ if (!compareDigest(ByteUtils.fromBase64(parsedResponse.v), serverSignature)) {
191
189
  throw new MongoRuntimeError('Server returned an invalid signature');
192
190
  }
193
191
 
@@ -199,14 +197,14 @@ async function continueScramConversation(
199
197
  const retrySaslContinueCmd = {
200
198
  saslContinue: 1,
201
199
  conversationId: r.conversationId,
202
- payload: Buffer.alloc(0)
200
+ payload: ByteUtils.allocate(0)
203
201
  };
204
202
 
205
203
  await connection.command(ns(`${db}.$cmd`), retrySaslContinueCmd, undefined);
206
204
  }
207
205
 
208
206
  function parsePayload(payload: Binary) {
209
- const payloadStr = payload.toString('utf8');
207
+ const payloadStr = ByteUtils.toUTF8(payload.buffer, 0, payload.position, false);
210
208
  const dict: Document = {};
211
209
  const parts = payloadStr.split(',');
212
210
  for (let i = 0; i < parts.length; i++) {
@@ -229,31 +227,36 @@ function passwordDigest(username: string, password: string) {
229
227
  throw new MongoInvalidArgumentError('Password cannot be empty');
230
228
  }
231
229
 
232
- let md5: crypto.Hash;
230
+ let nodeCrypto;
233
231
  try {
234
- md5 = crypto.createHash('md5');
232
+ // TODO: NODE-7424 - remove dependency on 'crypto' for SCRAM-SHA-1 authentication
233
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
234
+ nodeCrypto = require('crypto');
235
+ } catch (e) {
236
+ throw new MongoRuntimeError(
237
+ 'Node.js crypto module is required for SCRAM-SHA-1 authentication',
238
+ {
239
+ cause: e
240
+ }
241
+ );
242
+ }
243
+
244
+ try {
245
+ const md5 = nodeCrypto.createHash('md5');
246
+ md5.update(`${username}:mongo:${password}`, 'utf8');
247
+ return md5.digest('hex');
235
248
  } catch (err) {
236
- if (crypto.getFips()) {
249
+ if (nodeCrypto.getFips()) {
237
250
  // This error is (slightly) more helpful than what comes from OpenSSL directly, e.g.
238
251
  // 'Error: error:060800C8:digital envelope routines:EVP_DigestInit_ex:disabled for FIPS'
239
252
  throw new Error('Auth mechanism SCRAM-SHA-1 is not supported in FIPS mode');
240
253
  }
241
254
  throw err;
242
255
  }
243
- md5.update(`${username}:mongo:${password}`, 'utf8');
244
- return md5.digest('hex');
245
256
  }
246
257
 
247
258
  // XOR two buffers
248
- function xor(a: Buffer, b: Buffer) {
249
- if (!Buffer.isBuffer(a)) {
250
- a = Buffer.from(a);
251
- }
252
-
253
- if (!Buffer.isBuffer(b)) {
254
- b = Buffer.from(b);
255
- }
256
-
259
+ function xor(a: Uint8Array, b: Uint8Array) {
257
260
  const length = Math.max(a.length, b.length);
258
261
  const res = [];
259
262
 
@@ -261,19 +264,35 @@ function xor(a: Buffer, b: Buffer) {
261
264
  res.push(a[i] ^ b[i]);
262
265
  }
263
266
 
264
- return Buffer.from(res).toString('base64');
267
+ return ByteUtils.toBase64(ByteUtils.fromNumberArray(res));
265
268
  }
266
269
 
267
- function H(method: CryptoMethod, text: Buffer) {
268
- return crypto.createHash(method).update(text).digest();
270
+ async function H(method: CryptoMethod, text: Uint8Array): Promise<Uint8Array> {
271
+ const buffer = await crypto.subtle.digest(method === 'sha256' ? 'SHA-256' : 'SHA-1', text);
272
+ return new Uint8Array(buffer);
269
273
  }
270
274
 
271
- function HMAC(method: CryptoMethod, key: Buffer, text: Buffer | string) {
272
- return crypto.createHmac(method, key).update(text).digest();
275
+ async function HMAC(
276
+ method: CryptoMethod,
277
+ key: Uint8Array,
278
+ text: Uint8Array | string
279
+ ): Promise<Uint8Array> {
280
+ const keyBuffer = ByteUtils.toLocalBufferType(key);
281
+ const cryptoKey = await crypto.subtle.importKey(
282
+ 'raw',
283
+ keyBuffer,
284
+ { name: 'HMAC', hash: { name: method === 'sha256' ? 'SHA-256' : 'SHA-1' } },
285
+ false,
286
+ ['sign', 'verify']
287
+ );
288
+ const textData: Uint8Array = typeof text === 'string' ? new TextEncoder().encode(text) : text;
289
+ const textBuffer = ByteUtils.toLocalBufferType(textData);
290
+ const signature = await crypto.subtle.sign('HMAC', cryptoKey, textBuffer);
291
+ return new Uint8Array(signature);
273
292
  }
274
293
 
275
294
  interface HICache {
276
- [key: string]: Buffer;
295
+ [key: string]: Uint8Array;
277
296
  }
278
297
 
279
298
  let _hiCache: HICache = {};
@@ -288,21 +307,32 @@ const hiLengthMap = {
288
307
  sha1: 20
289
308
  };
290
309
 
291
- function HI(data: string, salt: Buffer, iterations: number, cryptoMethod: CryptoMethod) {
310
+ async function HI(data: string, salt: Uint8Array, iterations: number, cryptoMethod: CryptoMethod) {
292
311
  // omit the work if already generated
293
- const key = [data, salt.toString('base64'), iterations].join('_');
312
+ const key = [data, ByteUtils.toBase64(salt), iterations].join('_');
294
313
  if (_hiCache[key] != null) {
295
314
  return _hiCache[key];
296
315
  }
297
316
 
298
- // generate the salt
299
- const saltedData = crypto.pbkdf2Sync(
300
- data,
301
- salt,
302
- iterations,
303
- hiLengthMap[cryptoMethod],
304
- cryptoMethod
317
+ const keyMaterial = await crypto.subtle.importKey(
318
+ 'raw',
319
+ new TextEncoder().encode(data),
320
+ { name: 'PBKDF2' },
321
+ false,
322
+ ['deriveBits']
305
323
  );
324
+ const params = {
325
+ name: 'PBKDF2',
326
+ salt: salt,
327
+ iterations: iterations,
328
+ hash: { name: cryptoMethod === 'sha256' ? 'SHA-256' : 'SHA-1' }
329
+ };
330
+ const derivedBits = await crypto.subtle.deriveBits(
331
+ params,
332
+ keyMaterial,
333
+ hiLengthMap[cryptoMethod] * 8
334
+ );
335
+ const saltedData = new Uint8Array(derivedBits);
306
336
 
307
337
  // cache a copy to speed up the next lookup, but prevent unbounded cache growth
308
338
  if (_hiCacheCount >= 200) {
@@ -314,15 +344,11 @@ function HI(data: string, salt: Buffer, iterations: number, cryptoMethod: Crypto
314
344
  return saltedData;
315
345
  }
316
346
 
317
- function compareDigest(lhs: Buffer, rhs: Uint8Array) {
347
+ function compareDigest(lhs: Uint8Array, rhs: Uint8Array) {
318
348
  if (lhs.length !== rhs.length) {
319
349
  return false;
320
350
  }
321
351
 
322
- if (typeof crypto.timingSafeEqual === 'function') {
323
- return crypto.timingSafeEqual(lhs, rhs);
324
- }
325
-
326
352
  let result = 0;
327
353
  for (let i = 0; i < lhs.length; i++) {
328
354
  result |= lhs[i] ^ rhs[i];
@@ -1,5 +1,13 @@
1
- import type { BSONSerializeOptions, Document, Long } from '../bson';
2
- import * as BSON from '../bson';
1
+ import {
2
+ BSON,
3
+ type BSONSerializeOptions,
4
+ ByteUtils,
5
+ type Document,
6
+ type Long,
7
+ NumberUtils,
8
+ readInt32LE,
9
+ setUint32LE
10
+ } from '../bson';
3
11
  import { MongoInvalidArgumentError, MongoRuntimeError } from '../error';
4
12
  import { type ReadPreference } from '../read_preference';
5
13
  import type { ClientSession } from '../sessions';
@@ -30,7 +38,7 @@ const QUERY_FAILURE = 2;
30
38
  const SHARD_CONFIG_STALE = 4;
31
39
  const AWAIT_CAPABLE = 8;
32
40
 
33
- const encodeUTF8Into = BSON.BSON.onDemand.ByteUtils.encodeUTF8Into;
41
+ const encodeUTF8Into = ByteUtils.encodeUTF8Into;
34
42
 
35
43
  /** @internal */
36
44
  export type WriteProtocolMessageType = OpQueryRequest | OpMsgRequest;
@@ -182,10 +190,10 @@ export class OpQueryRequest {
182
190
  if (this.batchSize !== this.numberToReturn) this.numberToReturn = this.batchSize;
183
191
 
184
192
  // Allocate write protocol header buffer
185
- const header = Buffer.alloc(
193
+ const header = ByteUtils.allocate(
186
194
  4 * 4 + // Header
187
195
  4 + // Flags
188
- Buffer.byteLength(this.ns) +
196
+ ByteUtils.utf8ByteLength(this.ns) +
189
197
  1 + // namespace
190
198
  4 + // numberToSkip
191
199
  4 // numberToReturn
@@ -256,7 +264,7 @@ export class OpQueryRequest {
256
264
  index = index + 4;
257
265
 
258
266
  // Write collection name
259
- index = index + header.write(this.ns, index, 'utf8') + 1;
267
+ index = index + encodeUTF8Into(header, this.ns, index) + 1;
260
268
  header[index - 1] = 0;
261
269
 
262
270
  // Write header information flags numberToSkip
@@ -290,8 +298,8 @@ export interface MessageHeader {
290
298
  /** @internal */
291
299
  export class OpReply {
292
300
  parsed: boolean;
293
- raw: Buffer;
294
- data: Buffer;
301
+ raw: Uint8Array;
302
+ data: Uint8Array;
295
303
  opts: BSONSerializeOptions;
296
304
  length: number;
297
305
  requestId: number;
@@ -318,9 +326,9 @@ export class OpReply {
318
326
  moreToCome = false;
319
327
 
320
328
  constructor(
321
- message: Buffer,
329
+ message: Uint8Array,
322
330
  msgHeader: MessageHeader,
323
- msgBody: Buffer,
331
+ msgBody: Uint8Array,
324
332
  opts?: BSONSerializeOptions
325
333
  ) {
326
334
  this.parsed = false;
@@ -364,10 +372,10 @@ export class OpReply {
364
372
  this.index = 20;
365
373
 
366
374
  // Read the message body
367
- this.responseFlags = this.data.readInt32LE(0);
368
- this.cursorId = new BSON.Long(this.data.readInt32LE(4), this.data.readInt32LE(8));
369
- this.startingFrom = this.data.readInt32LE(12);
370
- this.numberReturned = this.data.readInt32LE(16);
375
+ this.responseFlags = readInt32LE(this.data, 0);
376
+ this.cursorId = new BSON.Long(readInt32LE(this.data, 4), readInt32LE(this.data, 8));
377
+ this.startingFrom = readInt32LE(this.data, 12);
378
+ this.numberReturned = readInt32LE(this.data, 16);
371
379
 
372
380
  if (this.numberReturned < 0 || this.numberReturned > 2 ** 32 - 1) {
373
381
  throw new RangeError(
@@ -433,7 +441,7 @@ export class DocumentSequence {
433
441
  documents: Document[];
434
442
  serializedDocumentsLength: number;
435
443
  private chunks: Uint8Array[];
436
- private header: Buffer;
444
+ private header: Uint8Array;
437
445
 
438
446
  /**
439
447
  * Create a new document sequence for the provided field.
@@ -446,7 +454,7 @@ export class DocumentSequence {
446
454
  this.serializedDocumentsLength = 0;
447
455
  // Document sequences starts with type 1 at the first byte.
448
456
  // Field strings must always be UTF-8.
449
- const buffer = Buffer.allocUnsafe(1 + 4 + this.field.length + 1);
457
+ const buffer = ByteUtils.allocateUnsafe(1 + 4 + this.field.length + 1);
450
458
  buffer[0] = 1;
451
459
  // Third part is the field name at offset 5 with trailing null byte.
452
460
  encodeUTF8Into(buffer, `${this.field}\0`, 5);
@@ -473,7 +481,13 @@ export class DocumentSequence {
473
481
  // Push the document raw bson.
474
482
  this.chunks.push(buffer);
475
483
  // Write the new length.
476
- this.header?.writeInt32LE(4 + this.field.length + 1 + this.serializedDocumentsLength, 1);
484
+ if (this.header) {
485
+ NumberUtils.setInt32LE(
486
+ this.header,
487
+ 1,
488
+ 4 + this.field.length + 1 + this.serializedDocumentsLength
489
+ );
490
+ }
477
491
  return this.serializedDocumentsLength + this.header.length;
478
492
  }
479
493
 
@@ -482,7 +496,7 @@ export class DocumentSequence {
482
496
  * @returns The section bytes.
483
497
  */
484
498
  toBin(): Uint8Array {
485
- return Buffer.concat(this.chunks);
499
+ return ByteUtils.concat(this.chunks);
486
500
  }
487
501
  }
488
502
 
@@ -531,8 +545,8 @@ export class OpMsgRequest {
531
545
  typeof options.exhaustAllowed === 'boolean' ? options.exhaustAllowed : false;
532
546
  }
533
547
 
534
- toBin(): Buffer[] {
535
- const buffers: Buffer[] = [];
548
+ toBin(): Uint8Array[] {
549
+ const buffers: Uint8Array[] = [];
536
550
  let flags = 0;
537
551
 
538
552
  if (this.checksumPresent) {
@@ -547,7 +561,7 @@ export class OpMsgRequest {
547
561
  flags |= OPTS_EXHAUST_ALLOWED;
548
562
  }
549
563
 
550
- const header = Buffer.alloc(
564
+ const header = ByteUtils.allocate(
551
565
  4 * 4 + // Header
552
566
  4 // Flags
553
567
  );
@@ -558,11 +572,13 @@ export class OpMsgRequest {
558
572
  const command = this.command;
559
573
  totalLength += this.makeSections(buffers, command);
560
574
 
561
- header.writeInt32LE(totalLength, 0); // messageLength
562
- header.writeInt32LE(this.requestId, 4); // requestID
563
- header.writeInt32LE(0, 8); // responseTo
564
- header.writeInt32LE(OP_MSG, 12); // opCode
565
- header.writeUInt32LE(flags, 16); // flags
575
+ NumberUtils.setInt32LE(header, 0, totalLength); // messageLength
576
+ NumberUtils.setInt32LE(header, 4, this.requestId); // requestID
577
+ NumberUtils.setInt32LE(header, 8, 0); // responseTo
578
+ NumberUtils.setInt32LE(header, 12, OP_MSG); // opCode
579
+ // The OP_MSG spec calls out that flags is uint32:
580
+ // https://github.com/mongodb/specifications/blob/master/source/message/OP_MSG.md#op_msg-1
581
+ setUint32LE(header, 16, flags); // flags
566
582
  return buffers;
567
583
  }
568
584
 
@@ -571,7 +587,7 @@ export class OpMsgRequest {
571
587
  */
572
588
  makeSections(buffers: Uint8Array[], document: Document): number {
573
589
  const sequencesBuffer = this.extractDocumentSequences(document);
574
- const payloadTypeBuffer = Buffer.allocUnsafe(1);
590
+ const payloadTypeBuffer = ByteUtils.allocateUnsafe(1);
575
591
  payloadTypeBuffer[0] = 0;
576
592
 
577
593
  const documentBuffer = this.serializeBson(document);
@@ -606,11 +622,11 @@ export class OpMsgRequest {
606
622
  }
607
623
  }
608
624
  if (chunks.length > 0) {
609
- return Buffer.concat(chunks);
625
+ return ByteUtils.concat(chunks);
610
626
  }
611
627
  // If we have no document sequences we return an empty buffer for nothing to add
612
628
  // to the payload.
613
- return Buffer.alloc(0);
629
+ return ByteUtils.allocate(0);
614
630
  }
615
631
 
616
632
  serializeBson(document: Document): Uint8Array {
@@ -630,8 +646,8 @@ export class OpMsgRequest {
630
646
  /** @internal */
631
647
  export class OpMsgResponse {
632
648
  parsed: boolean;
633
- raw: Buffer;
634
- data: Buffer;
649
+ raw: Uint8Array;
650
+ data: Uint8Array;
635
651
  opts: BSONSerializeOptions;
636
652
  length: number;
637
653
  requestId: number;
@@ -652,9 +668,9 @@ export class OpMsgResponse {
652
668
  sections: Uint8Array[] = [];
653
669
 
654
670
  constructor(
655
- message: Buffer,
671
+ message: Uint8Array,
656
672
  msgHeader: MessageHeader,
657
- msgBody: Buffer,
673
+ msgBody: Uint8Array,
658
674
  opts?: BSONSerializeOptions
659
675
  ) {
660
676
  this.parsed = false;
@@ -676,7 +692,7 @@ export class OpMsgResponse {
676
692
  this.fromCompressed = msgHeader.fromCompressed;
677
693
 
678
694
  // Read response flags
679
- this.responseFlags = msgBody.readInt32LE(0);
695
+ this.responseFlags = readInt32LE(msgBody, 0);
680
696
  this.checksumPresent = (this.responseFlags & OPTS_CHECKSUM_PRESENT) !== 0;
681
697
  this.moreToCome = (this.responseFlags & OPTS_MORE_TO_COME) !== 0;
682
698
  this.exhaustAllowed = (this.responseFlags & OPTS_EXHAUST_ALLOWED) !== 0;
@@ -700,9 +716,12 @@ export class OpMsgResponse {
700
716
  this.index = 4;
701
717
 
702
718
  while (this.index < this.data.length) {
703
- const payloadType = this.data.readUInt8(this.index++);
719
+ const payloadType = this.data[this.index++];
704
720
  if (payloadType === 0) {
705
- const bsonSize = this.data.readUInt32LE(this.index);
721
+ // BSON spec specifies that this is a 32-bit signed integer: https://bsonspec.org/spec.html#:~:text=%3A%3A%3D-,int32,-e_list%20unsigned_byte(0
722
+ // While allowing negative sizes seems odd, in practice we never expect a negative size. Also, the server's 16mb limit for BSON documents leaves plenty
723
+ // of room in an int32 to store a document of the max BSON size that the server supports
724
+ const bsonSize = readInt32LE(this.data, this.index);
706
725
  const bin = this.data.subarray(this.index, this.index + bsonSize);
707
726
 
708
727
  this.sections.push(bin);
@@ -758,31 +777,31 @@ export class OpCompressedRequest {
758
777
  return !uncompressibleCommands.has(commandName);
759
778
  }
760
779
 
761
- async toBin(): Promise<Buffer[]> {
762
- const concatenatedOriginalCommandBuffer = Buffer.concat(this.command.toBin());
780
+ async toBin(): Promise<Uint8Array[]> {
781
+ const concatenatedOriginalCommandBuffer = ByteUtils.concat(this.command.toBin());
763
782
  // otherwise, compress the message
764
783
  const messageToBeCompressed = concatenatedOriginalCommandBuffer.slice(MESSAGE_HEADER_SIZE);
765
784
 
766
785
  // Extract information needed for OP_COMPRESSED from the uncompressed message
767
- const originalCommandOpCode = concatenatedOriginalCommandBuffer.readInt32LE(12);
786
+ const originalCommandOpCode = readInt32LE(concatenatedOriginalCommandBuffer, 12);
768
787
 
769
788
  // Compress the message body
770
789
  const compressedMessage = await compress(this.options, messageToBeCompressed);
771
790
  // Create the msgHeader of OP_COMPRESSED
772
- const msgHeader = Buffer.alloc(MESSAGE_HEADER_SIZE);
773
- msgHeader.writeInt32LE(
774
- MESSAGE_HEADER_SIZE + COMPRESSION_DETAILS_SIZE + compressedMessage.length,
775
- 0
791
+ const msgHeader = ByteUtils.allocate(MESSAGE_HEADER_SIZE);
792
+ NumberUtils.setInt32LE(
793
+ msgHeader,
794
+ 0,
795
+ MESSAGE_HEADER_SIZE + COMPRESSION_DETAILS_SIZE + compressedMessage.length
776
796
  ); // messageLength
777
- msgHeader.writeInt32LE(this.command.requestId, 4); // requestID
778
- msgHeader.writeInt32LE(0, 8); // responseTo (zero)
779
- msgHeader.writeInt32LE(OP_COMPRESSED, 12); // opCode
780
-
797
+ NumberUtils.setInt32LE(msgHeader, 4, this.command.requestId); // requestID
798
+ NumberUtils.setInt32LE(msgHeader, 8, 0); // responseTo (zero)
799
+ NumberUtils.setInt32LE(msgHeader, 12, OP_COMPRESSED); // opCode
781
800
  // Create the compression details of OP_COMPRESSED
782
- const compressionDetails = Buffer.alloc(COMPRESSION_DETAILS_SIZE);
783
- compressionDetails.writeInt32LE(originalCommandOpCode, 0); // originalOpcode
784
- compressionDetails.writeInt32LE(messageToBeCompressed.length, 4); // Size of the uncompressed compressedMessage, excluding the MsgHeader
785
- compressionDetails.writeUInt8(Compressor[this.options.agreedCompressor], 8); // compressorID
801
+ const compressionDetails = ByteUtils.allocate(COMPRESSION_DETAILS_SIZE);
802
+ NumberUtils.setInt32LE(compressionDetails, 0, originalCommandOpCode); // originalOpcode
803
+ NumberUtils.setInt32LE(compressionDetails, 4, messageToBeCompressed.length); // Size of the uncompressed compressedMessage, excluding the MsgHeader
804
+ compressionDetails[8] = Compressor[this.options.agreedCompressor]; // compressorID
786
805
  return [msgHeader, compressionDetails, compressedMessage];
787
806
  }
788
807
  }
@@ -224,6 +224,7 @@ export interface HandshakeDocument extends Document {
224
224
  compression: string[];
225
225
  saslSupportedMechs?: string;
226
226
  loadBalanced?: boolean;
227
+ backpressure: true;
227
228
  }
228
229
 
229
230
  /**
@@ -241,6 +242,7 @@ export async function prepareHandshakeDocument(
241
242
 
242
243
  const handshakeDoc: HandshakeDocument = {
243
244
  [serverApi?.version || options.loadBalanced === true ? 'hello' : LEGACY_HELLO_COMMAND]: 1,
245
+ backpressure: true,
244
246
  helloOk: true,
245
247
  client: clientMetadata,
246
248
  compression: compressors
@@ -279,6 +281,15 @@ export async function prepareHandshakeDocument(
279
281
  return handshakeDoc;
280
282
  }
281
283
 
284
+ /**
285
+ * @internal
286
+ * Default TCP keepAlive initial delay in milliseconds.
287
+ * Set to half the Azure load balancer idle timeout (240s) to ensure
288
+ * probes fire well before cloud LBs (Azure, AWS PrivateLink/NLB)
289
+ * drop idle connections.
290
+ */
291
+ export const DEFAULT_KEEP_ALIVE_INITIAL_DELAY_MS = 120_000;
292
+
282
293
  /** @public */
283
294
  export const LEGAL_TLS_SOCKET_OPTIONS = [
284
295
  'allowPartialTrustChain',
@@ -322,7 +333,7 @@ function parseConnectOptions(options: ConnectionOptions): SocketConnectOpts {
322
333
  (result as Document)[name] = options[name];
323
334
  }
324
335
  }
325
- result.keepAliveInitialDelay ??= 120000;
336
+ result.keepAliveInitialDelay ??= DEFAULT_KEEP_ALIVE_INITIAL_DELAY_MS;
326
337
  result.keepAlive = true;
327
338
  result.noDelay = options.noDelay ?? true;
328
339
 
@@ -368,6 +379,9 @@ export async function makeSocket(options: MakeConnectionOptions): Promise<Stream
368
379
  const useTLS = options.tls ?? false;
369
380
  const connectTimeoutMS = options.connectTimeoutMS ?? 30000;
370
381
  const existingSocket = options.existingSocket;
382
+ const keepAliveInitialDelay =
383
+ options.keepAliveInitialDelay ?? DEFAULT_KEEP_ALIVE_INITIAL_DELAY_MS;
384
+ const noDelay = options.noDelay ?? true;
371
385
 
372
386
  let socket: Stream;
373
387
 
@@ -394,6 +408,12 @@ export async function makeSocket(options: MakeConnectionOptions): Promise<Stream
394
408
  socket = net.createConnection(parseConnectOptions(options));
395
409
  }
396
410
 
411
+ // Explicit setKeepAlive/setNoDelay are required because tls.connect() silently
412
+ // ignores these constructor options due to a Node.js bug.
413
+ // See: https://github.com/nodejs/node/issues/62003
414
+ // TODO(NODE-7474): remove this fix once the underlying Node.js issue is resolved.
415
+ socket.setKeepAlive(true, keepAliveInitialDelay);
416
+ socket.setNoDelay(noDelay);
397
417
  socket.setTimeout(connectTimeoutMS);
398
418
 
399
419
  let cancellationHandler: ((err: Error) => void) | null = null;
@@ -3,6 +3,7 @@ import { clearTimeout, setTimeout } from 'timers';
3
3
 
4
4
  import {
5
5
  type BSONSerializeOptions,
6
+ ByteUtils,
6
7
  deserialize,
7
8
  type DeserializeOptions,
8
9
  type Document,
@@ -35,6 +36,7 @@ import { type MongoClientAuthProviders } from '../mongo_client_auth_providers';
35
36
  import { MongoLoggableComponent, type MongoLogger, SeverityLevel } from '../mongo_logger';
36
37
  import { type Abortable, type CancellationToken, TypedEventEmitter } from '../mongo_types';
37
38
  import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
39
+ import { type Runtime } from '../runtime_adapters';
38
40
  import { ServerType } from '../sdam/common';
39
41
  import { applySession, type ClientSession, updateSessionFromResponse } from '../sessions';
40
42
  import { type TimeoutContext, TimeoutError } from '../timeout';
@@ -143,6 +145,8 @@ export interface ConnectionOptions
143
145
  metadata: Promise<ClientMetadata>;
144
146
  /** @internal */
145
147
  mongoLogger?: MongoLogger | undefined;
148
+ /** @internal */
149
+ runtime: Runtime;
146
150
  }
147
151
 
148
152
  /** @public */
@@ -174,7 +178,7 @@ function streamIdentifier(stream: Stream, options: ConnectionOptions): string {
174
178
  return HostAddress.fromHostPort(remoteAddress, remotePort).toString();
175
179
  }
176
180
 
177
- return uuidV4().toString('hex');
181
+ return ByteUtils.toHex(uuidV4());
178
182
  }
179
183
 
180
184
  /** @internal */
@@ -204,7 +208,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
204
208
  private lastUseTime: number;
205
209
  private clusterTime: Document | null = null;
206
210
  private error: Error | null = null;
207
- private dataEvents: AsyncGenerator<Buffer, void, void> | null = null;
211
+ private dataEvents: AsyncGenerator<Uint8Array, void, void> | null = null;
208
212
 
209
213
  private readonly socketTimeoutMS: number;
210
214
  private readonly monitorCommands: boolean;
@@ -582,6 +586,9 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
582
586
  this.throwIfAborted();
583
587
  }
584
588
  } catch (error) {
589
+ if (options.session != null && !(error instanceof MongoServerError)) {
590
+ updateSessionFromResponse(options.session, MongoDBResponse.empty);
591
+ }
585
592
  if (this.shouldEmitAndLogCommand) {
586
593
  this.emitAndLogCommand(
587
594
  this.monitorCommands,
@@ -696,7 +703,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
696
703
  zlibCompressionLevel: options.zlibCompressionLevel ?? 0
697
704
  });
698
705
 
699
- const buffer = Buffer.concat(await finalCommand.toBin());
706
+ const buffer = ByteUtils.concat(await finalCommand.toBin());
700
707
 
701
708
  if (options.timeoutContext?.csotEnabled()) {
702
709
  if (
@@ -794,7 +801,7 @@ export class SizedMessageTransform extends Transform {
794
801
  this.connection = connection;
795
802
  }
796
803
 
797
- override _transform(chunk: Buffer, encoding: unknown, callback: TransformCallback): void {
804
+ override _transform(chunk: Uint8Array, encoding: unknown, callback: TransformCallback): void {
798
805
  if (this.connection.delayedTimeoutId != null) {
799
806
  clearTimeout(this.connection.delayedTimeoutId);
800
807
  this.connection.delayedTimeoutId = null;