67 lines
4.6 KiB
TypeScript
67 lines
4.6 KiB
TypeScript
import 'fake-indexeddb/auto';
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { xml } from '@xmpp/client';
|
|
import { OmemoDB } from '../src/crypto/omemo-db.ts';
|
|
import { acceptDeviceListEvent, beginDeviceConversation, cachedDeviceList, DEVICE_LIST_MAX_AGE, invalidateDeviceLists, SEND_BURST_GAP } from '../src/crypto/omemo.ts';
|
|
import { buildDeviceList, deviceListEvent } from '../src/crypto/omemo-wire.ts';
|
|
const ns = 'eu.siacs.conversations.axolotl';
|
|
const result = (ids: number[]) => xml('pubsub', {xmlns:'http://jabber.org/protocol/pubsub'}, xml('items', {node:ns+'.devicelist'}, xml('item', {id:'current'}, buildDeviceList(ids))));
|
|
|
|
test('device events require the direct bare publisher and a valid legacy payload, including explicit empty', () => {
|
|
const event = (from: string, list = buildDeviceList([42])) => xml('message', {from}, xml('event', {xmlns:'http://jabber.org/protocol/pubsub#event'}, xml('items', {node:ns+'.devicelist'}, xml('item', {id:'current'}, list))));
|
|
assert.deepEqual(deviceListEvent(event('peer@example.org')), {peer:'peer@example.org', ids:[42]});
|
|
assert.deepEqual(deviceListEvent(event('peer@example.org', buildDeviceList([]))), {peer:'peer@example.org', ids:[]});
|
|
for (const from of ['', 'peer@example.org/resource', 'example.org', 'other peer@example.org']) assert.equal(deviceListEvent(event(from)), undefined);
|
|
for (const id of ['0', '-1', 'NaN', '2147483648']) assert.equal(deviceListEvent(event('peer@example.org', xml('list', {xmlns:ns}, xml('device', {id})))), undefined);
|
|
assert.equal(deviceListEvent(event('peer@example.org', xml('list', {xmlns:'wrong'}))), undefined);
|
|
assert.equal(deviceListEvent(xml('message', {from:'forwarder@example.org'}, xml('forwarded', null, event('peer@example.org')))), undefined);
|
|
});
|
|
|
|
test('freshness, non-roster bursts, reconnect, coalescing, failures and account isolation', async (t) => {
|
|
const db = await OmemoDB.open('freshness@example.org');
|
|
const other = await OmemoDB.open('another@example.org');
|
|
const peer = 'non-roster@example.org';
|
|
let calls = 0, ids = [42], fail = false;
|
|
const xmpp: any = {iqCaller:{get:async () => { calls++; if(fail) throw Error('offline'); return result(ids); }}};
|
|
let now = Date.now(); t.mock.method(Date, 'now', () => now);
|
|
try {
|
|
assert.deepEqual(await cachedDeviceList(xmpp, db, peer), [42]); assert.equal(calls, 1);
|
|
await Promise.all(Array.from({length:8}, () => cachedDeviceList(xmpp, db, peer, {forSend:true})));
|
|
assert.equal(calls, 2); // first send refreshes even a UI-warmed non-roster list
|
|
await cachedDeviceList(xmpp, db, peer, {forSend:true}); assert.equal(calls, 2);
|
|
now += SEND_BURST_GAP; ids = [42,43];
|
|
assert.deepEqual(await cachedDeviceList(xmpp, db, peer, {forSend:true}), ids); assert.equal(calls, 3);
|
|
beginDeviceConversation(xmpp, db, peer);
|
|
await cachedDeviceList(xmpp, db, peer, {forSend:true}); assert.equal(calls, 4);
|
|
await acceptDeviceListEvent(xmpp, db, peer, [43], true);
|
|
assert.deepEqual(await cachedDeviceList(xmpp, db, peer), [43]); assert.equal(calls, 4);
|
|
now += DEVICE_LIST_MAX_AGE; ids = [44];
|
|
assert.deepEqual(await cachedDeviceList(xmpp, db, peer), [44]); assert.equal(calls, 5);
|
|
invalidateDeviceLists(xmpp); ids = [45];
|
|
assert.deepEqual(await cachedDeviceList(xmpp, db, peer), [45]); assert.equal(calls, 6);
|
|
await cachedDeviceList(xmpp, other, peer); assert.equal(calls, 7);
|
|
fail = true;
|
|
await assert.rejects(cachedDeviceList(xmpp, db, peer, {force:true,forSend:true}), /offline/);
|
|
assert.deepEqual((await db.getCachedDevices(peer))?.deviceIds, [45]);
|
|
fail = false; await acceptDeviceListEvent(xmpp, db, peer, [], true);
|
|
await assert.rejects(cachedDeviceList(xmpp, db, peer, {forSend:true}), /no current encryption devices/);
|
|
const malformed: any = {iqCaller:{get:async () => xml('pubsub', null, xml('items', null, xml('item')))}};
|
|
await assert.rejects(cachedDeviceList(malformed, db, peer), /Invalid/);
|
|
assert.deepEqual((await db.getCachedDevices(peer))?.deviceIds, []);
|
|
} finally { db.close(); other.close(); }
|
|
});
|
|
|
|
test('an event wins against an older concurrent fetch without discarding ratchets', async () => {
|
|
const db = await OmemoDB.open('racing@example.org');
|
|
let resolve!: (value: any) => void;
|
|
const xmpp: any = {iqCaller:{get:() => new Promise(r => {resolve = r;})}};
|
|
try {
|
|
await db.putSession('peer:42:legacy', 'keep ratchet');
|
|
const fetch = cachedDeviceList(xmpp, db, 'peer@example.org');
|
|
while (!resolve) await new Promise(r => setTimeout(r, 1));
|
|
await acceptDeviceListEvent(xmpp, db, 'peer@example.org', [43], true);
|
|
resolve(result([42])); assert.deepEqual(await fetch, [43]);
|
|
assert.equal(await db.getSession('peer:42:legacy'), 'keep ratchet');
|
|
} finally {db.close();}
|
|
});
|