From 9c6259506f601d67f92c8a89f3c8808951909cb8 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 29 Dec 2023 11:23:03 +0000 Subject: [PATCH] Include "needsBackup" flag in inbound group session batches --- spec/unit/crypto/store/CryptoStore.spec.ts | 8 +++ src/crypto/store/base.ts | 7 ++- .../store/indexeddb-crypto-store-backend.ts | 53 +++++++++++-------- src/crypto/store/indexeddb-crypto-store.ts | 3 +- src/crypto/store/localStorage-crypto-store.ts | 13 +++-- src/crypto/store/memory-crypto-store.ts | 6 ++- 6 files changed, 61 insertions(+), 29 deletions(-) diff --git a/spec/unit/crypto/store/CryptoStore.spec.ts b/spec/unit/crypto/store/CryptoStore.spec.ts index 1cc7177407b..b6b6f652509 100644 --- a/spec/unit/crypto/store/CryptoStore.spec.ts +++ b/spec/unit/crypto/store/CryptoStore.spec.ts @@ -145,6 +145,11 @@ describe.each([ const N_SESSIONS_PER_DEVICE = 6; await createSessions(N_DEVICES, N_SESSIONS_PER_DEVICE); + // Mark one of the sessions as needing backup + await store.doTxn("readwrite", IndexedDBCryptoStore.STORE_BACKUP, async (txn) => { + await store.markSessionsNeedingBackup([{ senderKey: pad43("device5"), sessionId: "session5" }], txn); + }); + const batch = await store.getEndToEndInboundGroupSessionsBatch(); expect(batch!.length).toEqual(N_DEVICES * N_SESSIONS_PER_DEVICE); for (let i = 0; i < N_DEVICES; i++) { @@ -153,6 +158,9 @@ describe.each([ expect(r.senderKey).toEqual(pad43(`device${i}`)); expect(r.sessionId).toEqual(`session${j}`); + + // only the last session needs backup + expect(r.needsBackup).toBe(i === 5 && j === 5); } } }); diff --git a/src/crypto/store/base.ts b/src/crypto/store/base.ts index 3a363ca7f09..64a0d42c6ec 100644 --- a/src/crypto/store/base.ts +++ b/src/crypto/store/base.ts @@ -182,7 +182,7 @@ export interface CryptoStore { * @returns A batch of Megolm Sessions, or `null` if no sessions are left. * @internal */ - getEndToEndInboundGroupSessionsBatch(): Promise; + getEndToEndInboundGroupSessionsBatch(): Promise; /** * Delete a batch of Megolm sessions from the database. @@ -223,6 +223,11 @@ export interface ISession { sessionData?: InboundGroupSessionData; } +/** Extended data on a Megolm session */ +export interface ISessionExtended extends ISession { + needsBackup: boolean; +} + /** Data on an Olm session */ export interface ISessionInfo { deviceKey?: string; diff --git a/src/crypto/store/indexeddb-crypto-store-backend.ts b/src/crypto/store/indexeddb-crypto-store-backend.ts index b925d596f4d..db3d44a403f 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.ts +++ b/src/crypto/store/indexeddb-crypto-store-backend.ts @@ -21,6 +21,7 @@ import { IDeviceData, IProblem, ISession, + ISessionExtended, ISessionInfo, IWithheld, MigrationState, @@ -815,29 +816,39 @@ export class Backend implements CryptoStore { * * Implementation of {@link CryptoStore.getEndToEndInboundGroupSessionsBatch}. */ - public async getEndToEndInboundGroupSessionsBatch(): Promise { - const result: ISession[] = []; - await this.doTxn("readonly", [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => { - const objectStore = txn.objectStore(IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS); - const getReq = objectStore.openCursor(); - getReq.onsuccess = function (): void { - try { - const cursor = getReq.result; - if (cursor) { - result.push({ - senderKey: cursor.value.senderCurve25519Key, - sessionId: cursor.value.sessionId, - sessionData: cursor.value.session, - }); - if (result.length < SESSION_BATCH_SIZE) { - cursor.continue(); + public async getEndToEndInboundGroupSessionsBatch(): Promise { + const result: ISessionExtended[] = []; + await this.doTxn( + "readonly", + [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS, IndexedDBCryptoStore.STORE_BACKUP], + (txn) => { + const sessionStore = txn.objectStore(IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS); + const backupStore = txn.objectStore(IndexedDBCryptoStore.STORE_BACKUP); + + const getReq = sessionStore.openCursor(); + getReq.onsuccess = function (): void { + try { + const cursor = getReq.result; + if (cursor) { + const backupGetReq = backupStore.get(cursor.key); + backupGetReq.onsuccess = (): void => { + result.push({ + senderKey: cursor.value.senderCurve25519Key, + sessionId: cursor.value.sessionId, + sessionData: cursor.value.session, + needsBackup: backupGetReq.result !== undefined, + }); + if (result.length < SESSION_BATCH_SIZE) { + cursor.continue(); + } + }; } + } catch (e) { + abortWithException(txn, e); } - } catch (e) { - abortWithException(txn, e); - } - }; - }); + }; + }, + ); if (result.length === 0) { // No sessions left. diff --git a/src/crypto/store/indexeddb-crypto-store.ts b/src/crypto/store/indexeddb-crypto-store.ts index bfb5de55454..16e5047f926 100644 --- a/src/crypto/store/indexeddb-crypto-store.ts +++ b/src/crypto/store/indexeddb-crypto-store.ts @@ -25,6 +25,7 @@ import { IDeviceData, IProblem, ISession, + ISessionExtended, ISessionInfo, IWithheld, MigrationState, @@ -610,7 +611,7 @@ export class IndexedDBCryptoStore implements CryptoStore { * * @internal */ - public getEndToEndInboundGroupSessionsBatch(): Promise { + public getEndToEndInboundGroupSessionsBatch(): Promise { return this.backend!.getEndToEndInboundGroupSessionsBatch(); } diff --git a/src/crypto/store/localStorage-crypto-store.ts b/src/crypto/store/localStorage-crypto-store.ts index cdc9b4c040a..c118e0fa48f 100644 --- a/src/crypto/store/localStorage-crypto-store.ts +++ b/src/crypto/store/localStorage-crypto-store.ts @@ -21,6 +21,7 @@ import { IDeviceData, IProblem, ISession, + ISessionExtended, ISessionInfo, IWithheld, MigrationState, @@ -357,20 +358,24 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore implements Crypto * * @internal */ - public async getEndToEndInboundGroupSessionsBatch(): Promise { - const result: ISession[] = []; + public async getEndToEndInboundGroupSessionsBatch(): Promise { + const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; + const result: ISessionExtended[] = []; for (let i = 0; i < this.store.length; ++i) { const key = this.store.key(i); if (key?.startsWith(KEY_INBOUND_SESSION_PREFIX)) { + const key2 = key.slice(KEY_INBOUND_SESSION_PREFIX.length); + // we can't use split, as the components we are trying to split out // might themselves contain '/' characters. We rely on the // senderKey being a (32-byte) curve25519 key, base64-encoded // (hence 43 characters long). result.push({ - senderKey: key.slice(KEY_INBOUND_SESSION_PREFIX.length, KEY_INBOUND_SESSION_PREFIX.length + 43), - sessionId: key.slice(KEY_INBOUND_SESSION_PREFIX.length + 44), + senderKey: key2.slice(0, 43), + sessionId: key2.slice(44), sessionData: getJsonItem(this.store, key)!, + needsBackup: key2 in sessionsNeedingBackup, }); if (result.length >= SESSION_BATCH_SIZE) { diff --git a/src/crypto/store/memory-crypto-store.ts b/src/crypto/store/memory-crypto-store.ts index 6cb71053f9e..75d5a10c7a0 100644 --- a/src/crypto/store/memory-crypto-store.ts +++ b/src/crypto/store/memory-crypto-store.ts @@ -21,6 +21,7 @@ import { IDeviceData, IProblem, ISession, + ISessionExtended, ISessionInfo, IWithheld, MigrationState, @@ -534,13 +535,14 @@ export class MemoryCryptoStore implements CryptoStore { * * @internal */ - public async getEndToEndInboundGroupSessionsBatch(): Promise { - const result: ISession[] = []; + public async getEndToEndInboundGroupSessionsBatch(): Promise { + const result: ISessionExtended[] = []; for (const [key, session] of Object.entries(this.inboundGroupSessions)) { result.push({ senderKey: key.slice(0, 43), sessionId: key.slice(44), sessionData: session, + needsBackup: key in this.sessionsNeedingBackup, }); if (result.length >= SESSION_BATCH_SIZE) { return result;