(wasm) Treat arbitrary byte data as Uint8Array, instead of base64url marshalling.
This commit is contained in:
2
veilid-wasm/tests/.gitignore
vendored
2
veilid-wasm/tests/.gitignore
vendored
@@ -1,2 +1,2 @@
|
||||
node_modules
|
||||
veilid-wasm-pkg
|
||||
coverage
|
173
veilid-wasm/tests/src/VeilidRoutingContext.test.ts
Normal file
173
veilid-wasm/tests/src/VeilidRoutingContext.test.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { expect } from '@wdio/globals';
|
||||
|
||||
import {
|
||||
veilidCoreInitConfig,
|
||||
veilidCoreStartupConfig,
|
||||
} from './utils/veilid-config';
|
||||
|
||||
import {
|
||||
DHTRecordDescriptor,
|
||||
VeilidRoutingContext,
|
||||
veilidClient,
|
||||
veilidCrypto,
|
||||
} from 'veilid-wasm';
|
||||
import { textEncoder, textDecoder } from './utils/marshalling-utils';
|
||||
import { waitForMs } from './utils/wait-utils';
|
||||
|
||||
describe('VeilidRoutingContext', () => {
|
||||
before('veilid startup', async () => {
|
||||
veilidClient.initializeCore(veilidCoreInitConfig);
|
||||
await veilidClient.startupCore((_update) => {
|
||||
// if (_update.kind === 'Log') {
|
||||
// console.log(_update.message);
|
||||
// }
|
||||
}, JSON.stringify(veilidCoreStartupConfig));
|
||||
await veilidClient.attach();
|
||||
await waitForMs(2000);
|
||||
});
|
||||
|
||||
after('veilid shutdown', async () => {
|
||||
await veilidClient.detach();
|
||||
await veilidClient.shutdownCore();
|
||||
});
|
||||
|
||||
describe('constructors', () => {
|
||||
it('should create using .create()', async () => {
|
||||
const routingContext = VeilidRoutingContext.create();
|
||||
expect(routingContext instanceof VeilidRoutingContext).toBe(true);
|
||||
|
||||
routingContext.free();
|
||||
});
|
||||
|
||||
it('should create using new', async () => {
|
||||
const routingContext = new VeilidRoutingContext();
|
||||
expect(routingContext instanceof VeilidRoutingContext).toBe(true);
|
||||
|
||||
routingContext.free();
|
||||
});
|
||||
|
||||
it('should create with privacy', async () => {
|
||||
const routingContext = VeilidRoutingContext.create().withPrivacy();
|
||||
expect(routingContext instanceof VeilidRoutingContext).toBe(true);
|
||||
|
||||
routingContext.free();
|
||||
});
|
||||
|
||||
it('should create with custom privacy', async () => {
|
||||
const routingContext = VeilidRoutingContext.create().withCustomPrivacy({
|
||||
Safe: {
|
||||
hop_count: 2,
|
||||
sequencing: 'EnsureOrdered',
|
||||
stability: 'Reliable',
|
||||
},
|
||||
});
|
||||
expect(routingContext instanceof VeilidRoutingContext).toBe(true);
|
||||
|
||||
routingContext.free();
|
||||
});
|
||||
|
||||
it('should create with sequencing', async () => {
|
||||
const routingContext =
|
||||
VeilidRoutingContext.create().withSequencing('EnsureOrdered');
|
||||
expect(routingContext instanceof VeilidRoutingContext).toBe(true);
|
||||
|
||||
routingContext.free();
|
||||
});
|
||||
});
|
||||
|
||||
describe('operations', () => {
|
||||
let routingContext: VeilidRoutingContext;
|
||||
|
||||
before('create routing context', () => {
|
||||
routingContext = VeilidRoutingContext.create()
|
||||
.withPrivacy()
|
||||
.withSequencing('EnsureOrdered');
|
||||
});
|
||||
|
||||
after('free routing context', () => {
|
||||
routingContext.free();
|
||||
});
|
||||
|
||||
describe('DHT kitchen sink', async () => {
|
||||
let dhtRecord: DHTRecordDescriptor;
|
||||
const data = '🚀 This example DHT data with unicode a Ā 𐀀 文 🚀';
|
||||
|
||||
before('create dht record', async () => {
|
||||
const bestKind = veilidCrypto.bestCryptoKind();
|
||||
dhtRecord = await routingContext.createDhtRecord(
|
||||
{
|
||||
kind: 'DFLT',
|
||||
o_cnt: 1,
|
||||
},
|
||||
bestKind
|
||||
);
|
||||
|
||||
expect(dhtRecord.key).toBeDefined();
|
||||
expect(dhtRecord.owner).toBeDefined();
|
||||
expect(dhtRecord.owner_secret).toBeDefined();
|
||||
expect(dhtRecord.schema).toEqual({ kind: 'DFLT', o_cnt: 1 });
|
||||
});
|
||||
|
||||
after('free dht record', async () => {
|
||||
await routingContext.closeDhtRecord(dhtRecord.key);
|
||||
});
|
||||
|
||||
it('should set value', async () => {
|
||||
const setValueRes = await routingContext.setDhtValue(
|
||||
dhtRecord.key,
|
||||
0,
|
||||
textEncoder.encode(data)
|
||||
);
|
||||
expect(setValueRes).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should get value with force refresh', async () => {
|
||||
const getValueRes = await routingContext.getDhtValue(
|
||||
dhtRecord.key,
|
||||
0,
|
||||
true
|
||||
);
|
||||
expect(getValueRes?.data).toBeDefined();
|
||||
expect(textDecoder.decode(getValueRes?.data)).toBe(data);
|
||||
|
||||
expect(getValueRes?.writer).toBe(dhtRecord.owner);
|
||||
expect(getValueRes?.seq).toBe(0);
|
||||
});
|
||||
|
||||
it('should open readonly record', async () => {
|
||||
await routingContext.closeDhtRecord(dhtRecord.key);
|
||||
|
||||
const readonlyDhtRecord = await routingContext.openDhtRecord(
|
||||
dhtRecord.key
|
||||
);
|
||||
expect(readonlyDhtRecord).toBeDefined();
|
||||
|
||||
const setValueRes = routingContext.setDhtValue(
|
||||
dhtRecord.key,
|
||||
0,
|
||||
textEncoder.encode(data)
|
||||
);
|
||||
await expect(setValueRes).rejects.toEqual({
|
||||
kind: 'Generic',
|
||||
message: 'value is not writable',
|
||||
});
|
||||
});
|
||||
|
||||
it('should open writable record', async () => {
|
||||
await routingContext.closeDhtRecord(dhtRecord.key);
|
||||
|
||||
const writeableDhtRecord = await routingContext.openDhtRecord(
|
||||
dhtRecord.key,
|
||||
`${dhtRecord.owner}:${dhtRecord.owner_secret}`
|
||||
);
|
||||
expect(writeableDhtRecord).toBeDefined();
|
||||
const setValueRes = await routingContext.setDhtValue(
|
||||
dhtRecord.key,
|
||||
0,
|
||||
textEncoder.encode(`${data}👋`)
|
||||
);
|
||||
expect(setValueRes).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -6,7 +6,7 @@ import {
|
||||
} from './utils/veilid-config';
|
||||
|
||||
import { VeilidTableDB, veilidClient } from 'veilid-wasm';
|
||||
import { marshall, unmarshall } from './utils/marshalling-utils';
|
||||
import { textEncoder, textDecoder } from './utils/marshalling-utils';
|
||||
|
||||
const TABLE_NAME = 'some-table';
|
||||
const TABLE_COLS = 1;
|
||||
@@ -57,18 +57,22 @@ describe('VeilidTable', () => {
|
||||
const value = 'test value with unicode 🚀';
|
||||
|
||||
it('should store value', async () => {
|
||||
await table.store(0, marshall(key), marshall(value));
|
||||
await table.store(
|
||||
0,
|
||||
textEncoder.encode(key),
|
||||
textEncoder.encode(value)
|
||||
);
|
||||
});
|
||||
|
||||
it('should load value', async () => {
|
||||
const storedValue = await table.load(0, marshall(key));
|
||||
const storedValue = await table.load(0, textEncoder.encode(key));
|
||||
expect(storedValue).toBeDefined();
|
||||
expect(unmarshall(storedValue!)).toBe(value);
|
||||
expect(textDecoder.decode(storedValue!)).toBe(value);
|
||||
});
|
||||
|
||||
it('should have key in list of keys', async () => {
|
||||
const keys = await table.getKeys(0);
|
||||
const decodedKeys = keys.map(unmarshall);
|
||||
const decodedKeys = keys.map((key) => textDecoder.decode(key));
|
||||
expect(decodedKeys).toEqual([key]);
|
||||
});
|
||||
});
|
||||
@@ -82,15 +86,27 @@ describe('VeilidTable', () => {
|
||||
const second = 'second✔';
|
||||
const third = 'third📢';
|
||||
|
||||
transaction.store(0, marshall(key), marshall(first));
|
||||
transaction.store(0, marshall(key), marshall(second));
|
||||
transaction.store(0, marshall(key), marshall(third));
|
||||
transaction.store(
|
||||
0,
|
||||
textEncoder.encode(key),
|
||||
textEncoder.encode(first)
|
||||
);
|
||||
transaction.store(
|
||||
0,
|
||||
textEncoder.encode(key),
|
||||
textEncoder.encode(second)
|
||||
);
|
||||
transaction.store(
|
||||
0,
|
||||
textEncoder.encode(key),
|
||||
textEncoder.encode(third)
|
||||
);
|
||||
|
||||
await transaction.commit();
|
||||
|
||||
const storedValue = await table.load(0, marshall(key));
|
||||
const storedValue = await table.load(0, textEncoder.encode(key));
|
||||
expect(storedValue).toBeDefined();
|
||||
expect(unmarshall(storedValue!)).toBe(third);
|
||||
expect(textDecoder.decode(storedValue!)).toBe(third);
|
||||
|
||||
transaction.free();
|
||||
});
|
||||
|
@@ -1,13 +1,23 @@
|
||||
// TextEncoder/TextDecoder are used to solve for "The Unicode Problem" https://stackoverflow.com/a/30106551
|
||||
export const textDecoder = new TextDecoder();
|
||||
export const textEncoder = new TextEncoder();
|
||||
|
||||
export function marshall(data: string) {
|
||||
const byteString = bytesToString(new TextEncoder().encode(data));
|
||||
// TextEncoder/TextDecoder are used to solve for "The Unicode Problem" https://stackoverflow.com/a/30106551
|
||||
export function marshallString(data: string) {
|
||||
return marshallBytes(textEncoder.encode(data));
|
||||
}
|
||||
|
||||
export function unmarshallString(b64: string) {
|
||||
return textDecoder.decode(unmarshallBytes(b64));
|
||||
}
|
||||
|
||||
export function marshallBytes(data: Uint8Array) {
|
||||
const byteString = bytesToString(data);
|
||||
return base64UrlEncode(byteString);
|
||||
}
|
||||
|
||||
export function unmarshall(b64: string) {
|
||||
export function unmarshallBytes(b64: string) {
|
||||
const byteString = base64UrlDecode(b64);
|
||||
return new TextDecoder().decode(stringToBytes(byteString));
|
||||
return stringToBytes(byteString);
|
||||
}
|
||||
|
||||
function base64UrlEncode(data: string) {
|
||||
|
@@ -6,6 +6,7 @@ import {
|
||||
} from './utils/veilid-config';
|
||||
|
||||
import { veilidClient, veilidCrypto } from 'veilid-wasm';
|
||||
import { textEncoder, unmarshallBytes } from './utils/marshalling-utils';
|
||||
|
||||
describe('veilidCrypto', () => {
|
||||
before('veilid startup', async () => {
|
||||
@@ -29,10 +30,116 @@ describe('veilidCrypto', () => {
|
||||
expect(kinds.includes(bestKind)).toBe(true);
|
||||
});
|
||||
|
||||
it('should generate key pair', async () => {
|
||||
it('should generate key pair', () => {
|
||||
const bestKind = veilidCrypto.bestCryptoKind();
|
||||
const keypair = veilidCrypto.generateKeyPair(bestKind);
|
||||
expect(typeof keypair).toBe('string');
|
||||
// TODO: fix TypeScript return type of generateKeyPair to return string instead of KeyPair
|
||||
|
||||
const [publicKey, secretKey] = keypair.split(':');
|
||||
expect(unmarshallBytes(publicKey).length).toBe(32);
|
||||
expect(unmarshallBytes(secretKey).length).toBe(32);
|
||||
|
||||
const isValid = veilidCrypto.validateKeyPair(
|
||||
bestKind,
|
||||
publicKey,
|
||||
secretKey
|
||||
);
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('should generate random bytes', () => {
|
||||
const bestKind = veilidCrypto.bestCryptoKind();
|
||||
const bytes = veilidCrypto.randomBytes(bestKind, 64);
|
||||
expect(bytes instanceof Uint8Array).toBe(true);
|
||||
expect(bytes.length).toBe(64);
|
||||
});
|
||||
|
||||
it('should hash data and validate hash', () => {
|
||||
const bestKind = veilidCrypto.bestCryptoKind();
|
||||
const data = textEncoder.encode('this is my data🚀');
|
||||
const hash = veilidCrypto.generateHash(bestKind, data);
|
||||
|
||||
expect(hash).toBeDefined();
|
||||
expect(typeof hash).toBe('string');
|
||||
|
||||
const isValid = veilidCrypto.validateHash(bestKind, data, hash);
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('should hash and validate password', () => {
|
||||
const bestKind = veilidCrypto.bestCryptoKind();
|
||||
|
||||
const password = textEncoder.encode('this is my data🚀');
|
||||
const saltLength = veilidCrypto.defaultSaltLength(bestKind);
|
||||
expect(saltLength).toBeGreaterThan(0);
|
||||
|
||||
const salt = veilidCrypto.randomBytes(bestKind, saltLength);
|
||||
expect(salt instanceof Uint8Array).toBe(true);
|
||||
expect(salt.length).toBe(saltLength);
|
||||
|
||||
const hash = veilidCrypto.hashPassword(bestKind, password, salt);
|
||||
expect(hash).toBeDefined();
|
||||
expect(typeof hash).toBe('string');
|
||||
|
||||
const isValid = veilidCrypto.verifyPassword(bestKind, password, hash);
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('should aead encrypt and decrypt', () => {
|
||||
const bestKind = veilidCrypto.bestCryptoKind();
|
||||
const body = textEncoder.encode(
|
||||
'This is an encoded body with my secret data in it🔥'
|
||||
);
|
||||
const ad = textEncoder.encode(
|
||||
'This is data associated with my secret data👋'
|
||||
);
|
||||
|
||||
const nonce = veilidCrypto.randomNonce(bestKind);
|
||||
expect(typeof nonce).toBe('string');
|
||||
|
||||
const sharedSecred = veilidCrypto.randomSharedSecret(bestKind);
|
||||
expect(typeof sharedSecred).toBe('string');
|
||||
|
||||
const encBody = veilidCrypto.encryptAead(
|
||||
bestKind,
|
||||
body,
|
||||
nonce,
|
||||
sharedSecred,
|
||||
ad
|
||||
);
|
||||
expect(encBody instanceof Uint8Array).toBe(true);
|
||||
|
||||
const overhead = veilidCrypto.aeadOverhead(bestKind);
|
||||
expect(encBody.length - body.length).toBe(overhead);
|
||||
|
||||
const decBody = veilidCrypto.decryptAead(
|
||||
bestKind,
|
||||
encBody,
|
||||
nonce,
|
||||
sharedSecred,
|
||||
ad
|
||||
);
|
||||
expect(decBody instanceof Uint8Array).toBe(true);
|
||||
expect(body).toEqual(decBody);
|
||||
});
|
||||
|
||||
it('should sign and verify', () => {
|
||||
const bestKind = veilidCrypto.bestCryptoKind();
|
||||
const keypair = veilidCrypto.generateKeyPair(bestKind);
|
||||
const data = textEncoder.encode(
|
||||
'This is some data I am signing with my key 🔑'
|
||||
);
|
||||
expect(typeof keypair).toBe('string');
|
||||
|
||||
const [publicKey, secretKey] = keypair.split(':');
|
||||
|
||||
const sig = veilidCrypto.sign(bestKind, publicKey, secretKey, data);
|
||||
expect(typeof sig).toBe('string');
|
||||
|
||||
expect(() => {
|
||||
const res = veilidCrypto.verify(bestKind, publicKey, data, sig);
|
||||
expect(res).toBeUndefined();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -5,7 +5,20 @@ export const config: Options.Testrunner = {
|
||||
// Runner Configuration
|
||||
// ====================
|
||||
// WebdriverIO supports running e2e tests as well as unit and component tests.
|
||||
runner: ['browser', { viteConfig: './vite.config.ts' }],
|
||||
runner: [
|
||||
'browser',
|
||||
{
|
||||
viteConfig: './vite.config.ts',
|
||||
coverage: {
|
||||
enabled: true,
|
||||
// needed since the ../pkg directory that has the compiled wasm npm package
|
||||
// is outside the current directory. Coverage is only collected on files
|
||||
// that are in within `cwd`.
|
||||
cwd: '..',
|
||||
include: ['pkg/**'],
|
||||
},
|
||||
},
|
||||
],
|
||||
autoCompileOpts: {
|
||||
autoCompile: true,
|
||||
tsNodeOpts: {
|
||||
|
Reference in New Issue
Block a user