mongodb 7.1.1 → 7.2.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.
- package/README.md +11 -0
- package/lib/bson.js +26 -5
- package/lib/bson.js.map +1 -1
- package/lib/change_stream.js +4 -0
- 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 +1 -3
- 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 +53 -40
- 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 +1 -0
- 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/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/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/mongo_client.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 +4 -2
- package/lib/sdam/topology.js.map +1 -1
- package/lib/sessions.js +124 -79
- package/lib/sessions.js.map +1 -1
- package/lib/utils.js +10 -33
- package/lib/utils.js.map +1 -1
- package/mongodb.d.ts +45 -2
- package/package.json +30 -21
- package/src/bson.ts +28 -5
- package/src/change_stream.ts +5 -0
- package/src/client-side-encryption/auto_encrypter.ts +17 -11
- package/src/client-side-encryption/client_encryption.ts +1 -3
- 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 +82 -55
- package/src/cmap/commands.ts +70 -51
- package/src/cmap/connect.ts +2 -0
- 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/on_data.ts +5 -5
- package/src/cmap/wire_protocol/on_demand/document.ts +12 -14
- package/src/connection_string.ts +26 -8
- package/src/deps.ts +4 -4
- package/src/gridfs/download.ts +2 -2
- package/src/gridfs/upload.ts +13 -12
- package/src/index.ts +1 -0
- package/src/mongo_client.ts +24 -0
- package/src/operations/client_bulk_write/command_builder.ts +1 -1
- 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 +10 -7
- package/src/sessions.ts +140 -96
- package/src/utils.ts +21 -40
- package/tsconfig.json +1 -1
|
@@ -5,11 +5,11 @@ import type { Connection } from '../connection';
|
|
|
5
5
|
import { type AuthContext, AuthProvider } from './auth_provider';
|
|
6
6
|
import type { MongoCredentials } from './mongo_credentials';
|
|
7
7
|
import { AutomatedCallbackWorkflow } from './mongodb_oidc/automated_callback_workflow';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { azureCallback } from './mongodb_oidc/azure_machine_workflow';
|
|
9
|
+
import { gcpCallback } from './mongodb_oidc/gcp_machine_workflow';
|
|
10
|
+
import { k8sCallback } from './mongodb_oidc/k8s_machine_workflow';
|
|
11
11
|
import { TokenCache } from './mongodb_oidc/token_cache';
|
|
12
|
-
import {
|
|
12
|
+
import { tokenMachineCallback as testCallback } from './mongodb_oidc/token_machine_workflow';
|
|
13
13
|
|
|
14
14
|
/** Error when credentials are missing. */
|
|
15
15
|
const MISSING_CREDENTIALS_ERROR = 'AuthContext must provide credentials.';
|
package/src/cmap/auth/plain.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Binary } from '../../bson';
|
|
1
|
+
import { Binary, ByteUtils } from '../../bson';
|
|
2
2
|
import { MongoMissingCredentialsError } from '../../error';
|
|
3
3
|
import { ns } from '../../utils';
|
|
4
4
|
import { type AuthContext, AuthProvider } from './auth_provider';
|
|
@@ -12,7 +12,7 @@ export class Plain extends AuthProvider {
|
|
|
12
12
|
|
|
13
13
|
const { username, password } = credentials;
|
|
14
14
|
|
|
15
|
-
const payload = new Binary(
|
|
15
|
+
const payload = new Binary(ByteUtils.fromUTF8(`\x00${username}\x00${password}`));
|
|
16
16
|
const command = {
|
|
17
17
|
saslStart: 1,
|
|
18
18
|
mechanism: 'PLAIN',
|
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,37 @@ 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);
|
|
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);
|
|
170
169
|
const authMessage = [
|
|
171
170
|
clientFirstMessageBare(username, nonce),
|
|
172
171
|
payload.toString('utf8'),
|
|
173
172
|
withoutProof
|
|
174
173
|
].join(',');
|
|
175
174
|
|
|
176
|
-
const clientSignature = HMAC(cryptoMethod, storedKey, authMessage);
|
|
175
|
+
const clientSignature = await HMAC(cryptoMethod, storedKey, authMessage);
|
|
177
176
|
const clientProof = `p=${xor(clientKey, clientSignature)}`;
|
|
178
177
|
const clientFinal = [withoutProof, clientProof].join(',');
|
|
179
178
|
|
|
180
|
-
const serverSignature = HMAC(cryptoMethod, serverKey, authMessage);
|
|
179
|
+
const serverSignature = await HMAC(cryptoMethod, serverKey, authMessage);
|
|
181
180
|
const saslContinueCmd = {
|
|
182
181
|
saslContinue: 1,
|
|
183
182
|
conversationId: response.conversationId,
|
|
184
|
-
payload: new Binary(
|
|
183
|
+
payload: new Binary(ByteUtils.fromUTF8(clientFinal))
|
|
185
184
|
};
|
|
186
185
|
|
|
187
186
|
const r = await connection.command(ns(`${db}.$cmd`), saslContinueCmd, undefined);
|
|
188
187
|
const parsedResponse = parsePayload(r.payload);
|
|
189
188
|
|
|
190
|
-
if (!compareDigest(
|
|
189
|
+
if (!compareDigest(ByteUtils.fromBase64(parsedResponse.v), serverSignature)) {
|
|
191
190
|
throw new MongoRuntimeError('Server returned an invalid signature');
|
|
192
191
|
}
|
|
193
192
|
|
|
@@ -199,7 +198,7 @@ async function continueScramConversation(
|
|
|
199
198
|
const retrySaslContinueCmd = {
|
|
200
199
|
saslContinue: 1,
|
|
201
200
|
conversationId: r.conversationId,
|
|
202
|
-
payload:
|
|
201
|
+
payload: ByteUtils.allocate(0)
|
|
203
202
|
};
|
|
204
203
|
|
|
205
204
|
await connection.command(ns(`${db}.$cmd`), retrySaslContinueCmd, undefined);
|
|
@@ -229,31 +228,36 @@ function passwordDigest(username: string, password: string) {
|
|
|
229
228
|
throw new MongoInvalidArgumentError('Password cannot be empty');
|
|
230
229
|
}
|
|
231
230
|
|
|
232
|
-
let
|
|
231
|
+
let nodeCrypto;
|
|
233
232
|
try {
|
|
234
|
-
|
|
233
|
+
// TODO: NODE-7424 - remove dependency on 'crypto' for SCRAM-SHA-1 authentication
|
|
234
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
235
|
+
nodeCrypto = require('crypto');
|
|
236
|
+
} catch (e) {
|
|
237
|
+
throw new MongoRuntimeError(
|
|
238
|
+
'Node.js crypto module is required for SCRAM-SHA-1 authentication',
|
|
239
|
+
{
|
|
240
|
+
cause: e
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
const md5 = nodeCrypto.createHash('md5');
|
|
247
|
+
md5.update(`${username}:mongo:${password}`, 'utf8');
|
|
248
|
+
return md5.digest('hex');
|
|
235
249
|
} catch (err) {
|
|
236
|
-
if (
|
|
250
|
+
if (nodeCrypto.getFips()) {
|
|
237
251
|
// This error is (slightly) more helpful than what comes from OpenSSL directly, e.g.
|
|
238
252
|
// 'Error: error:060800C8:digital envelope routines:EVP_DigestInit_ex:disabled for FIPS'
|
|
239
253
|
throw new Error('Auth mechanism SCRAM-SHA-1 is not supported in FIPS mode');
|
|
240
254
|
}
|
|
241
255
|
throw err;
|
|
242
256
|
}
|
|
243
|
-
md5.update(`${username}:mongo:${password}`, 'utf8');
|
|
244
|
-
return md5.digest('hex');
|
|
245
257
|
}
|
|
246
258
|
|
|
247
259
|
// 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
|
-
|
|
260
|
+
function xor(a: Uint8Array, b: Uint8Array) {
|
|
257
261
|
const length = Math.max(a.length, b.length);
|
|
258
262
|
const res = [];
|
|
259
263
|
|
|
@@ -261,19 +265,35 @@ function xor(a: Buffer, b: Buffer) {
|
|
|
261
265
|
res.push(a[i] ^ b[i]);
|
|
262
266
|
}
|
|
263
267
|
|
|
264
|
-
return
|
|
268
|
+
return ByteUtils.toBase64(ByteUtils.fromNumberArray(res));
|
|
265
269
|
}
|
|
266
270
|
|
|
267
|
-
function H(method: CryptoMethod, text:
|
|
268
|
-
|
|
271
|
+
async function H(method: CryptoMethod, text: Uint8Array): Promise<Uint8Array> {
|
|
272
|
+
const buffer = await crypto.subtle.digest(method === 'sha256' ? 'SHA-256' : 'SHA-1', text);
|
|
273
|
+
return new Uint8Array(buffer);
|
|
269
274
|
}
|
|
270
275
|
|
|
271
|
-
function HMAC(
|
|
272
|
-
|
|
276
|
+
async function HMAC(
|
|
277
|
+
method: CryptoMethod,
|
|
278
|
+
key: Uint8Array,
|
|
279
|
+
text: Uint8Array | string
|
|
280
|
+
): Promise<Uint8Array> {
|
|
281
|
+
const keyBuffer = ByteUtils.toLocalBufferType(key);
|
|
282
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
283
|
+
'raw',
|
|
284
|
+
keyBuffer,
|
|
285
|
+
{ name: 'HMAC', hash: { name: method === 'sha256' ? 'SHA-256' : 'SHA-1' } },
|
|
286
|
+
false,
|
|
287
|
+
['sign', 'verify']
|
|
288
|
+
);
|
|
289
|
+
const textData: Uint8Array = typeof text === 'string' ? new TextEncoder().encode(text) : text;
|
|
290
|
+
const textBuffer = ByteUtils.toLocalBufferType(textData);
|
|
291
|
+
const signature = await crypto.subtle.sign('HMAC', cryptoKey, textBuffer);
|
|
292
|
+
return new Uint8Array(signature);
|
|
273
293
|
}
|
|
274
294
|
|
|
275
295
|
interface HICache {
|
|
276
|
-
[key: string]:
|
|
296
|
+
[key: string]: Uint8Array;
|
|
277
297
|
}
|
|
278
298
|
|
|
279
299
|
let _hiCache: HICache = {};
|
|
@@ -288,21 +308,32 @@ const hiLengthMap = {
|
|
|
288
308
|
sha1: 20
|
|
289
309
|
};
|
|
290
310
|
|
|
291
|
-
function HI(data: string, salt:
|
|
311
|
+
async function HI(data: string, salt: Uint8Array, iterations: number, cryptoMethod: CryptoMethod) {
|
|
292
312
|
// omit the work if already generated
|
|
293
|
-
const key = [data,
|
|
313
|
+
const key = [data, ByteUtils.toBase64(salt), iterations].join('_');
|
|
294
314
|
if (_hiCache[key] != null) {
|
|
295
315
|
return _hiCache[key];
|
|
296
316
|
}
|
|
297
317
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
data,
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
cryptoMethod
|
|
318
|
+
const keyMaterial = await crypto.subtle.importKey(
|
|
319
|
+
'raw',
|
|
320
|
+
new TextEncoder().encode(data),
|
|
321
|
+
{ name: 'PBKDF2' },
|
|
322
|
+
false,
|
|
323
|
+
['deriveBits']
|
|
305
324
|
);
|
|
325
|
+
const params = {
|
|
326
|
+
name: 'PBKDF2',
|
|
327
|
+
salt: salt,
|
|
328
|
+
iterations: iterations,
|
|
329
|
+
hash: { name: cryptoMethod === 'sha256' ? 'SHA-256' : 'SHA-1' }
|
|
330
|
+
};
|
|
331
|
+
const derivedBits = await crypto.subtle.deriveBits(
|
|
332
|
+
params,
|
|
333
|
+
keyMaterial,
|
|
334
|
+
hiLengthMap[cryptoMethod] * 8
|
|
335
|
+
);
|
|
336
|
+
const saltedData = new Uint8Array(derivedBits);
|
|
306
337
|
|
|
307
338
|
// cache a copy to speed up the next lookup, but prevent unbounded cache growth
|
|
308
339
|
if (_hiCacheCount >= 200) {
|
|
@@ -314,15 +345,11 @@ function HI(data: string, salt: Buffer, iterations: number, cryptoMethod: Crypto
|
|
|
314
345
|
return saltedData;
|
|
315
346
|
}
|
|
316
347
|
|
|
317
|
-
function compareDigest(lhs:
|
|
348
|
+
function compareDigest(lhs: Uint8Array, rhs: Uint8Array) {
|
|
318
349
|
if (lhs.length !== rhs.length) {
|
|
319
350
|
return false;
|
|
320
351
|
}
|
|
321
352
|
|
|
322
|
-
if (typeof crypto.timingSafeEqual === 'function') {
|
|
323
|
-
return crypto.timingSafeEqual(lhs, rhs);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
353
|
let result = 0;
|
|
327
354
|
for (let i = 0; i < lhs.length; i++) {
|
|
328
355
|
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
|
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;
|