Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/workers optimised #256

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 118 additions & 20 deletions lib/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@
"lint": "eslint ./src/**/*.ts ./tdf3/**/*.ts ./tests/**/*.ts",
"prepack": "npm run build",
"test": "npm run build && npm run test:mocha && npm run test:wtr && npm run test:browser && npm run coverage:merge",
"test:browser": "node dist/web/tests/server.js & trap \"node dist/web/tests/stopServer.js\" EXIT; npx webpack --config webpack.test.config.cjs && npx karma start karma.conf.cjs",
"test:mocha": "node dist/web/tests/server.js & trap \"node dist/web/tests/stopServer.js\" EXIT; c8 --exclude=\"dist/web/tests/\" --exclude=\"dist/web/tdf3/src/utils/aws-lib-storage/\" --exclude=\"dist/web/tests/**/*\" --report-dir=./coverage/mocha mocha 'dist/web/tests/mocha/**/*.spec.js' --file dist/web/tests/mocha/setup.js && npx c8 report --reporter=json --report-dir=./coverage/mocha",
"test:browser": "node --require ./preServer.js dist/web/tests/server.js & trap \"node dist/web/tests/stopServer.js\" EXIT; npx webpack --config webpack.test.config.cjs && npx karma start karma.conf.cjs",
"test:mocha": "node --require ./preServer.js dist/web/tests/server.js & trap \"node dist/web/tests/stopServer.js\" EXIT; c8 --exclude=\"dist/web/tests/\" --exclude=\"dist/web/tdf3/src/utils/aws-lib-storage/\" --exclude=\"dist/web/tests/**/*\" --report-dir=./coverage/mocha mocha 'dist/web/tests/mocha/**/*.spec.js' --file dist/web/tests/mocha/setup.js && npx c8 report --reporter=json --report-dir=./coverage/mocha",
"test:wtr": "web-test-runner",
"watch": "(trap 'kill 0' SIGINT; npm run build && (npm run build:watch & npm run test -- --watch))"
},
Expand All @@ -69,6 +69,7 @@
"dpop": "^1.2.0",
"events": "^3.3.0",
"jose": "^4.14.4",
"p-limit": "^5.0.0",
"streamsaver": "^2.0.6",
"uuid": "~9.0.0"
},
Expand Down Expand Up @@ -98,6 +99,7 @@
"colors": "^1.4.0",
"eslint": "^8.46.0",
"eslint-config-prettier": "^8.9.0",
"fetch-blob": "^4.0.0",
"glob": "^10.3.3",
"jsdom": "^22.1.0",
"karma": "^6.4.2",
Expand Down
1 change: 1 addition & 0 deletions lib/preServer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import('./dist/web/tests/mocha/setup.js').catch(e => console.error(e));
75 changes: 75 additions & 0 deletions lib/tdf3/src/crypto/decrypt-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { TdfDecryptError } from '../../../src/errors.js';

const maxWorkers = navigator?.hardwareConcurrency || 4; // save fallback number

interface DecryptData {
key: CryptoKey;
encryptedPayload: ArrayBuffer;
algo: AesCbcParams | AesGcmParams;
}

const workerScript = `
self.onmessage = async (event) => {
const { key, encryptedPayload, algo } = event.data;

try {
const decryptedData = await crypto.subtle.decrypt(algo, key, encryptedPayload);
self.postMessage({ success: true, data: decryptedData });
} catch (error) {
self.postMessage({ success: false, error: error.message });
}
};
`;

const workerBlob = new Blob([workerScript], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(workerBlob);
const workersArray: Worker[] = Array.from({ length: maxWorkers }, () => new Worker(workerUrl));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we'd be doing this at build time, not runtime. Do we have any methods for doing this? I can't find a good way to do this without doing a two-pass build.

Copy link
Contributor Author

@ivanovSPvirtru ivanovSPvirtru Nov 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code gets executate in the instance when we import entry point of sdk to any frontend app. Because this file eventually gets imported from entry point further down the nodes. And imports are synchronous. ANd code above too.

I dont think it could be initiated earlier that that in js world tbh

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally the bundler will do all this work for you can you can treat this like a dynamic import almost. It looks like both vitejs and webpack 5 support this:

https://vitejs.dev/guide/features.html#web-workers

https://webpack.js.org/guides/web-workers/

I guess the question is are they smart enough to work through all the intermediate translations we do. Most notably, virtru-sdk uses webpack5 to bundle, and then secure-lib.js loads that (webpack 5 again) as does secure-share (webpack 5 as well), but our only test library here uses vite.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes i tried it and i had constant problems on a app side. Ive tuned it on Virtru-sdk side but after webpack on secure share it had constant errors. Spend a wile on that.

Thats why i decided to go with this aproach. Its makes sdk independent of bundlers issues and doesnt slow down performance, since its a singleton instance that initializes workers as soon as sdk imported it. So when youll start download stuff its already be there.


interface WorkersQueue {
freeWorkers: Worker[];
resolvers: ((worker: Worker) => void)[];
push: (worker: Worker) => void;
pop: () => Promise<Worker>;
}

export const workersQueue: WorkersQueue = {
freeWorkers: [...workersArray],
resolvers: [],

push(worker: Worker) {
const resolve = this.resolvers.shift();
if (typeof resolve === 'function') {
resolve(worker);
} else {
this.freeWorkers.push(worker);
}
},

pop(): Promise<Worker> {
return new Promise((resolve) => {
const worker = this.freeWorkers.shift();
if (worker instanceof Worker) {
resolve(worker);
} else {
this.resolvers.push(resolve);
}
});
},
};

export async function decrypt(data: DecryptData): Promise<ArrayBuffer> {
const worker: Worker = await workersQueue.pop();
return await new Promise((resolve, reject) => {
worker.onmessage = (event) => {
const { success, data, error } = event.data;
workersQueue.push(worker);
if (success) {
resolve(data as ArrayBuffer);
} else {
reject(new TdfDecryptError(error));
}
};

worker.postMessage(data);
});
}
29 changes: 18 additions & 11 deletions lib/tdf3/src/crypto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { Algorithms } from '../ciphers/index.js';
import { decrypt as workerDecrypt } from './decrypt-worker.js';
import { Binary } from '../binary.js';
import {
CryptoService,
Expand Down Expand Up @@ -274,17 +275,23 @@ async function _doDecrypt(
const importedKey = await _importKey(key, algoDomString);
algoDomString.iv = iv.asArrayBuffer();

const decrypted = await crypto.subtle
.decrypt(algoDomString, importedKey, payloadBuffer)
// Catching this error so we can specifically check for OperationError
.catch((err) => {
if (err.name === 'OperationError') {
throw new TdfDecryptError(err);
}

throw err;
});
return { payload: Binary.fromArrayBuffer(decrypted) };
try {
const decrypted = navigator?.hardwareConcurrency
? await workerDecrypt({
key: importedKey,
encryptedPayload: payloadBuffer,
algo: algoDomString,
})
: await crypto.subtle.decrypt(algoDomString, importedKey, payloadBuffer);

return { payload: Binary.fromArrayBuffer(decrypted) };
} catch (err) {
if (err.name === 'OperationError') {
throw new TdfDecryptError(err);
}

throw err;
}
}

function _importKey(key: Binary, algorithm: AesCbcParams | AesGcmParams) {
Expand Down
Loading
Loading