From ab68d4b9974cfb310b66650170dcb289a76e339a Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 9 Feb 2016 17:01:48 +0700 Subject: [PATCH] Support transferable objects in web worker for zero copy support --- src/config/config.js | 1 + src/util.js | 29 +++++++++++++++++++++++++++++ src/worker/async_proxy.js | 5 +++-- src/worker/worker.js | 4 ++-- test/general/openpgp.js | 31 ++++++++++++++++++++++++++++++- 5 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/config/config.js b/src/config/config.js index fd40ef02..78c97647 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -41,6 +41,7 @@ export default { ignore_mdc_error: false, // fail on decrypt if message is not integrity protected rsa_blinding: true, useNative: true, // use native node.js crypto and Web Crypto apis (if available) + zeroCopy: false, // use transferable objects between the Web Worker and main thread debug: false, show_version: true, show_comment: true, diff --git a/src/util.js b/src/util.js index 4e429b38..7de64a8f 100644 --- a/src/util.js +++ b/src/util.js @@ -54,6 +54,35 @@ export default { return / $/.test(data); }, + /** + * Get transferable objects to pass buffers with zero copy (similar to "pass by reference" in C++) + * See: https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage + * @param {Object} options the options object to be passed to the web worker + * @return {Array} an array of binary data to be passed + */ + getTransferables: function(obj) { + if (config.zeroCopy && Object.prototype.isPrototypeOf(obj)) { + const transferables = []; + this.collectBuffers(obj, transferables); + return transferables; + } + }, + + collectBuffers: function(obj, collection) { + if (!obj) { + return; + } + if (this.isUint8Array(obj) && collection.indexOf(obj.buffer) === -1) { + collection.push(obj.buffer); + return; + } + if (Object.prototype.isPrototypeOf(obj)) { + for (let key in obj) { // recursively search all children + this.collectBuffers(obj[key], collection); + } + } + }, + readNumber: function (bytes) { var n = 0; diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index 0515a0c3..d989fa61 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -26,6 +26,7 @@ 'use strict'; +import util from '../util.js'; import crypto from '../crypto'; import packet from '../packet'; import * as key from '../key.js'; @@ -100,7 +101,7 @@ AsyncProxy.prototype.onMessage = function(event) { */ AsyncProxy.prototype.seedRandom = function(size) { const buf = this.getRandomBuffer(size); - this.worker.postMessage({ event:'seed-random', buf }); + this.worker.postMessage({ event:'seed-random', buf }, util.getTransferables.call(util, buf)); }; /** @@ -135,7 +136,7 @@ AsyncProxy.prototype.terminate = function() { AsyncProxy.prototype.delegate = function(method, options) { return new Promise((resolve, reject) => { // clone packets (for web worker structured cloning algorithm) - this.worker.postMessage({ event:method, options:clonePackets(options) }); + this.worker.postMessage({ event:method, options:clonePackets(options) }, util.getTransferables.call(util, options)); // remember to handle parsing cloned packets from worker this.tasks.push({ resolve: data => resolve(parseClonedPackets(data, method)), reject }); diff --git a/src/worker/worker.js b/src/worker/worker.js index a79e8ba5..9e136e39 100644 --- a/src/worker/worker.js +++ b/src/worker/worker.js @@ -163,7 +163,7 @@ function clonePackets(data) { function response(event) { if (window.openpgp.crypto.random.randomBuffer.size < MIN_SIZE_RANDOM_BUFFER) { - postMessage({event: 'request-seed'}); + self.postMessage({event: 'request-seed'}); } - postMessage(event); + self.postMessage(event, window.openpgp.util.getTransferables.call(window.openpgp.util, event.data)); } \ No newline at end of file diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 940d2c01..ee28a7f4 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -335,7 +335,7 @@ describe('OpenPGP.js public api tests', function() { var password1 = 'I am a password'; var password2 = 'I am another password'; - var privateKey, publicKey; + var privateKey, publicKey, zeroCopyVal; beforeEach(function() { publicKey = openpgp.key.readArmored(pub_key); @@ -344,6 +344,11 @@ describe('OpenPGP.js public api tests', function() { privateKey = openpgp.key.readArmored(priv_key); expect(privateKey.keys).to.have.length(1); expect(privateKey.err).to.not.exist; + zeroCopyVal = openpgp.config.zeroCopy; + }); + + afterEach(function() { + openpgp.config.zeroCopy = zeroCopyVal; }); it('Decrypting key with wrong passphrase returns false', function () { @@ -608,6 +613,30 @@ describe('OpenPGP.js public api tests', function() { done(); }); }); + + it('should encrypt and decrypt with binary data and transferable objects', function(done) { + openpgp.config.zeroCopy = true; // activate transferable objects + var encOpt = { + data: new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01]), + passwords: [password1, password2], + armor: false + }; + var decOpt = { + sessionKey: password2, + format: 'binary' + }; + openpgp.encrypt(encOpt).then(function(encrypted) { + decOpt.message = encrypted.message; + return openpgp.decrypt(decOpt); + }).then(function(decrypted) { + if (openpgp.getWorker()) { + expect(encOpt.data.byteLength).to.equal(0); // transfered buffer should be empty + } + expect(decrypted.data).to.deep.equal(new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01])); + openpgp.config.zeroCopy = false; + done(); + }); + }); }); }