aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2019-08-16 15:03:52 +0200
committerFlorian Dold <florian.dold@gmail.com>2019-08-16 15:03:52 +0200
commita1e0fc3b88ed4305f942fadbea66b29a3934721c (patch)
treecb4887bdb68f76eeeb7c4baecc47e5ceb2d4498c
parent8f5b6ffd7d02986176c95c8800a8292555e2702b (diff)
crypto worker refactoring
-rw-r--r--src/crypto/browserWorkerEntry.ts120
-rw-r--r--src/crypto/cryptoApi-test.ts3
-rw-r--r--src/crypto/cryptoApi.ts44
-rw-r--r--src/crypto/cryptoWorker.ts81
-rw-r--r--src/crypto/emscInterface-test.ts35
-rw-r--r--src/crypto/emscInterface.ts49
-rw-r--r--src/crypto/emscLoader.d.ts64
-rw-r--r--src/crypto/emscLoader.js117
-rw-r--r--src/crypto/nodeEmscriptenLoader.ts102
-rw-r--r--src/crypto/nodeProcessWorker.ts64
-rw-r--r--src/crypto/nodeWorkerEntry.ts105
-rw-r--r--src/crypto/synchronousWorker.ts122
-rw-r--r--src/headless/taler-wallet-cli.ts2
-rw-r--r--tsconfig.json4
-rw-r--r--webpack.config.js5
15 files changed, 424 insertions, 493 deletions
diff --git a/src/crypto/browserWorkerEntry.ts b/src/crypto/browserWorkerEntry.ts
new file mode 100644
index 000000000..3df59fe0d
--- /dev/null
+++ b/src/crypto/browserWorkerEntry.ts
@@ -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);
+ });
+};
diff --git a/src/crypto/cryptoApi-test.ts b/src/crypto/cryptoApi-test.ts
index 9c592412e..48231e5ff 100644
--- a/src/crypto/cryptoApi-test.ts
+++ b/src/crypto/cryptoApi-test.ts
@@ -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";
diff --git a/src/crypto/cryptoApi.ts b/src/crypto/cryptoApi.ts
index c68b10700..a4e12c00f 100644
--- a/src/crypto/cryptoApi.ts
+++ b/src/crypto/cryptoApi.ts
@@ -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;
@@ -142,24 +120,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.
*/
diff --git a/src/crypto/cryptoWorker.ts b/src/crypto/cryptoWorker.ts
index 11e3d964c..0ea641dde 100644
--- a/src/crypto/cryptoWorker.ts
+++ b/src/crypto/cryptoWorker.ts
@@ -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;
+} \ No newline at end of file
diff --git a/src/crypto/emscInterface-test.ts b/src/crypto/emscInterface-test.ts
index 51f2d58b6..b1a45cbcf 100644
--- a/src/crypto/emscInterface-test.ts
+++ b/src/crypto/emscInterface-test.ts
@@ -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();
diff --git a/src/crypto/emscInterface.ts b/src/crypto/emscInterface.ts
index 96e94e3fb..0c7edefab 100644
--- a/src/crypto/emscInterface.ts
+++ b/src/crypto/emscInterface.ts
@@ -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;
diff --git a/src/crypto/emscLoader.d.ts b/src/crypto/emscLoader.d.ts
deleted file mode 100644
index 3ec4f4cfb..000000000
--- a/src/crypto/emscLoader.d.ts
+++ /dev/null
@@ -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;
-}
diff --git a/src/crypto/emscLoader.js b/src/crypto/emscLoader.js
deleted file mode 100644
index 25dc6b85c..000000000
--- a/src/crypto/emscLoader.js
+++ /dev/null
@@ -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");
-}
diff --git a/src/crypto/nodeEmscriptenLoader.ts b/src/crypto/nodeEmscriptenLoader.ts
new file mode 100644
index 000000000..6d6a7306b
--- /dev/null
+++ b/src/crypto/nodeEmscriptenLoader.ts
@@ -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'",
+ );
+ }
+ });
+ }
+}
diff --git a/src/crypto/nodeProcessWorker.ts b/src/crypto/nodeProcessWorker.ts
index b5a2e8b44..c5d0f2e71 100644
--- a/src/crypto/nodeProcessWorker.ts
+++ b/src/crypto/nodeProcessWorker.ts
@@ -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()});
}
/**
diff --git a/src/crypto/nodeWorkerEntry.ts b/src/crypto/nodeWorkerEntry.ts
index 9e2647ff6..1ed16f870 100644
--- a/src/crypto/nodeWorkerEntry.ts
+++ b/src/crypto/nodeWorkerEntry.ts
@@ -17,61 +17,64 @@
// tslint:disable:no-var-requires
-const fs = require("fs");
-const vm = require("vm");
-
-process.once("message", (obj: any) => {
- const g: any = global as any;
-
- (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);
- }
- },
- };
-
- 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();
- }
- };
+import fs = require("fs");
+import vm = require("vm");
+import { NodeEmscriptenLoader } from "./nodeEmscriptenLoader";
+import { CryptoImplementation } from "./cryptoImplementation";
- Object.keys(g.self).forEach((key) => {
- g[key] = g.self[key];
- });
+const loader = new NodeEmscriptenLoader();
+
+async function handleRequest(operation: string, id: number, args: string[]) {
+ let emsc = await loader.getEmscriptenEnvironment();
- process.on("message", (msg: any) => {
- try {
- (g.onmessage || g.self.onmessage || (() => undefined))(JSON.parse(msg));
- } catch (err) {
- (g.onerror || g.self.onerror || (() => undefined))(err);
+ 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;
+ }
+}
- process.on("uncaughtException", (err: any) => {
- (g.onerror || g.self.onerror || (() => undefined))(err);
+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);
});
+});
- require(obj.scriptFilename);
+process.on("uncaughtException", (err: any) => {
+ console.log("uncaught exception in node worker entry", err);
});
diff --git a/src/crypto/synchronousWorker.ts b/src/crypto/synchronousWorker.ts
index 7d115f1dc..4369612ad 100644
--- a/src/crypto/synchronousWorker.ts
+++ b/src/crypto/synchronousWorker.ts
@@ -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);
diff --git a/src/headless/taler-wallet-cli.ts b/src/headless/taler-wallet-cli.ts
index 1e501cdc6..49cc608d9 100644
--- a/src/headless/taler-wallet-cli.ts
+++ b/src/headless/taler-wallet-cli.ts
@@ -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;
diff --git a/tsconfig.json b/tsconfig.json
index 8104054ac..c833ef9a3 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -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",
diff --git a/webpack.config.js b/webpack.config.js
index 845b56fe2..550146184 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -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",
};