343 lines
15 KiB
JavaScript
343 lines
15 KiB
JavaScript
/* eslint-disable no-console */
|
|
const assert = require('assert');
|
|
const path = require('path');
|
|
const { writeFileSync, unlinkSync } = require('fs');
|
|
const { fork } = require('child_process');
|
|
const openpgp = require('../..');
|
|
|
|
/**
|
|
* Benchmark max memory usage recorded during execution of the given function.
|
|
* This spawns a new v8 instance and runs the code there in isolation, to avoid interference between tests.
|
|
* @param {Funtion} function to benchmark (can be async)
|
|
* @returns {NodeJS.MemoryUsage} memory usage snapshot with max RSS (sizes in bytes)
|
|
*/
|
|
const benchmark = async function(fn) {
|
|
const tmpFileName = path.join(__dirname, 'tmp.js');
|
|
// the code to execute must be written to a file
|
|
writeFileSync(tmpFileName, `
|
|
const assert = require('assert');
|
|
const openpgp = require('../..');
|
|
let maxMemoryComsumption;
|
|
let activeSampling = false;
|
|
|
|
function sampleOnce() {
|
|
const memUsage = process.memoryUsage();
|
|
if (!maxMemoryComsumption || memUsage.rss > maxMemoryComsumption.rss) {
|
|
maxMemoryComsumption = memUsage;
|
|
}
|
|
}
|
|
|
|
function samplePeriodically() {
|
|
setImmediate(() => {
|
|
sampleOnce();
|
|
activeSampling && samplePeriodically();
|
|
});
|
|
}
|
|
|
|
// main body
|
|
(async () => {
|
|
maxMemoryComsumption = null;
|
|
activeSampling = true;
|
|
samplePeriodically();
|
|
await (${fn.toString()})();
|
|
// setImmediate is run at the end of the event loop, so we need to manually collect the latest sample
|
|
sampleOnce();
|
|
process.send(maxMemoryComsumption);
|
|
process.exit(); // child process doesn't exit otherwise
|
|
})();
|
|
`);
|
|
|
|
const maxMemoryComsumption = await new Promise((resolve, reject) => {
|
|
const child = fork(tmpFileName);
|
|
child.on('message', function (message) {
|
|
resolve(message);
|
|
});
|
|
child.on('error', function (err) {
|
|
reject(err);
|
|
});
|
|
});
|
|
|
|
unlinkSync(tmpFileName);
|
|
return maxMemoryComsumption;
|
|
};
|
|
|
|
const onError = err => {
|
|
console.error('The memory benchmark tests failed by throwing the following error:');
|
|
console.error(err);
|
|
// eslint-disable-next-line no-process-exit
|
|
process.exit(1);
|
|
};
|
|
|
|
class MemoryBenchamrkSuite {
|
|
constructor() {
|
|
this.tests = [];
|
|
}
|
|
|
|
add(name, fn) {
|
|
this.tests.push({ name, fn });
|
|
}
|
|
|
|
async run() {
|
|
const stats = [];
|
|
for (const { name, fn } of this.tests) {
|
|
const memoryUsage = await benchmark(fn).catch(onError);
|
|
// convert values to MB
|
|
Object.entries(memoryUsage).forEach(([name, value]) => {
|
|
memoryUsage[name] = (value / 1024 / 1024).toFixed(2);
|
|
});
|
|
const { rss, ...usageDetails } = memoryUsage;
|
|
// raw entry format accepted by github-action-pull-request-benchmark
|
|
stats.push({
|
|
name,
|
|
value: rss,
|
|
range: Object.entries(usageDetails).map(([name, value]) => `${name}: ${value}`).join(', '),
|
|
unit: 'MB',
|
|
biggerIsBetter: false
|
|
});
|
|
}
|
|
return stats;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Memory usage tests.
|
|
* All the necessary variables must be declared inside the test function.
|
|
*/
|
|
(async () => {
|
|
const suite = new MemoryBenchamrkSuite();
|
|
|
|
suite.add('empty test (baseline)', () => {});
|
|
|
|
suite.add('openpgp.encrypt/decrypt (CFB, binary)', async () => {
|
|
const ONE_MEGABYTE = 1000000;
|
|
const passwords = 'password';
|
|
const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
|
|
const plaintextMessage = await openpgp.createMessage({ binary: new Uint8Array(ONE_MEGABYTE) });
|
|
|
|
const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config });
|
|
const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage });
|
|
assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket);
|
|
await openpgp.decrypt({ message: encryptedMessage, passwords, config });
|
|
});
|
|
|
|
suite.add('openpgp.encrypt/decrypt (CFB, text)', async () => {
|
|
const ONE_MEGABYTE = 1000000;
|
|
const passwords = 'password';
|
|
const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
|
|
const plaintextMessage = await openpgp.createMessage({ text: 'a'.repeat(ONE_MEGABYTE / 2) }); // two bytes per character
|
|
|
|
const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config });
|
|
const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage });
|
|
assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket);
|
|
await openpgp.decrypt({ message: encryptedMessage, passwords, config });
|
|
});
|
|
|
|
suite.add('openpgp.encrypt/decrypt (AEAD, binary)', async () => {
|
|
const ONE_MEGABYTE = 1000000;
|
|
const passwords = 'password';
|
|
const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
|
|
const plaintextMessage = await openpgp.createMessage({ binary: new Uint8Array(ONE_MEGABYTE) });
|
|
|
|
const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config });
|
|
const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage });
|
|
assert.ok(encryptedMessage.packets[1] instanceof openpgp.AEADEncryptedDataPacket);
|
|
await openpgp.decrypt({ message: encryptedMessage, passwords, config });
|
|
});
|
|
|
|
suite.add('openpgp.encrypt/decrypt (AEAD, text)', async () => {
|
|
const ONE_MEGABYTE = 1000000;
|
|
const passwords = 'password';
|
|
const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
|
|
const plaintextMessage = await openpgp.createMessage({ text: 'a'.repeat(ONE_MEGABYTE / 2) }); // two bytes per character
|
|
|
|
const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config });
|
|
const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage });
|
|
assert.ok(encryptedMessage.packets[1] instanceof openpgp.AEADEncryptedDataPacket);
|
|
await openpgp.decrypt({ message: encryptedMessage, passwords, config });
|
|
});
|
|
|
|
// streaming tests
|
|
suite.add('openpgp.encrypt/decrypt (CFB, binary, with streaming)', async () => {
|
|
const ONE_MEGABYTE = 1000000;
|
|
function* largeDataGenerator({ chunk, numberOfChunks }) {
|
|
for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) {
|
|
yield chunk;
|
|
}
|
|
}
|
|
|
|
const passwords = 'password';
|
|
const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
|
|
const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: new Uint8Array(ONE_MEGABYTE), numberOfChunks: 1 }));
|
|
const plaintextMessage = await openpgp.createMessage({ binary: inputStream });
|
|
assert(plaintextMessage.fromStream);
|
|
|
|
const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config });
|
|
const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage });
|
|
assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket);
|
|
const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config });
|
|
// read out output stream to trigger decryption
|
|
await new Promise(resolve => {
|
|
decryptedData.pipe(require('fs').createWriteStream('/dev/null'));
|
|
decryptedData.on('end', resolve);
|
|
});
|
|
});
|
|
|
|
suite.add('openpgp.encrypt/decrypt (CFB, text, with streaming)', async () => {
|
|
const ONE_MEGABYTE = 1000000;
|
|
function* largeDataGenerator({ chunk, numberOfChunks }) {
|
|
for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) {
|
|
yield chunk;
|
|
}
|
|
}
|
|
|
|
const passwords = 'password';
|
|
const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
|
|
const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 1 }));
|
|
const plaintextMessage = await openpgp.createMessage({ text: inputStream });
|
|
assert(plaintextMessage.fromStream);
|
|
|
|
const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config });
|
|
const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage });
|
|
assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket);
|
|
const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config });
|
|
// read out output stream to trigger decryption
|
|
await new Promise(resolve => {
|
|
decryptedData.pipe(require('fs').createWriteStream('/dev/null'));
|
|
decryptedData.on('end', resolve);
|
|
});
|
|
});
|
|
|
|
suite.add('openpgp.encrypt/decrypt (AEAD, binary, with streaming)', async () => {
|
|
const ONE_MEGABYTE = 1000000;
|
|
function* largeDataGenerator({ chunk, numberOfChunks }) {
|
|
for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) {
|
|
yield chunk;
|
|
}
|
|
}
|
|
|
|
const passwords = 'password';
|
|
const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
|
|
const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: new Uint8Array(ONE_MEGABYTE), numberOfChunks: 1 }));
|
|
const plaintextMessage = await openpgp.createMessage({ binary:inputStream });
|
|
assert(plaintextMessage.fromStream);
|
|
|
|
const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config });
|
|
const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage });
|
|
assert.ok(encryptedMessage.packets[1] instanceof openpgp.AEADEncryptedDataPacket);
|
|
const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config });
|
|
// read out output stream to trigger decryption
|
|
await new Promise(resolve => {
|
|
decryptedData.pipe(require('fs').createWriteStream('/dev/null'));
|
|
decryptedData.on('end', resolve);
|
|
});
|
|
});
|
|
|
|
suite.add('openpgp.encrypt/decrypt (AEAD, text, with streaming)', async () => {
|
|
const ONE_MEGABYTE = 1000000;
|
|
function* largeDataGenerator({ chunk, numberOfChunks }) {
|
|
for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) {
|
|
yield chunk;
|
|
}
|
|
}
|
|
|
|
const passwords = 'password';
|
|
const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
|
|
const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 1 }));
|
|
const plaintextMessage = await openpgp.createMessage({ text: inputStream });
|
|
assert(plaintextMessage.fromStream);
|
|
|
|
const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config });
|
|
const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage });
|
|
assert.ok(encryptedMessage.packets[1] instanceof openpgp.AEADEncryptedDataPacket);
|
|
const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config });
|
|
// read out output stream to trigger decryption
|
|
await new Promise(resolve => {
|
|
decryptedData.pipe(require('fs').createWriteStream('/dev/null'));
|
|
decryptedData.on('end', resolve);
|
|
});
|
|
});
|
|
|
|
suite.add('openpgp.encrypt/decrypt (CFB, text @ 10MB, with streaming)', async () => {
|
|
const ONE_MEGABYTE = 1000000;
|
|
function* largeDataGenerator({ chunk, numberOfChunks }) {
|
|
for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) {
|
|
yield chunk;
|
|
}
|
|
}
|
|
|
|
const passwords = 'password';
|
|
const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
|
|
const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 }));
|
|
const plaintextMessage = await openpgp.createMessage({ text: inputStream });
|
|
assert(plaintextMessage.fromStream);
|
|
|
|
const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config });
|
|
const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage });
|
|
assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket);
|
|
const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config });
|
|
// read out output stream to trigger decryption
|
|
await new Promise(resolve => {
|
|
decryptedData.pipe(require('fs').createWriteStream('/dev/null'));
|
|
decryptedData.on('end', resolve);
|
|
});
|
|
});
|
|
|
|
suite.add('openpgp.encrypt/decrypt (CFB, text @ 10MB, with unauthenticated streaming)', async () => {
|
|
const ONE_MEGABYTE = 1000000;
|
|
function* largeDataGenerator({ chunk, numberOfChunks }) {
|
|
for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) {
|
|
yield chunk;
|
|
}
|
|
}
|
|
|
|
const passwords = 'password';
|
|
const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
|
|
const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 }));
|
|
const plaintextMessage = await openpgp.createMessage({ text: inputStream });
|
|
assert(plaintextMessage.fromStream);
|
|
|
|
const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config });
|
|
const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage });
|
|
assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket);
|
|
const { data: decryptedData } = await openpgp.decrypt({
|
|
message: encryptedMessage,
|
|
passwords,
|
|
config: { ...config, allowUnauthenticatedStream: true }
|
|
});
|
|
// read out output stream to trigger decryption
|
|
await new Promise(resolve => {
|
|
decryptedData.pipe(require('fs').createWriteStream('/dev/null'));
|
|
decryptedData.on('end', resolve);
|
|
});
|
|
});
|
|
|
|
suite.add('openpgp.encrypt/decrypt (AEAD, text @ 10MB, with streaming)', async () => {
|
|
const ONE_MEGABYTE = 1000000;
|
|
function* largeDataGenerator({ chunk, numberOfChunks }) {
|
|
for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) {
|
|
yield chunk;
|
|
}
|
|
}
|
|
|
|
const passwords = 'password';
|
|
const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
|
|
const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 }));
|
|
const plaintextMessage = await openpgp.createMessage({ text: inputStream });
|
|
assert(plaintextMessage.fromStream);
|
|
|
|
const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config });
|
|
const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage });
|
|
assert.ok(encryptedMessage.packets[1] instanceof openpgp.AEADEncryptedDataPacket);
|
|
const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config });
|
|
// read out output stream to trigger decryption
|
|
await new Promise(resolve => {
|
|
decryptedData.pipe(require('fs').createWriteStream('/dev/null'));
|
|
decryptedData.on('end', resolve);
|
|
});
|
|
});
|
|
|
|
const stats = await suite.run();
|
|
// Print JSON stats to stdout
|
|
console.log(JSON.stringify(stats, null, 4));
|
|
})();
|