crypto worker refactoring

This commit is contained in:
Florian Dold 2019-08-16 15:03:52 +02:00
parent 8f5b6ffd7d
commit a1e0fc3b88
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
15 changed files with 424 additions and 493 deletions

View File

@ -0,0 +1,120 @@
/*
This file is part of TALER
(C) 2016 GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Web worker for crypto operations.
*/
/**
* Imports.
*/
import { CryptoImplementation } from "./cryptoImplementation";
import { EmscEnvironment } from "./emscInterface";
const worker: Worker = (self as any) as Worker;
class BrowserEmscriptenLoader {
private cachedEmscEnvironment: EmscEnvironment | undefined = undefined;
private cachedEmscEnvironmentPromise:
| Promise<EmscEnvironment>
| undefined = undefined;
async getEmscriptenEnvironment(): Promise<EmscEnvironment> {
if (this.cachedEmscEnvironment) {
return this.cachedEmscEnvironment;
}
if (this.cachedEmscEnvironmentPromise) {
return this.cachedEmscEnvironmentPromise;
}
console.log("loading emscripten lib with 'importScripts'");
// @ts-ignore
self.TalerEmscriptenLib = {};
// @ts-ignore
importScripts('/emscripten/taler-emscripten-lib.js')
// @ts-ignore
if (!self.TalerEmscriptenLib) {
throw Error("can't import taler emscripten lib");
}
const locateFile = (path: string, scriptDir: string) => {
console.log("locating file", "path", path, "scriptDir", scriptDir);
// This is quite hacky and assumes that our scriptDir is dist/
return scriptDir + "../emscripten/" + path;
};
console.log("instantiating TalerEmscriptenLib");
// @ts-ignore
const lib = self.TalerEmscriptenLib({ locateFile });
return new Promise((resolve, reject) => {
lib.then((mod: any) => {
this.cachedEmscEnvironmentPromise = undefined;
const emsc = new EmscEnvironment(mod);
this.cachedEmscEnvironment = new EmscEnvironment(mod);
console.log("emscripten module fully loaded");
resolve(emsc);
});
});
}
}
let loader = new BrowserEmscriptenLoader();
async function handleRequest(operation: string, id: number, args: string[]) {
let emsc = await loader.getEmscriptenEnvironment();
const impl = new CryptoImplementation(emsc);
if (!(operation in impl)) {
console.error(`crypto operation '${operation}' not found`);
return;
}
try {
const result = (impl as any)[operation](...args);
worker.postMessage({ result, id });
} catch (e) {
console.log("error during operation", e);
return;
}
}
worker.onmessage = (msg: MessageEvent) => {
const args = msg.data.args;
if (!Array.isArray(args)) {
console.error("args must be array");
return;
}
const id = msg.data.id;
if (typeof id !== "number") {
console.error("RPC id must be number");
return;
}
const operation = msg.data.operation;
if (typeof operation !== "string") {
console.error("RPC operation must be string");
return;
}
if (CryptoImplementation.enableTracing) {
console.log("onmessage with", operation);
}
handleRequest(operation, id, args).catch((e) => {
console.error("error in browsere worker", e);
});
};

View File

@ -24,7 +24,8 @@ import {
ReserveRecord,
} from "../dbTypes";
import { CryptoApi, NodeCryptoWorkerFactory } from "./cryptoApi";
import { CryptoApi } from "./cryptoApi";
import { NodeCryptoWorkerFactory } from "./nodeProcessWorker";
const masterPub1: string =
"CQQZ9DY3MZ1ARMN5K1VKDETS04Y2QCKMMCFHZSWJWWVN82BTTH00";

View File

@ -34,6 +34,8 @@ import {
WireFee,
} from "../dbTypes";
import { CryptoWorker } from "./cryptoWorker";
import { ContractTerms, PaybackRequest } from "../talerTypes";
import { BenchmarkResult, CoinWithDenom, PayCoinInfo } from "../walletTypes";
@ -83,15 +85,6 @@ interface WorkItem {
*/
const NUM_PRIO = 5;
interface CryptoWorker {
postMessage(message: any): void;
terminate(): void;
onmessage: (m: any) => void;
onerror: (m: any) => void;
}
export interface CryptoWorkerFactory {
/**
* Start a new worker.
@ -105,21 +98,6 @@ export interface CryptoWorkerFactory {
getConcurrency(): number;
}
export class NodeCryptoWorkerFactory implements CryptoWorkerFactory {
startWorker(): CryptoWorker {
if (typeof require === "undefined") {
throw Error("cannot make worker, require(...) not defined");
}
const workerCtor = require("./nodeProcessWorker").Worker;
const workerPath = __dirname + "/cryptoWorker.js";
return new workerCtor(workerPath);
}
getConcurrency(): number {
return 2;
}
}
export class BrowserCryptoWorkerFactory implements CryptoWorkerFactory {
startWorker(): CryptoWorker {
const workerCtor = Worker;
@ -141,24 +119,6 @@ export class BrowserCryptoWorkerFactory implements CryptoWorkerFactory {
}
}
/**
* The synchronous crypto worker produced by this factory doesn't run in the
* background, but actually blocks the caller until the operation is done.
*/
export class SynchronousCryptoWorkerFactory implements CryptoWorkerFactory {
startWorker(): CryptoWorker {
if (typeof require === "undefined") {
throw Error("cannot make worker, require(...) not defined");
}
const workerCtor = require("./synchronousWorker").SynchronousCryptoWorker;
return new workerCtor();
}
getConcurrency(): number {
return 1;
}
}
/**
* Crypto API that interfaces manages a background crypto thread
* for the execution of expensive operations.

View File

@ -1,77 +1,8 @@
/*
This file is part of TALER
(C) 2016 GNUnet e.V.
export interface CryptoWorker {
postMessage(message: any): void;
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
terminate(): void;
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Web worker for crypto operations.
*/
/**
* Imports.
*/
import * as emscLoader from "./emscLoader";
import { CryptoImplementation } from "./cryptoImplementation";
import { EmscEnvironment } from "./emscInterface";
const worker: Worker = (self as any) as Worker;
let impl: CryptoImplementation | undefined;
worker.onmessage = (msg: MessageEvent) => {
const args = msg.data.args;
if (!Array.isArray(args)) {
console.error("args must be array");
return;
}
const id = msg.data.id;
if (typeof id !== "number") {
console.error("RPC id must be number");
return;
}
const operation = msg.data.operation;
if (typeof operation !== "string") {
console.error("RPC operation must be string");
return;
}
if (CryptoImplementation.enableTracing) {
console.log("onmessage with", operation);
}
emscLoader.getLib().then(p => {
const lib = p.lib;
const emsc = new EmscEnvironment(lib);
const impl = new CryptoImplementation(emsc);
if (!(operation in impl)) {
console.error(`unknown operation: '${operation}'`);
return;
}
if (CryptoImplementation.enableTracing) {
console.log("about to execute", operation);
}
const result = (impl as any)[operation](...args);
if (CryptoImplementation.enableTracing) {
console.log("finished executing", operation);
}
worker.postMessage({ result, id });
});
};
onmessage: (m: any) => void;
onerror: (m: any) => void;
}

View File

@ -17,13 +17,13 @@
// tslint:disable:max-line-length
import test from "ava";
import * as emscLoader from "./emscLoader";
import { NodeEmscriptenLoader } from "./nodeEmscriptenLoader";
import * as native from "./emscInterface";
test("string hashing", async (t) => {
const { lib } = await emscLoader.getLib();
const emsc = new native.EmscEnvironment(lib);
const loader = new NodeEmscriptenLoader();
const emsc = await loader.getEmscriptenEnvironment();
const x = native.ByteArray.fromStringWithNull(emsc, "hello taler");
const h = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR";
@ -35,8 +35,8 @@ test("string hashing", async (t) => {
test("signing", async (t) => {
const { lib } = await emscLoader.getLib();
const emsc = new native.EmscEnvironment(lib);
const loader = new NodeEmscriptenLoader();
const emsc = await loader.getEmscriptenEnvironment();
const x = native.ByteArray.fromStringWithNull(emsc, "hello taler");
const priv = native.EddsaPrivateKey.create(emsc, );
@ -49,8 +49,8 @@ test("signing", async (t) => {
test("signing-fixed-data", async (t) => {
const { lib } = await emscLoader.getLib();
const emsc = new native.EmscEnvironment(lib);
const loader = new NodeEmscriptenLoader();
const emsc = await loader.getEmscriptenEnvironment();
const x = native.ByteArray.fromStringWithNull(emsc, "hello taler");
const purpose = new native.EccSignaturePurpose(emsc, native.SignaturePurpose.TEST, x);
@ -74,8 +74,8 @@ const denomPubStr1 = "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30G9R64VK6HHS6M
test("rsa-encode", async (t) => {
const { lib } = await emscLoader.getLib();
const emsc = new native.EmscEnvironment(lib);
const loader = new NodeEmscriptenLoader();
const emsc = await loader.getEmscriptenEnvironment();
const pubHashStr = "JM63YM5X7X547164QJ3MGJZ4WDD47GEQR5DW5SH35G4JFZXEJBHE5JBNZM5K8XN5C4BRW25BE6GSVAYBF790G2BZZ13VW91D41S4DS0";
const denomPub = native.RsaPublicKey.fromCrock(emsc, denomPubStr1);
@ -86,8 +86,8 @@ test("rsa-encode", async (t) => {
test("withdraw-request", async (t) => {
const { lib } = await emscLoader.getLib();
const emsc = new native.EmscEnvironment(lib);
const loader = new NodeEmscriptenLoader();
const emsc = await loader.getEmscriptenEnvironment();
const reservePrivStr = "G9R8KRRCAFKPD0KW7PW48CC2T03VQ8K2AN9J6J6K2YW27J5MHN90";
const reservePriv = native.EddsaPrivateKey.fromCrock(emsc, reservePrivStr);
@ -117,9 +117,8 @@ test("withdraw-request", async (t) => {
test("currency-conversion", async (t) => {
const { lib } = await emscLoader.getLib();
const emsc = new native.EmscEnvironment(lib);
const loader = new NodeEmscriptenLoader();
const emsc = await loader.getEmscriptenEnvironment();
const a1 = new native.Amount(emsc, {currency: "KUDOS", value: 1, fraction: 50000000});
const a2 = new native.Amount(emsc, {currency: "KUDOS", value: 1, fraction: 50000000});
@ -133,8 +132,8 @@ test("currency-conversion", async (t) => {
test("ecdsa", async (t) => {
const { lib } = await emscLoader.getLib();
const emsc = new native.EmscEnvironment(lib);
const loader = new NodeEmscriptenLoader();
const emsc = await loader.getEmscriptenEnvironment();
const priv = native.EcdsaPrivateKey.create(emsc);
const pub1 = priv.getPublicKey();
@ -145,8 +144,8 @@ test("ecdsa", async (t) => {
test("ecdhe", async (t) => {
const { lib } = await emscLoader.getLib();
const emsc = new native.EmscEnvironment(lib);
const loader = new NodeEmscriptenLoader();
const emsc = await loader.getEmscriptenEnvironment();
const priv = native.EcdhePrivateKey.create(emsc);
const pub = priv.getPublicKey();

View File

@ -28,8 +28,6 @@
*/
import { AmountJson } from "../amounts";
import { EmscFunGen, EmscLib } from "./emscLoader";
/**
* Size of a native pointer. Must match the size
* use when compiling via emscripten.
@ -38,6 +36,53 @@ const PTR_SIZE = 4;
const GNUNET_OK = 1;
/**
* Signature of the function that retrieves emscripten
* function implementations.
*/
export interface EmscFunGen {
(name: string,
ret: string,
args: string[]): ((...x: Array<number|string>) => any);
(name: string,
ret: "number",
args: string[]): ((...x: Array<number|string>) => number);
(name: string,
ret: "void",
args: string[]): ((...x: Array<number|string>) => void);
(name: string,
ret: "string",
args: string[]): ((...x: Array<number|string>) => string);
}
interface EmscLib {
cwrap: EmscFunGen;
ccall(name: string, ret: "number"|"string", argTypes: any[], args: any[]): any;
stringToUTF8(s: string, addr: number, maxLength: number): void;
onRuntimeInitialized(f: () => void): void;
readBinary?: (filename: string) => Promise<ArrayBuffer>;
calledRun?: boolean;
_free(ptr: number): void;
_malloc(n: number): number;
Pointer_stringify(p: number, len?: number): string;
getValue(ptr: number, type: string, noSafe?: boolean): number;
setValue(ptr: number, value: number, type: string, noSafe?: boolean): void;
writeStringToMemory(s: string, buffer: number, dontAddNull?: boolean): void;
}
interface EmscFunctions {
amount_add(a1: number, a2: number, a3: number): number;
amount_cmp(a1: number, a2: number): number;

View File

@ -1,64 +0,0 @@
/*
This file is part of TALER
(C) 2016 GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
declare function getLib(): Promise<{ lib: EmscLib }>;
/**
* Signature of the function that retrieves emscripten
* function implementations.
*/
export interface EmscFunGen {
(name: string,
ret: string,
args: string[]): ((...x: Array<number|string>) => any);
(name: string,
ret: "number",
args: string[]): ((...x: Array<number|string>) => number);
(name: string,
ret: "void",
args: string[]): ((...x: Array<number|string>) => void);
(name: string,
ret: "string",
args: string[]): ((...x: Array<number|string>) => string);
}
interface EmscLib {
cwrap: EmscFunGen;
ccall(name: string, ret: "number"|"string", argTypes: any[], args: any[]): any;
stringToUTF8(s: string, addr: number, maxLength: number): void;
onRuntimeInitialized(f: () => void): void;
readBinary?: (filename: string) => Promise<ArrayBuffer>;
calledRun?: boolean;
_free(ptr: number): void;
_malloc(n: number): number;
Pointer_stringify(p: number, len?: number): string;
getValue(ptr: number, type: string, noSafe?: boolean): number;
setValue(ptr: number, value: number, type: string, noSafe?: boolean): void;
writeStringToMemory(s: string, buffer: number, dontAddNull?: boolean): void;
}

View File

@ -1,117 +0,0 @@
/*
This file is part of TALER
(C) 2017 Inria and GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
// @ts-nocheck
/**
* This module loads the emscripten library, and is written in unchecked
* JavaScript since it needs to do environment detection and dynamically select
* the right way to load the library.
*/
let cachedLib = undefined;
let cachedLibPromise = undefined;
export let enableTracing = false;
/**
* Load the taler emscripten lib.
*
* If in a WebWorker, importScripts is used. Inside a browser, the module must
* be globally available. Inside node, require is used.
*
* Returns a Promise<{ lib: EmscLib }>
*/
export function getLib() {
enableTracing && console.log("in getLib");
if (cachedLib) {
enableTracing && console.log("lib is cached");
return Promise.resolve({ lib: cachedLib });
}
if (cachedLibPromise) {
return cachedLibPromise;
}
if (typeof require !== "undefined") {
enableTracing && console.log("trying to load emscripten lib with 'require'");
// Make sure that TypeScript doesn't try
// to check the taler-emscripten-lib.
const indirectRequire = require;
const g = global;
// unavoidable hack, so that emscripten detects
// the environment as node even though importScripts
// is present.
const savedImportScripts = g.importScripts;
delete g.importScripts;
// Assume that the code is run from the build/ directory.
const libFn = indirectRequire("../../../emscripten/taler-emscripten-lib.js");
const lib = libFn();
g.importScripts = savedImportScripts;
if (lib) {
if (!lib.ccall) {
throw Error("sanity check failed: taler-emscripten lib does not have 'ccall'");
}
cachedLibPromise = new Promise((resolve, reject) => {
lib.onRuntimeInitialized = () => {
cachedLib = lib;
cachedLibPromise = undefined;
resolve({ lib: cachedLib });
};
});
return cachedLibPromise;
} else {
// When we're running as a webpack bundle, the above require might
// have failed and returned 'undefined', so we try other ways to import.
console.log("failed to load emscripten lib with 'require', trying alternatives");
}
}
if (typeof importScripts !== "undefined") {
console.log("trying to load emscripten lib with 'importScripts'");
self.TalerEmscriptenLib = {};
importScripts('/emscripten/taler-emscripten-lib.js')
if (!self.TalerEmscriptenLib) {
throw Error("can't import taler emscripten lib");
}
const locateFile = (path, scriptDir) => {
console.log("locating file", "path", path, "scriptDir", scriptDir);
// This is quite hacky and assumes that our scriptDir is dist/
return scriptDir + "../emscripten/" + path;
};
console.log("instantiating TalerEmscriptenLib");
const lib = self.TalerEmscriptenLib({ locateFile });
cachedLib = lib;
return new Promise((resolve, reject) => {
lib.then(mod => {
console.log("emscripten module fully loaded");
resolve({ lib: mod });
});
});
}
// Last resort, we don't have require, we're not running in a webworker.
// Maybe we're on a normal browser page, in this case TalerEmscriptenLib
// must be included in a script tag on the page.
if (typeof window !== "undefined") {
if (window.TalerEmscriptenLib) {
return Promise.resolve(TalerEmscriptenLib);
}
throw Error("Looks like running in browser, but TalerEmscriptenLib is not defined");
}
throw Error("Running in unsupported environment");
}

View File

@ -0,0 +1,102 @@
import { EmscEnvironment } from "./emscInterface";
import { CryptoImplementation } from "./cryptoImplementation";
import fs = require("fs");
export class NodeEmscriptenLoader {
private cachedEmscEnvironment: EmscEnvironment | undefined = undefined;
private cachedEmscEnvironmentPromise:
| Promise<EmscEnvironment>
| undefined = undefined;
private async getWasmBinary(): Promise<Uint8Array> {
// @ts-ignore
const akonoGetData = global.__akono_getData;
if (akonoGetData) {
// We're running embedded node on Android
console.log("reading wasm binary from akono");
const data = akonoGetData("taler-emscripten-lib.wasm");
// The data we get is base64-encoded binary data
let buf = new Buffer(data, 'base64');
return new Uint8Array(buf);
} else {
// We're in a normal node environment
const binaryPath = __dirname + "/../../../emscripten/taler-emscripten-lib.wasm";
console.log("reading from", binaryPath);
const wasmBinary = new Uint8Array(fs.readFileSync(binaryPath));
return wasmBinary;
}
}
async getEmscriptenEnvironment(): Promise<EmscEnvironment> {
if (this.cachedEmscEnvironment) {
return this.cachedEmscEnvironment;
}
if (this.cachedEmscEnvironmentPromise) {
return this.cachedEmscEnvironmentPromise;
}
let lib: any;
const wasmBinary = await this.getWasmBinary();
return new Promise((resolve, reject) => {
// Arguments passed to the emscripten prelude
const libArgs = {
wasmBinary,
onRuntimeInitialized: () => {
if (!lib) {
console.error("fatal emscripten initialization error");
return;
}
this.cachedEmscEnvironmentPromise = undefined;
this.cachedEmscEnvironment = new EmscEnvironment(lib);
resolve(this.cachedEmscEnvironment);
},
};
// Make sure that TypeScript doesn't try
// to check the taler-emscripten-lib.
const indirectRequire = require;
const g = global;
// unavoidable hack, so that emscripten detects
// the environment as node even though importScripts
// is present.
// @ts-ignore
const savedImportScripts = g.importScripts;
// @ts-ignore
delete g.importScripts;
// @ts-ignore
const savedCrypto = g.crypto;
// @ts-ignore
delete g.crypto;
// Assume that the code is run from the build/ directory.
const libFn = indirectRequire(
"../../../emscripten/taler-emscripten-lib.js",
);
lib = libFn(libArgs);
// @ts-ignore
g.importScripts = savedImportScripts;
// @ts-ignore
g.crypto = savedCrypto;
if (!lib) {
throw Error("could not load taler-emscripten-lib.js");
}
if (!lib.ccall) {
throw Error(
"sanity check failed: taler-emscripten lib does not have 'ccall'",
);
}
});
}
}

View File

@ -1,3 +1,5 @@
import { CryptoWorkerFactory } from "./cryptoApi";
/*
This file is part of TALER
(C) 2016 GNUnet e.V.
@ -17,11 +19,29 @@
// tslint:disable:no-var-requires
const path = require("path");
const fork = require("child_process").fork;
import { CryptoWorker } from "./cryptoWorker";
import path = require("path");
import child_process = require("child_process");
const nodeWorkerEntry = path.join(__dirname, "nodeWorkerEntry.js");
export class NodeCryptoWorkerFactory implements CryptoWorkerFactory {
startWorker(): CryptoWorker {
if (typeof require === "undefined") {
throw Error("cannot make worker, require(...) not defined");
}
const workerCtor = require("./nodeProcessWorker").Worker;
const workerPath = __dirname + "/cryptoWorker.js";
return new workerCtor(workerPath);
}
getConcurrency(): number {
return 4;
}
}
/**
* Worker implementation that uses node subprocesses.
*/
@ -38,33 +58,35 @@ export class Worker {
*/
onerror: undefined | ((m: any) => void);
constructor(scriptFilename: string) {
this.child = fork(nodeWorkerEntry);
private dispatchMessage(msg: any) {
if (this.onmessage) {
this.onmessage({ data: msg });
} else {
console.warn("no handler for worker event 'message' defined")
}
}
private dispatchError(msg: any) {
if (this.onerror) {
this.onerror({ data: msg });
} else {
console.warn("no handler for worker event 'error' defined")
}
}
constructor() {
this.child = child_process.fork(nodeWorkerEntry);
this.onerror = undefined;
this.onmessage = undefined;
this.child.on("error", (e: any) => {
if (this.onerror) {
this.onerror(e);
}
this.dispatchError(e);
});
this.child.on("message", (msg: any) => {
const message = JSON.parse(msg);
if (!message.error && this.onmessage) {
this.onmessage(message);
}
if (message.error && this.onerror) {
const error = new Error(message.error);
error.stack = message.stack;
this.onerror(error);
}
console.log("nodeProcessWorker got child message", msg);
this.dispatchMessage(msg);
});
this.child.send({scriptFilename, cwd: process.cwd()});
}
/**

View File

@ -17,61 +17,64 @@
// tslint:disable:no-var-requires
const fs = require("fs");
const vm = require("vm");
import fs = require("fs");
import vm = require("vm");
import { NodeEmscriptenLoader } from "./nodeEmscriptenLoader";
import { CryptoImplementation } from "./cryptoImplementation";
process.once("message", (obj: any) => {
const g: any = global as any;
const loader = new NodeEmscriptenLoader();
(g as any).self = {
addEventListener: (event: "error" | "message", fn: (x: any) => void) => {
if (event === "error") {
g.onerror = fn;
} else if (event === "message") {
g.onmessage = fn;
}
},
close: () => {
process.exit(0);
},
onerror: (err: any) => {
const str: string = JSON.stringify({error: err.message, stack: err.stack});
if (process.send) {
process.send(str);
}
},
onmessage: undefined,
postMessage: (msg: any) => {
const str: string = JSON.stringify({data: msg});
if (process.send) {
process.send(str);
}
},
};
async function handleRequest(operation: string, id: number, args: string[]) {
let emsc = await loader.getEmscriptenEnvironment();
g.__dirname = obj.cwd;
g.__filename = __filename;
g.importScripts = (...files: string[]) => {
if (files.length > 0) {
vm.createScript(files.map((file) => fs.readFileSync(file, "utf8")).join("\n")).runInThisContext();
const impl = new CryptoImplementation(emsc);
if (!(operation in impl)) {
console.error(`crypto operation '${operation}' not found`);
return;
}
try {
const result = (impl as any)[operation](...args);
if (process.send) {
process.send({ result, id });
} else {
console.error("process.send not available");
}
};
} catch (e) {
console.log("error during operation", e);
return;
}
}
Object.keys(g.self).forEach((key) => {
g[key] = g.self[key];
process.on("message", (msgStr: any) => {
console.log("got message in node worker entry", msgStr);
console.log("typeof msg", typeof msgStr);
const msg = JSON.parse(msgStr);
const args = msg.data.args;
if (!Array.isArray(args)) {
console.error("args must be array");
return;
}
const id = msg.data.id;
if (typeof id !== "number") {
console.error("RPC id must be number");
return;
}
const operation = msg.data.operation;
if (typeof operation !== "string") {
console.error("RPC operation must be string");
return;
}
handleRequest(operation, id, args).catch((e) => {
console.error("error in node worker", e);
});
process.on("message", (msg: any) => {
try {
(g.onmessage || g.self.onmessage || (() => undefined))(JSON.parse(msg));
} catch (err) {
(g.onerror || g.self.onerror || (() => undefined))(err);
}
});
process.on("uncaughtException", (err: any) => {
(g.onerror || g.self.onerror || (() => undefined))(err);
});
require(obj.scriptFilename);
});
process.on("uncaughtException", (err: any) => {
console.log("uncaught exception in node worker entry", err);
});

View File

@ -14,19 +14,37 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { EmscEnvironment } from "./emscInterface";
import { CryptoImplementation } from "./cryptoImplementation";
import { NodeEmscriptenLoader } from "./nodeEmscriptenLoader";
import fs = require("fs");
import { CryptoWorkerFactory } from "./cryptoApi";
import { CryptoWorker } from "./cryptoWorker";
/**
* The synchronous crypto worker produced by this factory doesn't run in the
* background, but actually blocks the caller until the operation is done.
*/
export class SynchronousCryptoWorkerFactory implements CryptoWorkerFactory {
startWorker(): CryptoWorker {
if (typeof require === "undefined") {
throw Error("cannot make worker, require(...) not defined");
}
const workerCtor = require("./synchronousWorker").SynchronousCryptoWorker;
return new workerCtor();
}
getConcurrency(): number {
return 1;
}
}
/**
* Worker implementation that uses node subprocesses.
*/
export class SynchronousCryptoWorker {
private cachedEmscEnvironment: EmscEnvironment | undefined = undefined;
private cachedEmscEnvironmentPromise:
| Promise<EmscEnvironment>
| undefined = undefined;
/**
* Function to be called when we receive a message from the worker thread.
@ -38,6 +56,8 @@ export class SynchronousCryptoWorker {
*/
onerror: undefined | ((m: any) => void);
private emscriptenLoader = new NodeEmscriptenLoader();
constructor() {
this.onerror = undefined;
this.onmessage = undefined;
@ -57,96 +77,6 @@ export class SynchronousCryptoWorker {
}
}
private async getWasmBinary(): Promise<Uint8Array> {
// @ts-ignore
const akonoGetData = global.__akono_getData;
if (akonoGetData) {
// We're running embedded node on Android
console.log("reading wasm binary from akono");
const data = akonoGetData("taler-emscripten-lib.wasm");
// The data we get is base64-encoded binary data
let buf = new Buffer(data, 'base64');
return new Uint8Array(buf);
} else {
// We're in a normal node environment
const binaryPath = __dirname + "/../../../emscripten/taler-emscripten-lib.wasm";
console.log("reading from", binaryPath);
const wasmBinary = new Uint8Array(fs.readFileSync(binaryPath));
return wasmBinary;
}
}
private async getEmscriptenEnvironment(): Promise<EmscEnvironment> {
if (this.cachedEmscEnvironment) {
return this.cachedEmscEnvironment;
}
if (this.cachedEmscEnvironmentPromise) {
return this.cachedEmscEnvironmentPromise;
}
let lib: any;
const wasmBinary = await this.getWasmBinary();
return new Promise((resolve, reject) => {
// Arguments passed to the emscripten prelude
const libArgs = {
wasmBinary,
onRuntimeInitialized: () => {
if (!lib) {
console.error("fatal emscripten initialization error");
return;
}
this.cachedEmscEnvironmentPromise = undefined;
this.cachedEmscEnvironment = new EmscEnvironment(lib);
resolve(this.cachedEmscEnvironment);
},
};
// Make sure that TypeScript doesn't try
// to check the taler-emscripten-lib.
const indirectRequire = require;
const g = global;
// unavoidable hack, so that emscripten detects
// the environment as node even though importScripts
// is present.
// @ts-ignore
const savedImportScripts = g.importScripts;
// @ts-ignore
delete g.importScripts;
// @ts-ignore
const savedCrypto = g.crypto;
// @ts-ignore
delete g.crypto;
// Assume that the code is run from the build/ directory.
const libFn = indirectRequire(
"../../../emscripten/taler-emscripten-lib.js",
);
lib = libFn(libArgs);
// @ts-ignore
g.importScripts = savedImportScripts;
// @ts-ignore
g.crypto = savedCrypto;
if (!lib) {
throw Error("could not load taler-emscripten-lib.js");
}
if (!lib.ccall) {
throw Error(
"sanity check failed: taler-emscripten lib does not have 'ccall'",
);
}
});
}
private dispatchMessage(msg: any) {
if (this.onmessage) {
this.onmessage({ data: msg });
@ -154,7 +84,7 @@ export class SynchronousCryptoWorker {
}
private async handleRequest(operation: string, id: number, args: string[]) {
let emsc = await this.getEmscriptenEnvironment();
let emsc = await this.emscriptenLoader.getEmscriptenEnvironment();
const impl = new CryptoImplementation(emsc);

View File

@ -26,7 +26,7 @@ import URI = require("urijs");
import querystring = require("querystring");
import { CheckPaymentResponse } from "../talerTypes";
import { NodeCryptoWorkerFactory, SynchronousCryptoWorkerFactory } from "../crypto/cryptoApi";
import { SynchronousCryptoWorkerFactory } from "../crypto/synchronousWorker";
const enableTracing = false;

View File

@ -27,14 +27,14 @@
"decl/urijs.d.ts",
"src/amounts.ts",
"src/checkable.ts",
"src/crypto/browserWorkerEntry.ts",
"src/crypto/cryptoApi-test.ts",
"src/crypto/cryptoApi.ts",
"src/crypto/cryptoImplementation.ts",
"src/crypto/cryptoWorker.ts",
"src/crypto/emscInterface-test.ts",
"src/crypto/emscInterface.ts",
"src/crypto/emscLoader.d.ts",
"src/crypto/emscLoader.js",
"src/crypto/nodeEmscriptenLoader.ts",
"src/crypto/nodeProcessWorker.ts",
"src/crypto/nodeWorkerEntry.ts",
"src/crypto/synchronousWorker.ts",

View File

@ -27,8 +27,7 @@ module.exports = function (env) {
{
test: /\.tsx?$/,
loader: 'awesome-typescript-loader',
exclude: /node_modules/,
exclude: /taler-emscripten-lib/,
exclude: /node_modules|taler-emscripten-lib|nodeEmscriptenLoader|synchronousWorker/,
}
]
},
@ -58,7 +57,7 @@ module.exports = function (env) {
}
}
const configWebWorker = {
entry: {"cryptoWorker": "./src/crypto/cryptoWorker.ts"},
entry: {"cryptoWorker": "./src/crypto/browserWorkerEntry.ts"},
target: "webworker",
name: "webworker",
};