mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2024-12-05 02:10:18 +00:00
Add built-in service (Sig) which signs data and verifies signatures (#110)
This commit is contained in:
parent
25da21aeeb
commit
48fc017a1b
14
package-lock.json
generated
14
package-lock.json
generated
@ -12,7 +12,6 @@
|
||||
"@chainsafe/libp2p-noise": "4.0.0",
|
||||
"@fluencelabs/avm": "^0.17.6",
|
||||
"async": "3.2.0",
|
||||
"base64-js": "1.5.1",
|
||||
"bs58": "4.0.1",
|
||||
"cids": "0.8.1",
|
||||
"it-length-prefixed": "3.0.1",
|
||||
@ -31,6 +30,7 @@
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.22",
|
||||
"jest": "^26.6.3",
|
||||
"js-base64": "^3.7.2",
|
||||
"ts-jest": "^26.5.4",
|
||||
"typedoc": "^0.21.9",
|
||||
"typescript": "^4.0.0"
|
||||
@ -4668,6 +4668,12 @@
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/js-base64": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz",
|
||||
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@ -11936,6 +11942,12 @@
|
||||
"supports-color": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"js-base64": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz",
|
||||
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
|
@ -23,7 +23,6 @@
|
||||
"@chainsafe/libp2p-noise": "4.0.0",
|
||||
"@fluencelabs/avm": "^0.17.6",
|
||||
"async": "3.2.0",
|
||||
"base64-js": "1.5.1",
|
||||
"bs58": "4.0.1",
|
||||
"cids": "0.8.1",
|
||||
"it-length-prefixed": "3.0.1",
|
||||
@ -44,6 +43,7 @@
|
||||
"jest": "^26.6.3",
|
||||
"ts-jest": "^26.5.4",
|
||||
"typedoc": "^0.21.9",
|
||||
"typescript": "^4.0.0"
|
||||
"typescript": "^4.0.0",
|
||||
"js-base64": "^3.7.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,36 @@
|
||||
import each from 'jest-each';
|
||||
import { CallServiceData } from '../../internal/commonTypes';
|
||||
import { defaultServices } from '../../internal/defaultServices';
|
||||
import each from 'jest-each';
|
||||
import { BuiltInServiceContext, builtInServices } from '../../internal/builtInServices';
|
||||
import { KeyPair } from '../../internal/KeyPair';
|
||||
import { toUint8Array } from 'js-base64';
|
||||
|
||||
const key = '+cmeYlZKj+MfSa9dpHV+BmLPm6wq4inGlsPlQ1GvtPk=';
|
||||
|
||||
const context = (async () => {
|
||||
const keyBytes = toUint8Array(key);
|
||||
const kp = await KeyPair.fromEd25519SK(keyBytes);
|
||||
const res: BuiltInServiceContext = {
|
||||
peerKeyPair: kp,
|
||||
peerId: kp.Libp2pPeerId.toB58String(),
|
||||
};
|
||||
return res;
|
||||
})();
|
||||
|
||||
const testData = [1, 2, 3, 4, 5, 6, 7, 9, 10];
|
||||
|
||||
// signature produced by KeyPair created from key above (`key` variable)
|
||||
const testDataSig = [
|
||||
224, 104, 245, 206, 140, 248, 27, 72, 68, 133, 111, 10, 164, 197, 242, 132, 107, 77, 224, 67, 99, 106, 76, 29, 144,
|
||||
121, 122, 169, 36, 173, 58, 80, 170, 102, 137, 253, 157, 247, 168, 87, 162, 223, 188, 214, 203, 220, 52, 246, 29,
|
||||
86, 77, 71, 224, 248, 16, 213, 254, 75, 78, 239, 243, 222, 241, 15,
|
||||
];
|
||||
|
||||
// signature produced by KeyPair created from some random KeyPair
|
||||
const testDataWrongSig = [
|
||||
116, 247, 189, 118, 236, 53, 147, 123, 219, 75, 176, 105, 101, 108, 233, 137, 97, 14, 146, 132, 252, 70, 51, 153,
|
||||
237, 167, 156, 150, 36, 90, 229, 108, 166, 231, 255, 137, 8, 246, 125, 0, 213, 150, 83, 196, 237, 221, 131, 159,
|
||||
157, 159, 25, 109, 95, 160, 181, 65, 254, 238, 47, 156, 240, 151, 58, 14,
|
||||
];
|
||||
|
||||
describe('Tests for default handler', () => {
|
||||
// prettier-ignore
|
||||
@ -36,6 +66,10 @@ describe('Tests for default handler', () => {
|
||||
${'peer'} | ${'timeout'} | ${[]} | ${1} | ${'timeout accepts exactly two arguments: timeout duration in ms and a message string'}}
|
||||
${'peer'} | ${'timeout'} | ${[200, 'test', 1]} | ${1} | ${'timeout accepts exactly two arguments: timeout duration in ms and a message string'}}
|
||||
|
||||
${'sig'} | ${'verify'} | ${[testData, testDataSig]} | ${0} | ${true}}
|
||||
${'sig'} | ${'verify'} | ${[testData, testDataWrongSig]} | ${0} | ${false}}
|
||||
${'sig'} | ${'sign'} | ${[]} | ${1} | ${'sign accepts exactly one argument: data be signed in format of u8 array of bytes'}}
|
||||
${'sig'} | ${'verify'} | ${[testData]} | ${1} | ${'verify accepts exactly two arguments: data and signature, both in format of u8 array of bytes'}}
|
||||
`.test(
|
||||
//
|
||||
'$fnName with $args expected retcode: $retCode and result: $result',
|
||||
@ -56,7 +90,7 @@ describe('Tests for default handler', () => {
|
||||
};
|
||||
|
||||
// act
|
||||
const fn = defaultServices[req.serviceId][req.fnName];
|
||||
const fn = builtInServices(await context)[req.serviceId][req.fnName];
|
||||
const res = await fn(req);
|
||||
|
||||
// assert
|
||||
@ -84,7 +118,7 @@ describe('Tests for default handler', () => {
|
||||
};
|
||||
|
||||
// act
|
||||
const fn = defaultServices[req.serviceId][req.fnName];
|
||||
const fn = builtInServices(await context)[req.serviceId][req.fnName];
|
||||
const res = await fn(req);
|
||||
|
||||
// assert
|
||||
@ -93,4 +127,166 @@ describe('Tests for default handler', () => {
|
||||
result: 'The JS implementation of Peer does not support identify',
|
||||
});
|
||||
});
|
||||
|
||||
it('sig.sign should create the correct signature', async () => {
|
||||
// arrange
|
||||
const ctx = await context;
|
||||
const req: CallServiceData = {
|
||||
serviceId: 'sig',
|
||||
fnName: 'sign',
|
||||
args: [testData],
|
||||
tetraplets: [
|
||||
[
|
||||
{
|
||||
function_name: 'get_trust_bytes',
|
||||
json_path: '',
|
||||
peer_pk: '',
|
||||
service_id: 'trust-graph',
|
||||
},
|
||||
],
|
||||
],
|
||||
particleContext: {
|
||||
particleId: 'some',
|
||||
initPeerId: ctx.peerId,
|
||||
timestamp: 595951200,
|
||||
ttl: 595961200,
|
||||
signature: 'sig',
|
||||
},
|
||||
};
|
||||
|
||||
// act
|
||||
const fn = builtInServices(ctx)[req.serviceId][req.fnName];
|
||||
const res = await fn(req);
|
||||
|
||||
// assert
|
||||
expect(res).toMatchObject({
|
||||
retCode: 0,
|
||||
result: testDataSig,
|
||||
});
|
||||
});
|
||||
|
||||
it('sign-verify call chain should work', async () => {
|
||||
const ctx = await context;
|
||||
const signReq: CallServiceData = {
|
||||
serviceId: 'sig',
|
||||
fnName: 'sign',
|
||||
args: [testData],
|
||||
tetraplets: [
|
||||
[
|
||||
{
|
||||
function_name: 'get_trust_bytes',
|
||||
json_path: '',
|
||||
peer_pk: '',
|
||||
service_id: 'trust-graph',
|
||||
},
|
||||
],
|
||||
],
|
||||
particleContext: {
|
||||
particleId: 'some',
|
||||
initPeerId: ctx.peerId,
|
||||
timestamp: 595951200,
|
||||
ttl: 595961200,
|
||||
signature: 'sig',
|
||||
},
|
||||
};
|
||||
|
||||
const signFn = builtInServices(ctx)[signReq.serviceId][signReq.fnName];
|
||||
const signRes = await signFn(signReq);
|
||||
|
||||
const verifyReq: CallServiceData = {
|
||||
serviceId: 'sig',
|
||||
fnName: 'verify',
|
||||
args: [testData, signRes.result],
|
||||
tetraplets: [],
|
||||
particleContext: {
|
||||
particleId: 'some',
|
||||
initPeerId: ctx.peerId,
|
||||
timestamp: 595951200,
|
||||
ttl: 595961200,
|
||||
signature: 'sig',
|
||||
},
|
||||
};
|
||||
|
||||
const verifyFn = builtInServices(ctx)[verifyReq.serviceId][verifyReq.fnName];
|
||||
const verifyRes = await verifyFn(verifyReq);
|
||||
|
||||
expect(verifyRes).toMatchObject({
|
||||
retCode: 0,
|
||||
result: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('sig.sign should not allow data from incorrect services', async () => {
|
||||
// arrange
|
||||
const ctx = await context;
|
||||
const req: CallServiceData = {
|
||||
serviceId: 'sig',
|
||||
fnName: 'sign',
|
||||
args: [testData],
|
||||
tetraplets: [
|
||||
[
|
||||
{
|
||||
function_name: 'some-other-fn',
|
||||
json_path: '',
|
||||
peer_pk: '',
|
||||
service_id: 'cool-service',
|
||||
},
|
||||
],
|
||||
],
|
||||
particleContext: {
|
||||
particleId: 'some',
|
||||
initPeerId: ctx.peerId,
|
||||
timestamp: 595951200,
|
||||
ttl: 595961200,
|
||||
signature: 'sig',
|
||||
},
|
||||
};
|
||||
|
||||
// act
|
||||
const fn = builtInServices(ctx)[req.serviceId][req.fnName];
|
||||
const res = await fn(req);
|
||||
|
||||
// assert
|
||||
expect(res).toMatchObject({
|
||||
retCode: 1,
|
||||
result: expect.stringContaining("Only data from the following services is allowed to be signed:"),
|
||||
});
|
||||
});
|
||||
|
||||
it('sig.sign should not allow particles initiated from other peers', async () => {
|
||||
// arrange
|
||||
const ctx = await context;
|
||||
const req: CallServiceData = {
|
||||
serviceId: 'sig',
|
||||
fnName: 'sign',
|
||||
args: [testData],
|
||||
tetraplets: [
|
||||
[
|
||||
{
|
||||
function_name: 'some-other-fn',
|
||||
json_path: '',
|
||||
peer_pk: '',
|
||||
service_id: 'cool-service',
|
||||
},
|
||||
],
|
||||
],
|
||||
particleContext: {
|
||||
particleId: 'some',
|
||||
initPeerId: (await KeyPair.randomEd25519()).Libp2pPeerId.toB58String(),
|
||||
timestamp: 595951200,
|
||||
ttl: 595961200,
|
||||
signature: 'sig',
|
||||
},
|
||||
};
|
||||
|
||||
// act
|
||||
const fn = builtInServices(ctx)[req.serviceId][req.fnName];
|
||||
const res = await fn(req);
|
||||
|
||||
// assert
|
||||
expect(res).toMatchObject({
|
||||
retCode: 1,
|
||||
result: 'sign is only allowed to be called on the same peer the particle was initiated from',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -33,7 +33,7 @@ import { createInterpreter, dataToString } from './utils';
|
||||
import { filter, pipe, Subject, tap } from 'rxjs';
|
||||
import { RequestFlow } from './compilerSupport/v1';
|
||||
import log from 'loglevel';
|
||||
import { defaultServices } from './defaultServices';
|
||||
import { BuiltInServiceContext, builtInServices } from './builtInServices';
|
||||
import { instanceOf } from 'ts-pattern';
|
||||
|
||||
/**
|
||||
@ -210,7 +210,10 @@ export class FluencePeer {
|
||||
}
|
||||
|
||||
this._legacyCallServiceHandler = new LegacyCallServiceHandler();
|
||||
registerDefaultServices(this);
|
||||
registerDefaultServices(this, {
|
||||
peerKeyPair: this._keyPair,
|
||||
peerId: this.getStatus().peerId,
|
||||
});
|
||||
|
||||
this._startParticleProcessing();
|
||||
}
|
||||
@ -459,7 +462,7 @@ export class FluencePeer {
|
||||
this._execSingleCallRequest(req)
|
||||
.catch(
|
||||
(err): CallServiceResult => ({
|
||||
retCode: ResultCodes.exceptionInHandler,
|
||||
retCode: ResultCodes.error,
|
||||
result: `Handler failed. fnName="${req.fnName}" serviceId="${
|
||||
req.serviceId
|
||||
}" error: ${err.toString()}`,
|
||||
@ -529,7 +532,7 @@ export class FluencePeer {
|
||||
res = handler
|
||||
? await handler(req)
|
||||
: {
|
||||
retCode: ResultCodes.unknownError,
|
||||
retCode: ResultCodes.error,
|
||||
result: `No handler has been registered for serviceId='${req.serviceId}' fnName='${req.fnName}' args='${req.args}'`,
|
||||
};
|
||||
}
|
||||
@ -576,10 +579,11 @@ function serviceFnKey(serviceId: string, fnName: string) {
|
||||
return `${serviceId}/${fnName}`;
|
||||
}
|
||||
|
||||
function registerDefaultServices(peer: FluencePeer) {
|
||||
for (let serviceId in defaultServices) {
|
||||
for (let fnName in defaultServices[serviceId]) {
|
||||
const h = defaultServices[serviceId][fnName];
|
||||
function registerDefaultServices(peer: FluencePeer, context: BuiltInServiceContext) {
|
||||
const ctx = builtInServices(context);
|
||||
for (let serviceId in ctx) {
|
||||
for (let fnName in ctx[serviceId]) {
|
||||
const h = ctx[serviceId][fnName];
|
||||
peer.internals.regHandler.common(serviceId, fnName, h);
|
||||
}
|
||||
}
|
||||
|
@ -54,4 +54,12 @@ export class KeyPair {
|
||||
toEd25519PrivateKey(): Uint8Array {
|
||||
return this.Libp2pPeerId.privKey.marshal().subarray(0, 32);
|
||||
}
|
||||
|
||||
signBytes(data: Uint8Array): Promise<Uint8Array> {
|
||||
return this.Libp2pPeerId.privKey.sign(data);
|
||||
}
|
||||
|
||||
verify(data: Uint8Array, signature: Uint8Array): Promise<boolean> {
|
||||
return this.Libp2pPeerId.privKey.public.verify(data, signature);
|
||||
}
|
||||
}
|
||||
|
175
src/internal/builtInServices.ts
Normal file
175
src/internal/builtInServices.ts
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2021 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CallServiceResult } from '@fluencelabs/avm';
|
||||
import { encode, decode } from 'bs58';
|
||||
import { PeerIdB58 } from 'src';
|
||||
import { GenericCallServiceHandler, ResultCodes } from './commonTypes';
|
||||
import { KeyPair } from './KeyPair';
|
||||
|
||||
const success = (result: any): CallServiceResult => {
|
||||
return {
|
||||
result: result,
|
||||
retCode: ResultCodes.success,
|
||||
};
|
||||
};
|
||||
|
||||
const error = (error: string): CallServiceResult => {
|
||||
return {
|
||||
result: error,
|
||||
retCode: ResultCodes.error,
|
||||
};
|
||||
};
|
||||
|
||||
export interface BuiltInServiceContext {
|
||||
peerKeyPair: KeyPair;
|
||||
peerId: PeerIdB58;
|
||||
}
|
||||
|
||||
export function builtInServices(context: BuiltInServiceContext): {
|
||||
[serviceId in string]: { [fnName in string]: GenericCallServiceHandler };
|
||||
} {
|
||||
return {
|
||||
op: {
|
||||
noop: (req) => {
|
||||
return success({});
|
||||
},
|
||||
|
||||
array: (req) => {
|
||||
return success(req.args);
|
||||
},
|
||||
|
||||
identity: (req) => {
|
||||
if (req.args.length > 1) {
|
||||
return error(`identity accepts up to 1 arguments, received ${req.args.length} arguments`);
|
||||
} else {
|
||||
return success(req.args.length === 0 ? {} : req.args[0]);
|
||||
}
|
||||
},
|
||||
|
||||
concat: (req) => {
|
||||
const incorrectArgIndices = req.args //
|
||||
.map((x, i) => [Array.isArray(x), i])
|
||||
.filter(([isArray, _]) => !isArray)
|
||||
.map(([_, index]) => index);
|
||||
|
||||
if (incorrectArgIndices.length > 0) {
|
||||
const str = incorrectArgIndices.join(', ');
|
||||
return error(`All arguments of 'concat' must be arrays: arguments ${str} are not`);
|
||||
} else {
|
||||
return success([].concat.apply([], req.args));
|
||||
}
|
||||
},
|
||||
|
||||
string_to_b58: (req) => {
|
||||
if (req.args.length !== 1) {
|
||||
return error('string_to_b58 accepts only one string argument');
|
||||
} else {
|
||||
return success(encode(new TextEncoder().encode(req.args[0])));
|
||||
}
|
||||
},
|
||||
|
||||
string_from_b58: (req) => {
|
||||
if (req.args.length !== 1) {
|
||||
return error('string_from_b58 accepts only one string argument');
|
||||
} else {
|
||||
return success(new TextDecoder().decode(decode(req.args[0])));
|
||||
}
|
||||
},
|
||||
|
||||
bytes_to_b58: (req) => {
|
||||
if (req.args.length !== 1 || !Array.isArray(req.args[0])) {
|
||||
return error('bytes_to_b58 accepts only single argument: array of numbers');
|
||||
} else {
|
||||
const argumentArray = req.args[0] as number[];
|
||||
return success(encode(new Uint8Array(argumentArray)));
|
||||
}
|
||||
},
|
||||
|
||||
bytes_from_b58: (req) => {
|
||||
if (req.args.length !== 1) {
|
||||
return error('bytes_from_b58 accepts only one string argument');
|
||||
} else {
|
||||
return success(Array.from(decode(req.args[0])));
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
peer: {
|
||||
timeout: (req) => {
|
||||
if (req.args.length !== 2) {
|
||||
return error('timeout accepts exactly two arguments: timeout duration in ms and a message string');
|
||||
}
|
||||
const durationMs = req.args[0];
|
||||
const message = req.args[1];
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const res = success(message);
|
||||
resolve(res);
|
||||
}, durationMs);
|
||||
});
|
||||
},
|
||||
|
||||
identify: (req) => {
|
||||
return error('The JS implementation of Peer does not support identify');
|
||||
},
|
||||
},
|
||||
|
||||
sig: {
|
||||
sign: async (req) => {
|
||||
if (req.args.length !== 1) {
|
||||
return error('sign accepts exactly one argument: data be signed in format of u8 array of bytes');
|
||||
}
|
||||
|
||||
if (req.particleContext.initPeerId !== context.peerId) {
|
||||
return error('sign is only allowed to be called on the same peer the particle was initiated from');
|
||||
}
|
||||
|
||||
const t = req.tetraplets[0][0];
|
||||
const serviceFnPair = `${t.service_id}.${t.function_name}`;
|
||||
|
||||
const allowedServices = [
|
||||
'trust-graph.get_trust_bytes',
|
||||
'trust-graph.get_revocation_bytes',
|
||||
'registry.get_key_bytes',
|
||||
'registry.get_record_bytes',
|
||||
];
|
||||
|
||||
if (allowedServices.indexOf(serviceFnPair) === -1) {
|
||||
return error(
|
||||
'Only data from the following services is allowed to be signed: ' + allowedServices.join(', '),
|
||||
);
|
||||
}
|
||||
|
||||
const [data] = req.args;
|
||||
const signedData = await context.peerKeyPair.signBytes(Uint8Array.from(data));
|
||||
return success(Array.from(signedData));
|
||||
},
|
||||
|
||||
verify: async (req) => {
|
||||
if (req.args.length !== 2) {
|
||||
return error(
|
||||
'verify accepts exactly two arguments: data and signature, both in format of u8 array of bytes',
|
||||
);
|
||||
}
|
||||
const [data, signature] = req.args;
|
||||
const result = await context.peerKeyPair.verify(Uint8Array.from(data), Uint8Array.from(signature));
|
||||
return success(result);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
@ -59,8 +59,7 @@ export interface CallParams<ArgName extends string | null> {
|
||||
|
||||
export enum ResultCodes {
|
||||
success = 0,
|
||||
unknownError = 1,
|
||||
exceptionInHandler = 2,
|
||||
error = 1,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,7 +37,7 @@ export const callLegacyCallServiceHandler = (
|
||||
|
||||
if (res.retCode === undefined) {
|
||||
res = {
|
||||
retCode: ResultCodes.unknownError,
|
||||
retCode: ResultCodes.error,
|
||||
result: `The handler did not set any result. Make sure you are calling the right peer and the handler has been registered. Original request data was: serviceId='${req.serviceId}' fnName='${req.fnName}' args='${req.args}'`,
|
||||
};
|
||||
}
|
||||
|
@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CallServiceResult } from '@fluencelabs/avm';
|
||||
import { encode, decode } from 'bs58';
|
||||
import { GenericCallServiceHandler, ResultCodes } from './commonTypes';
|
||||
|
||||
const success = (result: any): CallServiceResult => {
|
||||
return {
|
||||
result: result,
|
||||
retCode: ResultCodes.success,
|
||||
};
|
||||
};
|
||||
|
||||
const error = (error: string): CallServiceResult => {
|
||||
return {
|
||||
result: error,
|
||||
retCode: ResultCodes.unknownError,
|
||||
};
|
||||
};
|
||||
|
||||
export const defaultServices: { [serviceId in string]: { [fnName in string]: GenericCallServiceHandler } } = {
|
||||
op: {
|
||||
noop: (req) => {
|
||||
return success({});
|
||||
},
|
||||
|
||||
array: (req) => {
|
||||
return success(req.args);
|
||||
},
|
||||
|
||||
identity: (req) => {
|
||||
if (req.args.length > 1) {
|
||||
return error(`identity accepts up to 1 arguments, received ${req.args.length} arguments`);
|
||||
} else {
|
||||
return success(req.args.length === 0 ? {} : req.args[0]);
|
||||
}
|
||||
},
|
||||
|
||||
concat: (req) => {
|
||||
const incorrectArgIndices = req.args //
|
||||
.map((x, i) => [Array.isArray(x), i])
|
||||
.filter(([isArray, _]) => !isArray)
|
||||
.map(([_, index]) => index);
|
||||
|
||||
if (incorrectArgIndices.length > 0) {
|
||||
const str = incorrectArgIndices.join(', ');
|
||||
return error(`All arguments of 'concat' must be arrays: arguments ${str} are not`);
|
||||
} else {
|
||||
return success([].concat.apply([], req.args));
|
||||
}
|
||||
},
|
||||
|
||||
string_to_b58: (req) => {
|
||||
if (req.args.length !== 1) {
|
||||
return error('string_to_b58 accepts only one string argument');
|
||||
} else {
|
||||
return success(encode(new TextEncoder().encode(req.args[0])));
|
||||
}
|
||||
},
|
||||
|
||||
string_from_b58: (req) => {
|
||||
if (req.args.length !== 1) {
|
||||
return error('string_from_b58 accepts only one string argument');
|
||||
} else {
|
||||
return success(new TextDecoder().decode(decode(req.args[0])));
|
||||
}
|
||||
},
|
||||
|
||||
bytes_to_b58: (req) => {
|
||||
if (req.args.length !== 1 || !Array.isArray(req.args[0])) {
|
||||
return error('bytes_to_b58 accepts only single argument: array of numbers');
|
||||
} else {
|
||||
const argumentArray = req.args[0] as number[];
|
||||
return success(encode(new Uint8Array(argumentArray)));
|
||||
}
|
||||
},
|
||||
|
||||
bytes_from_b58: (req) => {
|
||||
if (req.args.length !== 1) {
|
||||
return error('bytes_from_b58 accepts only one string argument');
|
||||
} else {
|
||||
return success(Array.from(decode(req.args[0])));
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
peer: {
|
||||
timeout: (req) => {
|
||||
if (req.args.length !== 2) {
|
||||
return error('timeout accepts exactly two arguments: timeout duration in ms and a message string');
|
||||
}
|
||||
const durationMs = req.args[0];
|
||||
const message = req.args[1];
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const res = success(message);
|
||||
resolve(res);
|
||||
}, durationMs);
|
||||
});
|
||||
},
|
||||
|
||||
identify: (req) => {
|
||||
return error('The JS implementation of Peer does not support identify');
|
||||
},
|
||||
},
|
||||
};
|
Loading…
Reference in New Issue
Block a user