crypto worker refactoring
This commit is contained in:
parent
8f5b6ffd7d
commit
a1e0fc3b88
120
src/crypto/browserWorkerEntry.ts
Normal file
120
src/crypto/browserWorkerEntry.ts
Normal 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);
|
||||
});
|
||||
};
|
@ -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";
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
@ -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();
|
||||
|
@ -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;
|
||||
|
64
src/crypto/emscLoader.d.ts
vendored
64
src/crypto/emscLoader.d.ts
vendored
@ -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;
|
||||
}
|
@ -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");
|
||||
}
|
102
src/crypto/nodeEmscriptenLoader.ts
Normal file
102
src/crypto/nodeEmscriptenLoader.ts
Normal 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'",
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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()});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user