@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.
- package/README.md +5 -5
- package/changes.json +18 -0
- package/lib/bson.js +32 -5
- package/lib/bson.js.map +1 -1
- package/lib/change_stream.js +4 -1
- package/lib/change_stream.js.map +1 -1
- package/lib/client-side-encryption/auto_encrypter.js +19 -10
- package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
- package/lib/client-side-encryption/client_encryption.js +5 -6
- package/lib/client-side-encryption/client_encryption.js.map +1 -1
- package/lib/cmap/auth/aws4.js +4 -4
- package/lib/cmap/auth/aws4.js.map +1 -1
- package/lib/cmap/auth/gssapi.js +3 -6
- package/lib/cmap/auth/gssapi.js.map +1 -1
- package/lib/cmap/auth/mongodb_aws.js +3 -2
- package/lib/cmap/auth/mongodb_aws.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/azure_machine_workflow.js +3 -3
- package/lib/cmap/auth/mongodb_oidc/azure_machine_workflow.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/gcp_machine_workflow.js +3 -3
- package/lib/cmap/auth/mongodb_oidc/gcp_machine_workflow.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js +3 -3
- package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/token_machine_workflow.js +3 -3
- package/lib/cmap/auth/mongodb_oidc/token_machine_workflow.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc.js +4 -4
- package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
- package/lib/cmap/auth/plain.js +1 -1
- package/lib/cmap/auth/plain.js.map +1 -1
- package/lib/cmap/auth/scram.js +58 -46
- package/lib/cmap/auth/scram.js.map +1 -1
- package/lib/cmap/commands.js +46 -39
- package/lib/cmap/commands.js.map +1 -1
- package/lib/cmap/connect.js +19 -2
- package/lib/cmap/connect.js.map +1 -1
- package/lib/cmap/connection.js +5 -2
- package/lib/cmap/connection.js.map +1 -1
- package/lib/cmap/handshake/client_metadata.js +3 -4
- package/lib/cmap/handshake/client_metadata.js.map +1 -1
- package/lib/cmap/wire_protocol/compression.js +8 -7
- package/lib/cmap/wire_protocol/compression.js.map +1 -1
- package/lib/cmap/wire_protocol/constants.js +2 -2
- package/lib/cmap/wire_protocol/on_data.js.map +1 -1
- package/lib/cmap/wire_protocol/on_demand/document.js +9 -9
- package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -1
- package/lib/connection_string.js +21 -5
- package/lib/connection_string.js.map +1 -1
- package/lib/cursor/abstract_cursor.js +0 -1
- package/lib/cursor/abstract_cursor.js.map +1 -1
- package/lib/cursor/find_cursor.js.map +1 -1
- package/lib/gridfs/download.js +2 -1
- package/lib/gridfs/download.js.map +1 -1
- package/lib/gridfs/upload.js +7 -7
- package/lib/gridfs/upload.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/mongo_client.js +0 -1
- package/lib/mongo_client.js.map +1 -1
- package/lib/operations/count.js.map +1 -1
- package/lib/operations/execute_operation.js +114 -41
- package/lib/operations/execute_operation.js.map +1 -1
- package/lib/operations/operation.js +1 -0
- package/lib/operations/operation.js.map +1 -1
- package/lib/runtime_adapters.js +32 -0
- package/lib/runtime_adapters.js.map +1 -0
- package/lib/sdam/srv_polling.js +1 -1
- package/lib/sdam/srv_polling.js.map +1 -1
- package/lib/sdam/topology.js +5 -3
- package/lib/sdam/topology.js.map +1 -1
- package/lib/sessions.js +125 -81
- package/lib/sessions.js.map +1 -1
- package/lib/utils.js +52 -36
- package/lib/utils.js.map +1 -1
- package/mongodb.d.ts +96 -38
- package/package.json +66 -53
- package/src/bson.ts +34 -5
- package/src/change_stream.ts +5 -1
- package/src/client-side-encryption/auto_encrypter.ts +17 -11
- package/src/client-side-encryption/client_encryption.ts +42 -14
- package/src/cmap/auth/auth_provider.ts +1 -1
- package/src/cmap/auth/aws4.ts +5 -5
- package/src/cmap/auth/gssapi.ts +9 -6
- package/src/cmap/auth/mongodb_aws.ts +2 -2
- package/src/cmap/auth/mongodb_oidc/azure_machine_workflow.ts +1 -1
- package/src/cmap/auth/mongodb_oidc/gcp_machine_workflow.ts +1 -1
- package/src/cmap/auth/mongodb_oidc/k8s_machine_workflow.ts +1 -1
- package/src/cmap/auth/mongodb_oidc/token_machine_workflow.ts +1 -1
- package/src/cmap/auth/mongodb_oidc.ts +4 -4
- package/src/cmap/auth/plain.ts +2 -2
- package/src/cmap/auth/scram.ts +87 -61
- package/src/cmap/commands.ts +70 -51
- package/src/cmap/connect.ts +21 -1
- package/src/cmap/connection.ts +11 -4
- package/src/cmap/handshake/client_metadata.ts +6 -6
- package/src/cmap/wire_protocol/compression.ts +18 -14
- package/src/cmap/wire_protocol/constants.ts +2 -2
- package/src/cmap/wire_protocol/on_data.ts +5 -5
- package/src/cmap/wire_protocol/on_demand/document.ts +12 -14
- package/src/connection_string.ts +29 -11
- package/src/cursor/abstract_cursor.ts +0 -1
- package/src/cursor/find_cursor.ts +1 -1
- package/src/deps.ts +4 -4
- package/src/gridfs/download.ts +2 -2
- package/src/gridfs/upload.ts +13 -12
- package/src/index.ts +2 -0
- package/src/mongo_client.ts +28 -1
- package/src/operations/client_bulk_write/command_builder.ts +1 -1
- package/src/operations/count.ts +1 -2
- package/src/operations/execute_operation.ts +146 -45
- package/src/operations/operation.ts +8 -0
- package/src/runtime_adapters.ts +64 -0
- package/src/sdam/srv_polling.ts +1 -1
- package/src/sdam/topology.ts +11 -8
- package/src/sessions.ts +142 -99
- package/src/utils.ts +68 -46
- package/tsconfig.json +1 -1
package/src/cmap/auth/scram.ts
CHANGED
|
@@ -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:
|
|
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
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
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.
|
|
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
|
|
230
|
+
let nodeCrypto;
|
|
233
231
|
try {
|
|
234
|
-
|
|
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 (
|
|
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:
|
|
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
|
|
267
|
+
return ByteUtils.toBase64(ByteUtils.fromNumberArray(res));
|
|
265
268
|
}
|
|
266
269
|
|
|
267
|
-
function H(method: CryptoMethod, text:
|
|
268
|
-
|
|
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(
|
|
272
|
-
|
|
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]:
|
|
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:
|
|
310
|
+
async function HI(data: string, salt: Uint8Array, iterations: number, cryptoMethod: CryptoMethod) {
|
|
292
311
|
// omit the work if already generated
|
|
293
|
-
const key = [data,
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
data,
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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:
|
|
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];
|
package/src/cmap/commands.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
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 =
|
|
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 =
|
|
193
|
+
const header = ByteUtils.allocate(
|
|
186
194
|
4 * 4 + // Header
|
|
187
195
|
4 + // Flags
|
|
188
|
-
|
|
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
|
|
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:
|
|
294
|
-
data:
|
|
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:
|
|
329
|
+
message: Uint8Array,
|
|
322
330
|
msgHeader: MessageHeader,
|
|
323
|
-
msgBody:
|
|
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
|
|
368
|
-
this.cursorId = new BSON.Long(this.data
|
|
369
|
-
this.startingFrom = this.data
|
|
370
|
-
this.numberReturned = this.data
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
|
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():
|
|
535
|
-
const buffers:
|
|
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 =
|
|
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
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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:
|
|
634
|
-
data:
|
|
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:
|
|
671
|
+
message: Uint8Array,
|
|
656
672
|
msgHeader: MessageHeader,
|
|
657
|
-
msgBody:
|
|
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 =
|
|
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
|
|
719
|
+
const payloadType = this.data[this.index++];
|
|
704
720
|
if (payloadType === 0) {
|
|
705
|
-
|
|
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<
|
|
762
|
-
const concatenatedOriginalCommandBuffer =
|
|
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 =
|
|
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 =
|
|
773
|
-
|
|
774
|
-
|
|
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
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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 =
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
compressionDetails
|
|
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
|
}
|
package/src/cmap/connect.ts
CHANGED
|
@@ -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 ??=
|
|
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;
|
package/src/cmap/connection.ts
CHANGED
|
@@ -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()
|
|
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<
|
|
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 =
|
|
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:
|
|
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;
|