CyberChef/src/web/WorkerWaiter.mjs

340 lines
9.9 KiB
JavaScript
Raw Normal View History

2018-05-15 17:36:45 +00:00
/**
* @author n1474335 [n1474335@gmail.com]
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
2018-05-15 17:36:45 +00:00
* @license Apache-2.0
*/
import ChefWorker from "worker-loader?inline&fallback=false!../core/ChefWorker";
/**
* Waiter to handle conversations with the ChefWorker.
*/
class WorkerWaiter {
/**
* WorkerWaiter constructor.
*
* @param {App} app - The main view object for CyberChef
2018-05-15 17:36:45 +00:00
* @param {Manager} manager - The CyberChef event manager.
*/
constructor(app, manager) {
this.app = app;
this.manager = manager;
this.callbacks = {};
this.callbackID = 0;
this.pendingInputs = [];
this.runningWorkers = 0;
this.chefWorkers = [];
this.outputs = [];
2018-05-15 17:36:45 +00:00
}
/**
* Sets up a pool of ChefWorkers to be used for baking
2018-05-15 17:36:45 +00:00
*/
setupChefWorkers() {
const threads = navigator.hardwareConcurrency || 4; // Default to 4
for (let i = 0; i < threads; i++) {
const newWorker = new ChefWorker();
newWorker.addEventListener("message", this.handleChefMessage.bind(this));
let docURL = document.location.href.split(/[#?]/)[0];
const index = docURL.lastIndexOf("/");
if (index > 0) {
docURL = docURL.substring(0, index);
}
newWorker.postMessage({"action": "docURL", "data": docURL});
newWorker.postMessage({"action": "inputNum", "data": 0});
this.chefWorkers.push({
worker: newWorker,
inputNum: 0
});
2018-05-15 17:36:45 +00:00
}
}
/**
* Handler for messages sent back by the ChefWorker.
*
* @param {MessageEvent} e
*/
handleChefMessage(e) {
const r = e.data;
log.debug("Receiving '" + r.action + "' from ChefWorker");
switch (r.action) {
case "bakeComplete":
this.runningWorkers -= 1;
this.outputs.push({
data: r.data,
inputNum: r.data.inputNum
});
if (this.pendingInputs.length > 0) {
log.debug("Bake complete. Baking next input");
this.bakeNextInput(r.data.inputNum);
} else if (this.runningWorkers <= 0) {
this.runningWorkers = 0;
this.recipeConfig = undefined;
this.options = undefined;
this.progress = undefined;
this.step = undefined;
this.bakingComplete();
}
2018-05-15 17:36:45 +00:00
break;
case "bakeError":
this.runningWorkers -= 1;
2018-05-15 17:36:45 +00:00
this.app.handleError(r.data);
this.setBakingStatus(false);
break;
case "dishReturned":
this.callbacks[r.data.id](r.data);
break;
case "silentBakeComplete":
break;
case "workerLoaded":
this.app.workerLoaded = true;
log.debug("ChefWorker loaded");
this.app.loaded();
break;
case "statusMessage":
this.manager.output.setStatusMsg(r.data);
break;
case "optionUpdate":
log.debug(`Setting ${r.data.option} to ${r.data.value}`);
this.app.options[r.data.option] = r.data.value;
break;
case "setRegisters":
this.manager.recipe.setRegisters(r.data.opIndex, r.data.numPrevRegisters, r.data.registers);
break;
case "highlightsCalculated":
this.manager.highlighter.displayHighlights(r.data.pos, r.data.direction);
break;
default:
log.error("Unrecognised message from ChefWorker", e);
break;
}
}
/**
* Updates the UI to show if baking is in process or not.
*
* @param {bakingStatus}
*/
setBakingStatus(bakingStatus) {
this.app.baking = bakingStatus;
this.manager.output.toggleLoader(bakingStatus);
}
/**
* Calcels the current bake by terminating and removing all ChefWorkers,
* and creating a new one
2018-05-15 17:36:45 +00:00
*/
cancelBake() {
for (let i = this.chefWorkers.length - 1; i >= 0; i--) {
this.chefWorkers[i].worker.terminate();
this.chefWorkers.pop();
}
this.setupChefWorkers();
2018-05-15 17:36:45 +00:00
this.setBakingStatus(false);
this.manager.controls.showStaleIndicator();
}
/**
* Handler for completed bakes
*/
bakingComplete() {
this.setBakingStatus(false);
if (this.pendingInputs.length !== 0) return;
for (let i = 0; i < this.outputs.length; i++) {
if (this.outputs[i].error) {
this.app.handleError(this.outputs[i].error);
}
}
this.app.progress = this.outputs[0].data.progress;
this.app.dish = this.outputs[0].data.dish;
this.manager.recipe.updateBreakpointIndicator(this.app.progress);
this.manager.output.multiSet(this.outputs);
log.debug("--- Bake complete ---");
}
/**
* Handler for completed bakes
2018-05-15 17:36:45 +00:00
*
* @param {Object} response
*/
bakingCompleteOld(response) {
2018-05-15 17:36:45 +00:00
this.setBakingStatus(false);
if (!response) return;
if (response.error) {
this.app.handleError(response.error);
}
this.app.progress = response.progress;
this.app.dish = response.dish;
this.manager.recipe.updateBreakpointIndicator(response.progress);
this.manager.output.set(response.result, response.type, response.duration);
log.debug("--- Bake complete ---");
}
/**
* Asks the ChefWorker to bake the current input using the current recipe.
*
* @param {string | Array} input
2018-05-15 17:36:45 +00:00
* @param {Object[]} recipeConfig
* @param {Object} options
* @param {number} progress
* @param {boolean} step
*/
bake(input, recipeConfig, options, progress, step) {
this.setBakingStatus(true);
this.recipeConfig = recipeConfig;
this.options = options;
this.progress = progress;
this.step = step;
this.outputs = [];
if (typeof input === "string") {
input = [{
2018-05-15 17:36:45 +00:00
input: input,
inputNum: 0
}];
}
const initialInputs = input.slice(0, this.chefWorkers.length);
this.pendingInputs = input.slice(this.chefWorkers.length, input.length);
this.runningWorkers = 0;
for (let i = 0; i < initialInputs.length; i++) {
this.runningWorkers += 1;
this.chefWorkers[i].inputNum = initialInputs[i].inputNum;
this.chefWorkers[i].worker.postMessage({
action: "bake",
data: {
input: initialInputs[i].input,
recipeConfig: recipeConfig,
options: options,
progress: progress,
step: step,
inputNum: initialInputs[i].inputNum
}
});
}
}
/**
*
* @param inputNum
*/
bakeNextInput(inputNum) {
this.runningWorkers += 1;
const nextInput = this.pendingInputs.pop();
for (let i = 0; i < this.chefWorkers.length; i++) {
if (this.chefWorkers[i].inputNum === inputNum) {
this.chefWorkers[i].inputNum = nextInput.inputNum;
this.chefWorkers[i].worker.postMessage({
action: "bake",
data: {
input: nextInput.input,
recipeConfig: this.recipeConfig,
options: this.options,
progress: this.progress,
step: this.step,
inputNum: nextInput.inputNum
}
});
2018-05-15 17:36:45 +00:00
}
}
2018-05-15 17:36:45 +00:00
}
/**
* Asks the ChefWorker to run a silent bake, forcing the browser to load and cache all the relevant
* JavaScript code needed to do a real bake.
*
* @param {Object[]} [recipeConfig]
*/
silentBake(recipeConfig) {
this.chefWorkers[0].worker.postMessage({
2018-05-15 17:36:45 +00:00
action: "silentBake",
data: {
recipeConfig: recipeConfig
}
});
}
/**
* Asks the ChefWorker to calculate highlight offsets if possible.
*
* @param {Object[]} recipeConfig
* @param {string} direction
* @param {Object} pos - The position object for the highlight.
* @param {number} pos.start - The start offset.
* @param {number} pos.end - The end offset.
*/
highlight(recipeConfig, direction, pos) {
this.chefWorkers[0].postMessage({
2018-05-15 17:36:45 +00:00
action: "highlight",
data: {
recipeConfig: recipeConfig,
direction: direction,
pos: pos
}
});
}
/**
* Asks the ChefWorker to return the dish as the specified type
*
* @param {Dish} dish
* @param {string} type
* @param {Function} callback
*/
getDishAs(dish, type, callback) {
const id = this.callbackID++;
this.callbacks[id] = callback;
this.chefWorkers[0].worker.postMessage({
2018-05-15 17:36:45 +00:00
action: "getDishAs",
data: {
dish: dish,
type: type,
id: id
}
});
}
/**
* Sets the console log level in the worker.
*
* @param {string} level
*/
setLogLevel(level) {
if (!this.chefWorkers || !this.chefWorkers.length > 0) return;
2018-05-15 17:36:45 +00:00
for (let i = 0; i < this.chefWorkers.length; i++) {
this.chefWorkers[i].worker.postMessage({
action: "setLogLevel",
data: log.getLevel()
});
}
2018-05-15 17:36:45 +00:00
}
}
export default WorkerWaiter;