Skip to content

Commit 95849dd

Browse files
authored
chore(NODE-5455): benchmark FLE (#16)
1 parent 270ecc6 commit 95849dd

File tree

6 files changed

+283
-20
lines changed

6 files changed

+283
-20
lines changed

addon/mongocrypt.cc

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,11 @@ std::unique_ptr<mongocrypt_binary_t, MongoCryptBinaryDeleter> Uint8ArrayToBinary
4545
}
4646

4747
Uint8Array BufferFromBinary(Env env, mongocrypt_binary_t* binary) {
48-
const uint8_t* data = mongocrypt_binary_data(binary);
49-
size_t len = mongocrypt_binary_len(binary);
50-
return Buffer<uint8_t>::Copy(env, data, len);
48+
return Buffer<uint8_t>::Copy(env, (uint8_t*)binary->data, binary->len);
5149
}
5250

5351
Uint8Array BufferWithLengthOf(Env env, mongocrypt_binary_t* binary) {
54-
size_t len = mongocrypt_binary_len(binary);
55-
return Buffer<uint8_t>::New(env, len);
52+
return Buffer<uint8_t>::New(env, binary->len);
5653
}
5754

5855
Uint8Array Uint8ArrayFromValue(Napi::Value v, std::string argument_name) {
@@ -64,13 +61,13 @@ Uint8Array Uint8ArrayFromValue(Napi::Value v, std::string argument_name) {
6461
}
6562

6663
void CopyBufferData(mongocrypt_binary_t* out, Uint8Array buffer, size_t count) {
67-
assert(count <= mongocrypt_binary_len(out));
64+
assert(count <= out->len);
6865
assert(count <= buffer.ByteLength());
69-
memcpy(mongocrypt_binary_data(out), buffer.Data(), count);
66+
memcpy(out->data, buffer.Data(), count);
7067
}
7168

7269
void CopyBufferData(mongocrypt_binary_t* out, Uint8Array buffer) {
73-
CopyBufferData(out, buffer, mongocrypt_binary_len(out));
70+
CopyBufferData(out, buffer, out->len);
7471
}
7572

7673
std::string errorStringFromStatus(mongocrypt_t* crypt) {
@@ -184,12 +181,12 @@ static bool aes_256_generic_hook(MongoCrypt* mongoCrypt,
184181
Uint8Array keyBuffer = BufferFromBinary(env, key);
185182
Uint8Array ivBuffer = BufferFromBinary(env, iv);
186183
Uint8Array inBuffer = BufferFromBinary(env, in);
187-
Uint8Array outBuffer = BufferWithLengthOf(env, out);
184+
Uint8Array outputBuffer = BufferWithLengthOf(env, out);
188185

189186
Value result;
190187
try {
191-
result =
192-
hook.Call(std::initializer_list<napi_value>{keyBuffer, ivBuffer, inBuffer, outBuffer});
188+
result = hook.Call(
189+
std::initializer_list<napi_value>{keyBuffer, ivBuffer, inBuffer, outputBuffer});
193190
} catch (...) {
194191
return false;
195192
}
@@ -200,7 +197,7 @@ static bool aes_256_generic_hook(MongoCrypt* mongoCrypt,
200197
}
201198

202199
*bytes_written = result.ToNumber().Uint32Value();
203-
CopyBufferData(out, outBuffer, *bytes_written);
200+
CopyBufferData(out, outputBuffer, *bytes_written);
204201
return true;
205202
}
206203

@@ -262,11 +259,11 @@ bool MongoCrypt::setupCryptoHooks() {
262259
HandleScope scope(env);
263260
Function hook = mongoCrypt->GetCallback("randomHook");
264261

265-
Uint8Array outBuffer = BufferWithLengthOf(env, out);
262+
Uint8Array outputBuffer = BufferWithLengthOf(env, out);
266263
Napi::Value result;
267264
try {
268265
result =
269-
hook.Call(std::initializer_list<napi_value>{outBuffer, Number::New(env, count)});
266+
hook.Call(std::initializer_list<napi_value>{outputBuffer, Number::New(env, count)});
270267
} catch (...) {
271268
return false;
272269
}
@@ -276,7 +273,7 @@ bool MongoCrypt::setupCryptoHooks() {
276273
return false;
277274
}
278275

279-
CopyBufferData(out, outBuffer);
276+
CopyBufferData(out, outputBuffer);
280277
return true;
281278
};
282279

package-lock.json

Lines changed: 4 additions & 4 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
@@ -46,7 +46,7 @@
4646
"@types/mocha": "^10.0.6",
4747
"@types/node": "^20.12.7",
4848
"@typescript-eslint/eslint-plugin": "^7.7.0",
49-
"bson": "^6.6.0",
49+
"bson": "^6.7.0",
5050
"chai": "^4.4.1",
5151
"chai-subset": "^1.6.0",
5252
"clang-format": "^1.8.0",

test/benchmarks/bench.mjs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// @ts-check
2+
/* eslint-disable no-console */
3+
import os from 'node:os';
4+
import path from 'node:path';
5+
import url from 'node:url';
6+
import process from 'node:process';
7+
import fs from 'node:fs';
8+
import { EJSON, BSON } from 'bson';
9+
import { cryptoCallbacks } from './crypto_callbacks.mjs';
10+
import { MongoCrypt } from '../../lib/index.js';
11+
12+
const NEED_MONGO_KEYS = 3;
13+
const READY = 5;
14+
const ERROR = 0;
15+
16+
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
17+
18+
const { CRYPT_SHARED_LIB_PATH: cryptSharedLibPath = '', BENCH_WITH_NATIVE_CRYPTO = '' } =
19+
process.env;
20+
21+
const warmupSecs = 2;
22+
const testInSecs = 57;
23+
const fieldCount = 1500;
24+
25+
const LOCAL_KEY = new Uint8Array([
26+
0x9d, 0x94, 0x4b, 0x0d, 0x93, 0xd0, 0xc5, 0x44, 0xa5, 0x72, 0xfd, 0x32, 0x1b, 0x94, 0x30, 0x90,
27+
0x23, 0x35, 0x73, 0x7c, 0xf0, 0xf6, 0xc2, 0xf4, 0xda, 0x23, 0x56, 0xe7, 0x8f, 0x04, 0xcc, 0xfa,
28+
0xde, 0x75, 0xb4, 0x51, 0x87, 0xf3, 0x8b, 0x97, 0xd7, 0x4b, 0x44, 0x3b, 0xac, 0x39, 0xa2, 0xc6,
29+
0x4d, 0x91, 0x00, 0x3e, 0xd1, 0xfa, 0x4a, 0x30, 0xc1, 0xd2, 0xc6, 0x5e, 0xfb, 0xac, 0x41, 0xf2,
30+
0x48, 0x13, 0x3c, 0x9b, 0x50, 0xfc, 0xa7, 0x24, 0x7a, 0x2e, 0x02, 0x63, 0xa3, 0xc6, 0x16, 0x25,
31+
0x51, 0x50, 0x78, 0x3e, 0x0f, 0xd8, 0x6e, 0x84, 0xa6, 0xec, 0x8d, 0x2d, 0x24, 0x47, 0xe5, 0xaf
32+
]);
33+
34+
const padNum = i => i.toString().padStart(4, '0');
35+
const kmsProviders = { local: { key: LOCAL_KEY } };
36+
const algorithm = 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic';
37+
const keyDocument = EJSON.parse(
38+
await fs.promises.readFile(path.join(__dirname, 'keyDocument.json'), 'utf8'),
39+
{ relaxed: false }
40+
);
41+
42+
function createEncryptedDocument(mongoCrypt) {
43+
const { _id: keyId } = keyDocument;
44+
45+
const encrypted = {};
46+
47+
for (let i = 0; i < fieldCount; i++) {
48+
const key = `key${padNum(i + 1)}`;
49+
const v = `value ${padNum(i + 1)}`;
50+
51+
const ctx = mongoCrypt.makeExplicitEncryptionContext(BSON.serialize({ v }), {
52+
keyId: keyId.buffer,
53+
algorithm
54+
});
55+
56+
if (ctx.state === NEED_MONGO_KEYS) {
57+
ctx.addMongoOperationResponse(BSON.serialize(keyDocument));
58+
ctx.finishMongoOperation();
59+
}
60+
61+
if (ctx.state !== READY) throw new Error(`not ready: [${ctx.state}] ${ctx.status.message}`);
62+
const result = ctx.finalize();
63+
if (ctx.state === ERROR) throw new Error(`error: [${ctx.state}] ${ctx.status.message}`);
64+
const { v: encryptedValue } = BSON.deserialize(result);
65+
encrypted[key] = encryptedValue;
66+
}
67+
68+
return encrypted;
69+
}
70+
71+
function measureMedianOpsPerSecOfDecrypt(mongoCrypt, toDecrypt, seconds) {
72+
let operationsPerSecond = [];
73+
74+
for (let second = 0; second < seconds; second++) {
75+
const startTime = performance.now();
76+
/** @type {number | null} */
77+
let operations = 0;
78+
79+
while (performance.now() - startTime < 1000) {
80+
const ctx = mongoCrypt.makeDecryptionContext(toDecrypt);
81+
if (ctx.state === NEED_MONGO_KEYS) {
82+
// We ran over a minute
83+
operations = null;
84+
break;
85+
}
86+
87+
if (ctx.state !== READY) throw new Error(`NOT READY: ${ctx.state}`);
88+
89+
ctx.finalize();
90+
operations += 1;
91+
}
92+
93+
if (operations != null) operationsPerSecond.push(operations);
94+
}
95+
96+
console.log('samples taken: ', operationsPerSecond.length);
97+
operationsPerSecond.sort((a, b) => a - b);
98+
return operationsPerSecond[Math.floor(operationsPerSecond.length / 2)];
99+
}
100+
101+
function main() {
102+
const hw = os.cpus();
103+
const ram = os.totalmem() / 1024 ** 3;
104+
const platform = { name: hw[0].model, cores: hw.length, ram: `${ram}GB` };
105+
106+
const systemInfo = () =>
107+
[
108+
`\n- cpu: ${platform.name}`,
109+
`- node: ${process.version}`,
110+
`- cores: ${platform.cores}`,
111+
`- arch: ${os.arch()}`,
112+
`- os: ${process.platform} (${os.release()})`,
113+
`- ram: ${platform.ram}\n`
114+
].join('\n');
115+
console.log(systemInfo());
116+
117+
console.log(
118+
`BenchmarkRunner is using ` +
119+
`libmongocryptVersion=${MongoCrypt.libmongocryptVersion}, ` +
120+
`warmupSecs=${warmupSecs}, ` +
121+
`testInSecs=${testInSecs}`
122+
);
123+
124+
const mongoCryptOptions = { kmsProviders: BSON.serialize(kmsProviders) };
125+
if (!BENCH_WITH_NATIVE_CRYPTO) mongoCryptOptions.cryptoCallbacks = cryptoCallbacks;
126+
if (cryptSharedLibPath) mongoCryptOptions.cryptSharedLibPath = cryptSharedLibPath;
127+
128+
const mongoCrypt = new MongoCrypt(mongoCryptOptions);
129+
130+
const encrypted = createEncryptedDocument(mongoCrypt);
131+
const toDecrypt = BSON.serialize(encrypted);
132+
133+
const created_at = new Date();
134+
135+
// warmup
136+
measureMedianOpsPerSecOfDecrypt(mongoCrypt, toDecrypt, warmupSecs);
137+
// bench
138+
const medianOpsPerSec = measureMedianOpsPerSecOfDecrypt(mongoCrypt, toDecrypt, testInSecs);
139+
140+
const completed_at = new Date();
141+
142+
console.log(`Decrypting 1500 fields median ops/sec : ${medianOpsPerSec}`);
143+
144+
const perfSend = {
145+
info: { test_name: 'javascript_decrypt_1500' },
146+
created_at,
147+
completed_at,
148+
artifacts: [],
149+
metrics: [{ name: 'medianOpsPerSec', type: 'THROUGHPUT', value: medianOpsPerSec }],
150+
sub_tests: []
151+
};
152+
console.log(perfSend);
153+
}
154+
155+
main();

test/benchmarks/crypto_callbacks.mjs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import crypto from 'node:crypto';
2+
3+
function makeAES256Hook(method, mode) {
4+
return function (key, iv, input, output) {
5+
let result;
6+
try {
7+
const cipher = crypto[method](mode, key, iv);
8+
cipher.setAutoPadding(false);
9+
result = cipher.update(input);
10+
const final = cipher.final();
11+
if (final.length > 0) {
12+
result = Buffer.concat([result, final]);
13+
}
14+
} catch (e) {
15+
return e;
16+
}
17+
result.copy(output);
18+
return result.length;
19+
};
20+
}
21+
22+
function randomHook(buffer, count) {
23+
try {
24+
crypto.randomFillSync(buffer, 0, count);
25+
} catch (e) {
26+
return e;
27+
}
28+
return count;
29+
}
30+
31+
function sha256Hook(input, output) {
32+
let result;
33+
try {
34+
result = crypto.createHash('sha256').update(input).digest();
35+
} catch (e) {
36+
return e;
37+
}
38+
result.copy(output);
39+
return result.length;
40+
}
41+
42+
function makeHmacHook(algorithm) {
43+
return (key, input, output) => {
44+
let result;
45+
try {
46+
result = crypto.createHmac(algorithm, key).update(input).digest();
47+
} catch (e) {
48+
return e;
49+
}
50+
result.copy(output);
51+
return result.length;
52+
};
53+
}
54+
55+
function signRsaSha256Hook(key, input, output) {
56+
let result;
57+
try {
58+
const signer = crypto.createSign('sha256WithRSAEncryption');
59+
const privateKey = Buffer.from(
60+
`-----BEGIN PRIVATE KEY-----\n${key.toString('base64')}\n-----END PRIVATE KEY-----\n`
61+
);
62+
result = signer.update(input).end().sign(privateKey);
63+
} catch (e) {
64+
return e;
65+
}
66+
result.copy(output);
67+
return result.length;
68+
}
69+
70+
const aes256CbcEncryptHook = makeAES256Hook('createCipheriv', 'aes-256-cbc');
71+
const aes256CbcDecryptHook = makeAES256Hook('createDecipheriv', 'aes-256-cbc');
72+
const aes256CtrEncryptHook = makeAES256Hook('createCipheriv', 'aes-256-ctr');
73+
const aes256CtrDecryptHook = makeAES256Hook('createDecipheriv', 'aes-256-ctr');
74+
const hmacSha512Hook = makeHmacHook('sha512');
75+
const hmacSha256Hook = makeHmacHook('sha256');
76+
77+
export const cryptoCallbacks = {
78+
randomHook,
79+
sha256Hook,
80+
signRsaSha256Hook,
81+
aes256CbcEncryptHook,
82+
aes256CbcDecryptHook,
83+
aes256CtrEncryptHook,
84+
aes256CtrDecryptHook,
85+
hmacSha512Hook,
86+
hmacSha256Hook
87+
};

test/benchmarks/keyDocument.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"_id": {
3+
"$binary": {
4+
"base64": "YWFhYWFhYWFhYWFhYWFhYQ==",
5+
"subType": "04"
6+
}
7+
},
8+
"keyMaterial": {
9+
"$binary": {
10+
"base64": "ACR7Hm33dDOAAD7l2ubZhSpSUWK8BkALUY+qW3UgBAEcTV8sBwZnaAWnzDsmrX55dgmYHWfynDlJogC/e33u6pbhyXvFTs5ow9OLCuCWBJ39T/Ivm3kMaZJybkejY0V+uc4UEdHvVVz/SbitVnzs2WXdMGmo1/HmDRrxGYZjewFslquv8wtUHF5pyB+QDlQBd/al9M444/8bJZFbMSmtIg==",
11+
"subType": "00"
12+
}
13+
},
14+
"creationDate": {
15+
"$date": "2023-08-21T14:28:20.875Z"
16+
},
17+
"updateDate": {
18+
"$date": "2023-08-21T14:28:20.875Z"
19+
},
20+
"status": 0,
21+
"masterKey": {
22+
"provider": "local"
23+
}
24+
}

0 commit comments

Comments
 (0)