Skip to content

Commit 1add538

Browse files
feat(NODE-7315): Use BSON ByteUtils instead of Nodejs Buffer (#4840)
Co-authored-by: Anna Henningsen <github@addaleax.net>
1 parent d2ad07f commit 1add538

File tree

23 files changed

+261
-212
lines changed

23 files changed

+261
-212
lines changed

.eslintrc.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,13 @@
286286
}
287287
]
288288
}
289+
],
290+
"no-restricted-globals": [
291+
"error",
292+
{
293+
"name": "Buffer",
294+
"message": "Use Uint8Array instead"
295+
}
289296
]
290297
}
291298
},

package-lock.json

Lines changed: 20 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
},
2727
"dependencies": {
2828
"@mongodb-js/saslprep": "^1.3.0",
29-
"bson": "^7.1.1",
29+
"bson": "^7.2.0",
3030
"mongodb-connection-string-url": "^7.0.0"
3131
},
3232
"peerDependencies": {

src/bson.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable no-restricted-imports */
2-
import { BSON, type DeserializeOptions, type SerializeOptions } from 'bson';
2+
import { BSON, type DeserializeOptions, NumberUtils, type SerializeOptions } from 'bson';
33

44
export {
55
Binary,
@@ -8,6 +8,7 @@ export {
88
BSONRegExp,
99
BSONSymbol,
1010
BSONType,
11+
ByteUtils,
1112
calculateObjectSize,
1213
Code,
1314
DBRef,
@@ -38,10 +39,32 @@ export function parseToElementsToArray(bytes: Uint8Array, offset?: number): BSON
3839
return Array.isArray(res) ? res : [...res];
3940
}
4041

41-
export const getInt32LE = BSON.onDemand.NumberUtils.getInt32LE;
42-
export const getFloat64LE = BSON.onDemand.NumberUtils.getFloat64LE;
43-
export const getBigInt64LE = BSON.onDemand.NumberUtils.getBigInt64LE;
44-
export const toUTF8 = BSON.onDemand.ByteUtils.toUTF8;
42+
// validates buffer inputs, used for read operations
43+
const validateBufferInputs = (buffer: Uint8Array, offset: number, length: number) => {
44+
if (offset < 0 || offset + length > buffer.length) {
45+
throw new RangeError(
46+
`Attempt to access memory outside buffer bounds: buffer length: ${buffer.length}, offset: ${offset}, length: ${length}`
47+
);
48+
}
49+
};
50+
51+
// readInt32LE, reads a 32-bit integer from buffer at given offset
52+
// throws if offset is out of bounds
53+
export const readInt32LE = (buffer: Uint8Array, offset: number): number => {
54+
validateBufferInputs(buffer, offset, 4);
55+
return NumberUtils.getInt32LE(buffer, offset);
56+
};
57+
58+
export const setUint32LE = (destination: Uint8Array, offset: number, value: number): 4 => {
59+
destination[offset] = value;
60+
value >>>= 8;
61+
destination[offset + 1] = value;
62+
value >>>= 8;
63+
destination[offset + 2] = value;
64+
value >>>= 8;
65+
destination[offset + 3] = value;
66+
return 4;
67+
};
4568

4669
/**
4770
* BSON Serialization options.

src/client-side-encryption/auto_encrypter.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type MongoCrypt, type MongoCryptOptions } from 'mongodb-client-encryption';
22
import * as net from 'net';
33

4-
import { deserialize, type Document, serialize } from '../bson';
4+
import { ByteUtils, deserialize, type Document, serialize } from '../bson';
55
import { type CommandOptions, type ProxyOptions } from '../cmap/connection';
66
import { kDecorateResult } from '../constants';
77
import { getMongoDBClientEncryption } from '../deps';
@@ -256,20 +256,26 @@ export class AutoEncrypter {
256256
errorWrapper: defaultErrorWrapper
257257
};
258258
if (options.schemaMap) {
259-
mongoCryptOptions.schemaMap = Buffer.isBuffer(options.schemaMap)
260-
? options.schemaMap
261-
: (serialize(options.schemaMap) as Buffer);
259+
if (ByteUtils.isUint8Array(options.schemaMap)) {
260+
mongoCryptOptions.schemaMap = options.schemaMap;
261+
} else {
262+
mongoCryptOptions.schemaMap = serialize(options.schemaMap);
263+
}
262264
}
263265

264266
if (options.encryptedFieldsMap) {
265-
mongoCryptOptions.encryptedFieldsMap = Buffer.isBuffer(options.encryptedFieldsMap)
266-
? options.encryptedFieldsMap
267-
: (serialize(options.encryptedFieldsMap) as Buffer);
267+
if (ByteUtils.isUint8Array(options.encryptedFieldsMap)) {
268+
mongoCryptOptions.encryptedFieldsMap = options.encryptedFieldsMap;
269+
} else {
270+
mongoCryptOptions.encryptedFieldsMap = serialize(options.encryptedFieldsMap);
271+
}
268272
}
269273

270-
mongoCryptOptions.kmsProviders = !Buffer.isBuffer(this._kmsProviders)
271-
? (serialize(this._kmsProviders) as Buffer)
272-
: this._kmsProviders;
274+
if (ByteUtils.isUint8Array(this._kmsProviders)) {
275+
mongoCryptOptions.kmsProviders = this._kmsProviders;
276+
} else {
277+
mongoCryptOptions.kmsProviders = serialize(this._kmsProviders);
278+
}
273279

274280
if (options.options?.logger) {
275281
mongoCryptOptions.logger = options.options.logger;
@@ -396,7 +402,7 @@ export class AutoEncrypter {
396402
return cmd;
397403
}
398404

399-
const commandBuffer = Buffer.isBuffer(cmd) ? cmd : serialize(cmd, options);
405+
const commandBuffer: Uint8Array = serialize(cmd, options);
400406
const context = this._mongocrypt.makeEncryptionContext(
401407
MongoDBCollectionNamespace.fromString(ns).db,
402408
commandBuffer

src/client-side-encryption/client_encryption.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,7 @@ export class ClientEncryption {
143143

144144
const mongoCryptOptions: MongoCryptOptions = {
145145
...options,
146-
kmsProviders: !Buffer.isBuffer(this._kmsProviders)
147-
? (serialize(this._kmsProviders) as Buffer)
148-
: this._kmsProviders,
146+
kmsProviders: serialize(this._kmsProviders),
149147
errorWrapper: defaultErrorWrapper
150148
};
151149

src/cmap/auth/auth_provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class AuthContext {
2121
/** A response from an initial auth attempt, only some mechanisms use this (e.g, SCRAM) */
2222
response?: Document;
2323
/** A random nonce generated for use in an authentication conversation */
24-
nonce?: Buffer;
24+
nonce?: Uint8Array;
2525

2626
constructor(
2727
connection: Connection,

src/cmap/auth/aws4.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BSON } from '../../bson';
1+
import { ByteUtils } from '../../bson';
22
import { type AWSCredentials } from '../../deps';
33

44
export type AwsSigv4Options = {
@@ -31,7 +31,7 @@ export type SignedHeaders = {
3131
const getHexSha256 = async (str: string): Promise<string> => {
3232
const data = stringToBuffer(str);
3333
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
34-
const hashHex = BSON.onDemand.ByteUtils.toHex(new Uint8Array(hashBuffer));
34+
const hashHex = ByteUtils.toHex(new Uint8Array(hashBuffer));
3535
return hashHex;
3636
};
3737

@@ -81,8 +81,8 @@ const convertHeaderValue = (value: string | number) => {
8181
* @returns Uint8Array containing the UTF-8 encoded string.
8282
*/
8383
function stringToBuffer(str: string): Uint8Array {
84-
const data = new Uint8Array(BSON.onDemand.ByteUtils.utf8ByteLength(str));
85-
BSON.onDemand.ByteUtils.encodeUTF8Into(data, str, 0);
84+
const data = new Uint8Array(ByteUtils.utf8ByteLength(str));
85+
ByteUtils.encodeUTF8Into(data, str, 0);
8686
return data;
8787
}
8888

@@ -189,7 +189,7 @@ export async function aws4Sign(
189189

190190
// 5. Calculate the signature
191191
const signatureBuffer = await getHmacSha256(signingKey, stringToSign);
192-
const signature = BSON.onDemand.ByteUtils.toHex(signatureBuffer);
192+
const signature = ByteUtils.toHex(signatureBuffer);
193193

194194
// 6. Add the signature to the request
195195
// Calculate the Authorization header

src/cmap/auth/mongodb_aws.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import type { Binary, BSONSerializeOptions } from '../../bson';
1+
import { type Binary, type BSONSerializeOptions, ByteUtils } from '../../bson';
22
import * as BSON from '../../bson';
33
import {
44
MongoCompatibilityError,
55
MongoMissingCredentialsError,
66
MongoRuntimeError
77
} from '../../error';
8-
import { ByteUtils, maxWireVersion, ns, randomBytes } from '../../utils';
8+
import { maxWireVersion, ns, randomBytes } from '../../utils';
99
import { type AuthContext, AuthProvider } from './auth_provider';
1010
import {
1111
type AWSCredentialProvider,

src/cmap/auth/plain.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Binary } from '../../bson';
1+
import { Binary, ByteUtils } from '../../bson';
22
import { MongoMissingCredentialsError } from '../../error';
33
import { ns } from '../../utils';
44
import { type AuthContext, AuthProvider } from './auth_provider';
@@ -12,7 +12,7 @@ export class Plain extends AuthProvider {
1212

1313
const { username, password } = credentials;
1414

15-
const payload = new Binary(Buffer.from(`\x00${username}\x00${password}`));
15+
const payload = new Binary(ByteUtils.fromUTF8(`\x00${username}\x00${password}`));
1616
const command = {
1717
saslStart: 1,
1818
mechanism: 'PLAIN',

0 commit comments

Comments
 (0)