diff --git a/ts/SignalProtocolStore.ts b/ts/SignalProtocolStore.ts index 6c3cac8a8d8c..f669d8bf28ab 100644 --- a/ts/SignalProtocolStore.ts +++ b/ts/SignalProtocolStore.ts @@ -967,13 +967,17 @@ export class SignalProtocolStore extends EventsMixin { conversationIds.has(session.fromDB.conversationId) ); const openEntries: Array< - SessionCacheEntry | undefined + | undefined + | { + entry: SessionCacheEntry; + record: SessionRecord; + } > = await Promise.all( entries.map(async entry => { if (entry.hydrated) { const record = entry.item; if (record.hasCurrentState()) { - return entry; + return { record, entry }; } return undefined; @@ -981,7 +985,7 @@ export class SignalProtocolStore extends EventsMixin { const record = await this._maybeMigrateSession(entry.fromDB); if (record.hasCurrentState()) { - return entry; + return { record, entry }; } return undefined; @@ -989,10 +993,11 @@ export class SignalProtocolStore extends EventsMixin { ); const devices = openEntries - .map(entry => { - if (!entry) { + .map(item => { + if (!item) { return undefined; } + const { entry, record } = item; const { conversationId } = entry.fromDB; conversationIds.delete(conversationId); @@ -1015,9 +1020,12 @@ export class SignalProtocolStore extends EventsMixin { ); } + const registrationId = record.remoteRegistrationId(); + return { identifier, id, + registrationId, }; }) .filter(isNotNil); diff --git a/ts/test-electron/SignalProtocolStore_test.ts b/ts/test-electron/SignalProtocolStore_test.ts index 81ea8dd2b238..faa5a3842e94 100644 --- a/ts/test-electron/SignalProtocolStore_test.ts +++ b/ts/test-electron/SignalProtocolStore_test.ts @@ -1330,18 +1330,22 @@ describe('SignalProtocolStore', () => { { id: 1, identifier: number, + registrationId: 243, }, { id: 2, identifier: number, + registrationId: 243, }, { id: 3, identifier: number, + registrationId: 243, }, { id: 10, identifier: number, + registrationId: 243, }, ], emptyIdentifiers: ['blah', 'blah2'], diff --git a/ts/test-electron/util/sendToGroup_test.ts b/ts/test-electron/util/sendToGroup_test.ts index bb695d93f471..f55b2eb4bb73 100644 --- a/ts/test-electron/util/sendToGroup_test.ts +++ b/ts/test-electron/util/sendToGroup_test.ts @@ -14,14 +14,17 @@ describe('sendToGroup', () => { { identifier: 'ident-guid-one', id: 1, + registrationId: 11, }, { identifier: 'ident-guid-one', id: 2, + registrationId: 22, }, { identifier: 'ident-guid-two', id: 2, + registrationId: 33, }, ]; } @@ -60,10 +63,12 @@ describe('sendToGroup', () => { { identifier: 'ident-guid-one', id: 2, + registrationId: 22, }, { identifier: 'ident-guid-two', id: 2, + registrationId: 33, }, ]); assert.deepEqual(newToMemberUuids, ['ident-guid-one', 'ident-guid-two']); @@ -90,10 +95,12 @@ describe('sendToGroup', () => { { identifier: 'ident-guid-one', id: 2, + registrationId: 22, }, { identifier: 'ident-guid-two', id: 2, + registrationId: 33, }, ]); assert.deepEqual(removedFromMemberUuids, [ diff --git a/ts/textsecure/Types.d.ts b/ts/textsecure/Types.d.ts index dfa357b5e8fb..9c25fb9cac05 100644 --- a/ts/textsecure/Types.d.ts +++ b/ts/textsecure/Types.d.ts @@ -32,6 +32,7 @@ export type WebAPICredentials = { export type DeviceType = { id: number; identifier: string; + registrationId: number; }; // How the legacy APIs generate these types diff --git a/ts/util/sendToGroup.ts b/ts/util/sendToGroup.ts index 746c52992044..c161ca5c0a70 100644 --- a/ts/util/sendToGroup.ts +++ b/ts/util/sendToGroup.ts @@ -44,7 +44,7 @@ import { } from '../textsecure/WebAPI'; import { SignalService as Proto } from '../protobuf'; -import { assert } from './assert'; +import { strictAssert } from './assert'; import { isGroupV2 } from './whatTypeOfConversation'; const ERROR_EXPIRED_OR_MISSING_DEVICES = 409; @@ -56,7 +56,7 @@ const DAY = 24 * HOUR; const MAX_CONCURRENCY = 5; // sendWithSenderKey is recursive, but we don't want to loop back too many times. -const MAX_RECURSION = 5; +const MAX_RECURSION = 10; // TODO: remove once we move away from ArrayBuffers const FIXMEU8 = Uint8Array; @@ -80,7 +80,7 @@ export async function sendToGroup({ sendOptions?: SendOptionsType; sendType: SendTypesType; }): Promise { - assert( + strictAssert( window.textsecure.messaging, 'sendToGroup: textsecure.messaging not available!' ); @@ -133,7 +133,7 @@ export async function sendContentMessageToGroup({ timestamp: number; }): Promise { const logId = conversation.idForLogging(); - assert( + strictAssert( window.textsecure.messaging, 'sendContentMessageToGroup: textsecure.messaging not available!' ); @@ -247,7 +247,7 @@ export async function sendToGroupViaSenderKey(options: { ); } - assert( + strictAssert( window.textsecure.messaging, 'sendToGroupViaSenderKey: textsecure.messaging not available!' ); @@ -293,14 +293,14 @@ export async function sendToGroupViaSenderKey(options: { ) { await fetchKeysForIdentifiers(emptyIdentifiers); - // Restart here to capture devices for accounts we just started sesions with + // Restart here to capture devices for accounts we just started sessions with return sendToGroupViaSenderKey({ ...options, recursionCount: recursionCount + 1, }); } - assert( + strictAssert( attributes.senderKeyInfo, `sendToGroupViaSenderKey/${logId}: expect senderKeyInfo` ); @@ -380,6 +380,13 @@ export async function sendToGroupViaSenderKey(options: { ), { messageIds: [], sendType: 'senderKeyDistributionMessage' } ); + + // Restart here because we might have discovered new or dropped devices as part of + // distributing our sender key. + return sendToGroupViaSenderKey({ + ...options, + recursionCount: recursionCount + 1, + }); } // 9. Update memberDevices with both adds and the removals which didn't require a reset. @@ -696,7 +703,7 @@ async function handle410Response( // been re-registered or re-linked. const senderKeyInfo = conversation.get('senderKeyInfo'); if (senderKeyInfo) { - const devicesToRemove: Array = devices.staleDevices.map( + const devicesToRemove: Array = devices.staleDevices.map( id => ({ id, identifier: uuid }) ); conversation.set({ @@ -705,7 +712,7 @@ async function handle410Response( memberDevices: differenceWith( senderKeyInfo.memberDevices, devicesToRemove, - deviceComparator + partialDeviceComparator ), }, }); @@ -732,7 +739,7 @@ function getXorOfAccessKeys(devices: Array): Buffer { const uuids = getUuidsFromDevices(devices); const result = Buffer.alloc(ACCESS_KEY_LENGTH); - assert( + strictAssert( result.length === ACCESS_KEY_LENGTH, 'getXorOfAccessKeys starting value' ); @@ -876,6 +883,21 @@ function isValidSenderKeyRecipient( } function deviceComparator(left?: DeviceType, right?: DeviceType): boolean { + return Boolean( + left && + right && + left.id === right.id && + left.identifier === right.identifier && + left.registrationId === right.registrationId + ); +} + +type PartialDeviceType = Omit; + +function partialDeviceComparator( + left?: PartialDeviceType, + right?: PartialDeviceType +): boolean { return Boolean( left && right &&