mirror of
https://github.com/gchq/CyberChef
synced 2025-01-25 10:45:03 +00:00
c289e1beef
Don't create a DOM element for every tab, just reuse the same ones. Display file information while the files are loading. (Output tabs no longer work)
339 lines
9.9 KiB
JavaScript
339 lines
9.9 KiB
JavaScript
/**
|
|
* @author n1474335 [n1474335@gmail.com]
|
|
* @author j433866 [j433866@gmail.com]
|
|
* @copyright Crown Copyright 2019
|
|
* @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
|
|
* @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 = [];
|
|
}
|
|
|
|
/**
|
|
* Sets up a pool of ChefWorkers to be used for baking
|
|
*/
|
|
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
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 ${r.data.inputNum} 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();
|
|
}
|
|
break;
|
|
case "bakeError":
|
|
this.runningWorkers -= 1;
|
|
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
|
|
*/
|
|
cancelBake() {
|
|
for (let i = this.chefWorkers.length - 1; i >= 0; i--) {
|
|
this.chefWorkers[i].worker.terminate();
|
|
this.chefWorkers.pop();
|
|
}
|
|
this.setupChefWorkers();
|
|
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
|
|
*
|
|
* @param {Object} response
|
|
*/
|
|
bakingCompleteOld(response) {
|
|
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
|
|
* @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 = [{
|
|
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
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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({
|
|
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({
|
|
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({
|
|
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;
|
|
|
|
for (let i = 0; i < this.chefWorkers.length; i++) {
|
|
this.chefWorkers[i].worker.postMessage({
|
|
action: "setLogLevel",
|
|
data: log.getLevel()
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
export default WorkerWaiter;
|