updatePNI: Cleanup only for obsolete PNI
This commit is contained in:
		
					parent
					
						
							
								a72a431e0f
							
						
					
				
			
			
				commit
				
					
						f366454893
					
				
			
		
					 6 changed files with 132 additions and 45 deletions
				
			
		|  | @ -1062,7 +1062,9 @@ export class ConversationController { | ||||||
| 
 | 
 | ||||||
|       log.warn(`${logId}: Delete all sessions tied to old conversationId`); |       log.warn(`${logId}: Delete all sessions tied to old conversationId`); | ||||||
|       // Note: we use the conversationId here in case we've already lost our uuid.
 |       // Note: we use the conversationId here in case we've already lost our uuid.
 | ||||||
|       await window.textsecure.storage.protocol.removeAllSessions(obsoleteId); |       await window.textsecure.storage.protocol.removeSessionsByConversation( | ||||||
|  |         obsoleteId | ||||||
|  |       ); | ||||||
| 
 | 
 | ||||||
|       log.warn( |       log.warn( | ||||||
|         `${logId}: Delete all identity information tied to old conversationId` |         `${logId}: Delete all identity information tied to old conversationId` | ||||||
|  |  | ||||||
|  | @ -1226,35 +1226,68 @@ export class SignalProtocolStore extends EventEmitter { | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async removeAllSessions(identifier: string): Promise<void> { |   async removeSessionsByConversation(identifier: string): Promise<void> { | ||||||
|     return this.withZone(GLOBAL_ZONE, 'removeAllSessions', async () => { |     return this.withZone( | ||||||
|  |       GLOBAL_ZONE, | ||||||
|  |       'removeSessionsByConversation', | ||||||
|  |       async () => { | ||||||
|  |         if (!this.sessions) { | ||||||
|  |           throw new Error( | ||||||
|  |             'removeSessionsByConversation: this.sessions not yet cached!' | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (identifier == null) { | ||||||
|  |           throw new Error( | ||||||
|  |             'removeSessionsByConversation: identifier was undefined/null' | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         log.info( | ||||||
|  |           'removeSessionsByConversation: deleting sessions for', | ||||||
|  |           identifier | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         const id = window.ConversationController.getConversationId(identifier); | ||||||
|  |         strictAssert( | ||||||
|  |           id, | ||||||
|  |           `removeSessionsByConversation: Conversation not found: ${identifier}` | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         const entries = Array.from(this.sessions.values()); | ||||||
|  | 
 | ||||||
|  |         for (let i = 0, max = entries.length; i < max; i += 1) { | ||||||
|  |           const entry = entries[i]; | ||||||
|  |           if (entry.fromDB.conversationId === id) { | ||||||
|  |             this.sessions.delete(entry.fromDB.id); | ||||||
|  |             this.pendingSessions.delete(entry.fromDB.id); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         await window.Signal.Data.removeSessionsByConversation(id); | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async removeSessionsByUUID(uuid: UUIDStringType): Promise<void> { | ||||||
|  |     return this.withZone(GLOBAL_ZONE, 'removeSessionsByUUID', async () => { | ||||||
|       if (!this.sessions) { |       if (!this.sessions) { | ||||||
|         throw new Error('removeAllSessions: this.sessions not yet cached!'); |         throw new Error('removeSessionsByUUID: this.sessions not yet cached!'); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       if (identifier == null) { |       log.info('removeSessionsByUUID: deleting sessions for', uuid); | ||||||
|         throw new Error('removeAllSessions: identifier was undefined/null'); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       log.info('removeAllSessions: deleting sessions for', identifier); |  | ||||||
| 
 |  | ||||||
|       const id = window.ConversationController.getConversationId(identifier); |  | ||||||
|       strictAssert( |  | ||||||
|         id, |  | ||||||
|         `removeAllSessions: Conversation not found: ${identifier}` |  | ||||||
|       ); |  | ||||||
| 
 | 
 | ||||||
|       const entries = Array.from(this.sessions.values()); |       const entries = Array.from(this.sessions.values()); | ||||||
| 
 | 
 | ||||||
|       for (let i = 0, max = entries.length; i < max; i += 1) { |       for (let i = 0, max = entries.length; i < max; i += 1) { | ||||||
|         const entry = entries[i]; |         const entry = entries[i]; | ||||||
|         if (entry.fromDB.conversationId === id) { |         if (entry.fromDB.uuid === uuid) { | ||||||
|           this.sessions.delete(entry.fromDB.id); |           this.sessions.delete(entry.fromDB.id); | ||||||
|           this.pendingSessions.delete(entry.fromDB.id); |           this.pendingSessions.delete(entry.fromDB.id); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       await window.Signal.Data.removeSessionsByConversation(id); |       await window.Signal.Data.removeSessionsByUUID(uuid); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -1961,10 +1994,7 @@ export class SignalProtocolStore extends EventEmitter { | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async removeIdentityKey( |   async removeIdentityKey(uuid: UUID): Promise<void> { | ||||||
|     uuid: UUID, |  | ||||||
|     options?: { disableSessionDeletion: boolean } |  | ||||||
|   ): Promise<void> { |  | ||||||
|     if (!this.identityKeys) { |     if (!this.identityKeys) { | ||||||
|       throw new Error('removeIdentityKey: this.identityKeys not yet cached!'); |       throw new Error('removeIdentityKey: this.identityKeys not yet cached!'); | ||||||
|     } |     } | ||||||
|  | @ -1972,9 +2002,7 @@ export class SignalProtocolStore extends EventEmitter { | ||||||
|     const id = uuid.toString(); |     const id = uuid.toString(); | ||||||
|     this.identityKeys.delete(id); |     this.identityKeys.delete(id); | ||||||
|     await window.Signal.Data.removeIdentityKeyById(id); |     await window.Signal.Data.removeIdentityKeyById(id); | ||||||
|     if (!options?.disableSessionDeletion) { |     await this.removeSessionsByUUID(id); | ||||||
|       await this.removeAllSessions(id); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // Not yet processed messages - for resiliency
 |   // Not yet processed messages - for resiliency
 | ||||||
|  |  | ||||||
|  | @ -1966,14 +1966,7 @@ export class ConversationModel extends window.Backbone | ||||||
|     //   for the case where we need to do old and new PNI comparisons. We'll wait
 |     //   for the case where we need to do old and new PNI comparisons. We'll wait
 | ||||||
|     //   for the PNI update to do that.
 |     //   for the PNI update to do that.
 | ||||||
|     if (oldValue && oldValue !== this.get('pni')) { |     if (oldValue && oldValue !== this.get('pni')) { | ||||||
|       // We've already changed our UUID, so we need account for lookups on that old UUID
 |       window.textsecure.storage.protocol.removeIdentityKey(UUID.cast(oldValue)); | ||||||
|       //   to returng nothing: pass conversationId into removeAllSessions, and disable
 |  | ||||||
|       //   auto-deletion in removeIdentityKey.
 |  | ||||||
|       window.textsecure.storage.protocol.removeAllSessions(this.id); |  | ||||||
|       window.textsecure.storage.protocol.removeIdentityKey( |  | ||||||
|         UUID.cast(oldValue), |  | ||||||
|         { disableSessionDeletion: true } |  | ||||||
|       ); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     this.captureChange('updateUuid'); |     this.captureChange('updateUuid'); | ||||||
|  | @ -2059,14 +2052,7 @@ export class ConversationModel extends window.Backbone | ||||||
| 
 | 
 | ||||||
|     // If this PNI is going away or going to someone else, we'll delete all its sessions
 |     // If this PNI is going away or going to someone else, we'll delete all its sessions
 | ||||||
|     if (oldValue) { |     if (oldValue) { | ||||||
|       // We've already changed our UUID, so we need account for lookups on that old UUID
 |       window.textsecure.storage.protocol.removeIdentityKey(UUID.cast(oldValue)); | ||||||
|       //   to returng nothing: pass conversationId into removeAllSessions, and disable
 |  | ||||||
|       //   auto-deletion in removeIdentityKey.
 |  | ||||||
|       window.textsecure.storage.protocol.removeAllSessions(this.id); |  | ||||||
|       window.textsecure.storage.protocol.removeIdentityKey( |  | ||||||
|         UUID.cast(oldValue), |  | ||||||
|         { disableSessionDeletion: true } |  | ||||||
|       ); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (pni && !this.get('uuid')) { |     if (pni && !this.get('uuid')) { | ||||||
|  |  | ||||||
|  | @ -425,6 +425,7 @@ export type DataInterface = { | ||||||
|   bulkAddSessions: (array: Array<SessionType>) => Promise<void>; |   bulkAddSessions: (array: Array<SessionType>) => Promise<void>; | ||||||
|   removeSessionById: (id: SessionIdType) => Promise<void>; |   removeSessionById: (id: SessionIdType) => Promise<void>; | ||||||
|   removeSessionsByConversation: (conversationId: string) => Promise<void>; |   removeSessionsByConversation: (conversationId: string) => Promise<void>; | ||||||
|  |   removeSessionsByUUID: (uuid: UUIDStringType) => Promise<void>; | ||||||
|   removeAllSessions: () => Promise<void>; |   removeAllSessions: () => Promise<void>; | ||||||
|   getAllSessions: () => Promise<Array<SessionType>>; |   getAllSessions: () => Promise<Array<SessionType>>; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -200,6 +200,7 @@ const dataInterface: ServerInterface = { | ||||||
|   bulkAddSessions, |   bulkAddSessions, | ||||||
|   removeSessionById, |   removeSessionById, | ||||||
|   removeSessionsByConversation, |   removeSessionsByConversation, | ||||||
|  |   removeSessionsByUUID, | ||||||
|   removeAllSessions, |   removeAllSessions, | ||||||
|   getAllSessions, |   getAllSessions, | ||||||
| 
 | 
 | ||||||
|  | @ -1308,6 +1309,17 @@ async function removeSessionsByConversation( | ||||||
|     conversationId, |     conversationId, | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  | async function removeSessionsByUUID(uuid: UUIDStringType): Promise<void> { | ||||||
|  |   const db = getInstance(); | ||||||
|  |   db.prepare<Query>( | ||||||
|  |     ` | ||||||
|  |     DELETE FROM sessions | ||||||
|  |     WHERE uuid = $uuid; | ||||||
|  |     ` | ||||||
|  |   ).run({ | ||||||
|  |     uuid, | ||||||
|  |   }); | ||||||
|  | } | ||||||
| async function removeAllSessions(): Promise<void> { | async function removeAllSessions(): Promise<void> { | ||||||
|   return removeAllFromTable(getInstance(), SESSIONS_TABLE); |   return removeAllFromTable(getInstance(), SESSIONS_TABLE); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -995,7 +995,7 @@ describe('SignalProtocolStore', () => { | ||||||
|       assert.equal(record, testRecord); |       assert.equal(record, testRecord); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|   describe('removeAllSessions', () => { |   describe('removeSessionsByUUID', () => { | ||||||
|     it('removes all sessions for a uuid', async () => { |     it('removes all sessions for a uuid', async () => { | ||||||
|       const devices = [1, 2, 3].map( |       const devices = [1, 2, 3].map( | ||||||
|         deviceId => |         deviceId => | ||||||
|  | @ -1008,14 +1008,72 @@ describe('SignalProtocolStore', () => { | ||||||
|         }) |         }) | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
|       await store.removeAllSessions(theirUuid.toString()); |       const records0 = await Promise.all( | ||||||
|  |         devices.map(device => store.loadSession(device)) | ||||||
|  |       ); | ||||||
|  |       for (let i = 0, max = records0.length; i < max; i += 1) { | ||||||
|  |         assert.exists(records0[i], 'before delete'); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       await store.removeSessionsByUUID(theirUuid.toString()); | ||||||
| 
 | 
 | ||||||
|       const records = await Promise.all( |       const records = await Promise.all( | ||||||
|         devices.map(device => store.loadSession(device)) |         devices.map(device => store.loadSession(device)) | ||||||
|       ); |       ); | ||||||
| 
 |  | ||||||
|       for (let i = 0, max = records.length; i < max; i += 1) { |       for (let i = 0, max = records.length; i < max; i += 1) { | ||||||
|         assert.isUndefined(records[i]); |         assert.isUndefined(records[i], 'in-memory'); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       await store.hydrateCaches(); | ||||||
|  | 
 | ||||||
|  |       const records2 = await Promise.all( | ||||||
|  |         devices.map(device => store.loadSession(device)) | ||||||
|  |       ); | ||||||
|  |       for (let i = 0, max = records2.length; i < max; i += 1) { | ||||||
|  |         assert.isUndefined(records2[i], 'from database'); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |   describe('removeSessionsByConversation', () => { | ||||||
|  |     it('removes all sessions for a uuid', async () => { | ||||||
|  |       const devices = [1, 2, 3].map( | ||||||
|  |         deviceId => | ||||||
|  |           new QualifiedAddress(ourUuid, new Address(theirUuid, deviceId)) | ||||||
|  |       ); | ||||||
|  |       const conversationId = window.ConversationController.getOrCreate( | ||||||
|  |         theirUuid.toString(), | ||||||
|  |         'private' | ||||||
|  |       ).id; | ||||||
|  | 
 | ||||||
|  |       await Promise.all( | ||||||
|  |         devices.map(async encodedAddress => { | ||||||
|  |           await store.storeSession(encodedAddress, getSessionRecord()); | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|  |       const records0 = await Promise.all( | ||||||
|  |         devices.map(device => store.loadSession(device)) | ||||||
|  |       ); | ||||||
|  |       for (let i = 0, max = records0.length; i < max; i += 1) { | ||||||
|  |         assert.exists(records0[i], 'before delete'); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       await store.removeSessionsByConversation(conversationId); | ||||||
|  | 
 | ||||||
|  |       const records = await Promise.all( | ||||||
|  |         devices.map(device => store.loadSession(device)) | ||||||
|  |       ); | ||||||
|  |       for (let i = 0, max = records.length; i < max; i += 1) { | ||||||
|  |         assert.isUndefined(records[i], 'in-memory'); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       await store.hydrateCaches(); | ||||||
|  | 
 | ||||||
|  |       const records2 = await Promise.all( | ||||||
|  |         devices.map(device => store.loadSession(device)) | ||||||
|  |       ); | ||||||
|  |       for (let i = 0, max = records2.length; i < max; i += 1) { | ||||||
|  |         assert.isUndefined(records[i], 'from database'); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|  | @ -1145,7 +1203,7 @@ describe('SignalProtocolStore', () => { | ||||||
| 
 | 
 | ||||||
|     beforeEach(async () => { |     beforeEach(async () => { | ||||||
|       await store.removeAllUnprocessed(); |       await store.removeAllUnprocessed(); | ||||||
|       await store.removeAllSessions(theirUuid.toString()); |       await store.removeSessionsByUUID(theirUuid.toString()); | ||||||
|       await store.removeAllSenderKeys(); |       await store.removeAllSenderKeys(); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Scott Nonnenberg
				Scott Nonnenberg