From f42240eaa3619080ea3cb345c2a50367b168d4bc Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Mon, 16 Jan 2023 17:18:32 +0000 Subject: [PATCH] Batch migrate olm and megolm sessions --- MatrixSDK.xcodeproj/project.pbxproj | 14 ++ .../Background/MXBackgroundCryptoStore.m | 28 +-- MatrixSDK/Crypto/Data/Store/MXCryptoStore.h | 30 +++- .../MXRealmCryptoStore/MXRealmCryptoStore.m | 123 ++++++++++---- MatrixSDK/Crypto/MXCrypto.h | 12 +- MatrixSDK/Crypto/MXCrypto.m | 39 ++++- MatrixSDK/Crypto/MXCryptoV2.swift | 159 ++++++++++++------ .../Data/MXCryptoMigrationStore.swift | 89 +++++----- .../Migration/MXCryptoMigrationV2.swift | 79 ++++++++- MatrixSDK/MXSession.m | 8 +- MatrixSDK/MXSessionStartupProgress.swift | 8 + .../Data/Store/MXMemoryCryptoStore.swift | 13 +- .../MXRealmCryptoStoreTests.swift | 114 +++++++++++++ .../MXCryptoMigrationStoreUnitTests.swift | 47 ++++-- .../Migration/MXCryptoMigrationV2Tests.swift | 2 +- changelog.d/pr-1687.change | 1 + 16 files changed, 599 insertions(+), 167 deletions(-) create mode 100644 MatrixSDKTests/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStoreTests.swift create mode 100644 changelog.d/pr-1687.change diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index 5fff74b920..806dae0201 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -1858,6 +1858,8 @@ ED4114EC292E498100728459 /* MXBackgroundCryptoV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4114EA292E498100728459 /* MXBackgroundCryptoV2.swift */; }; ED4114EE292E49C000728459 /* MXLegacyBackgroundCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4114ED292E49C000728459 /* MXLegacyBackgroundCrypto.swift */; }; ED4114EF292E49C000728459 /* MXLegacyBackgroundCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4114ED292E49C000728459 /* MXLegacyBackgroundCrypto.swift */; }; + ED4368B129784CCE002B6272 /* MXRealmCryptoStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4368B029784CCE002B6272 /* MXRealmCryptoStoreTests.swift */; }; + ED4368B229784CCE002B6272 /* MXRealmCryptoStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4368B029784CCE002B6272 /* MXRealmCryptoStoreTests.swift */; }; ED44F01128180BCC00452A5D /* MXSharedHistoryKeyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED44F01028180BCC00452A5D /* MXSharedHistoryKeyRequest.swift */; }; ED44F01228180BCC00452A5D /* MXSharedHistoryKeyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED44F01028180BCC00452A5D /* MXSharedHistoryKeyRequest.swift */; }; ED44F01428180EAB00452A5D /* MXSharedHistoryKeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED44F01328180EAB00452A5D /* MXSharedHistoryKeyManager.swift */; }; @@ -3070,6 +3072,7 @@ ED4114E7292E496C00728459 /* MXBackgroundCrypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXBackgroundCrypto.swift; sourceTree = ""; }; ED4114EA292E498100728459 /* MXBackgroundCryptoV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXBackgroundCryptoV2.swift; sourceTree = ""; }; ED4114ED292E49C000728459 /* MXLegacyBackgroundCrypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXLegacyBackgroundCrypto.swift; sourceTree = ""; }; + ED4368B029784CCE002B6272 /* MXRealmCryptoStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRealmCryptoStoreTests.swift; sourceTree = ""; }; ED44F01028180BCC00452A5D /* MXSharedHistoryKeyRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXSharedHistoryKeyRequest.swift; sourceTree = ""; }; ED44F01328180EAB00452A5D /* MXSharedHistoryKeyManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXSharedHistoryKeyManager.swift; sourceTree = ""; }; ED44F01728180F1C00452A5D /* MXSharedHistoryKeyManagerUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXSharedHistoryKeyManagerUnitTests.swift; sourceTree = ""; }; @@ -5467,6 +5470,14 @@ path = Crypto; sourceTree = ""; }; + ED4368AF29784CA3002B6272 /* MXRealmCryptoStore */ = { + isa = PBXGroup; + children = ( + ED4368B029784CCE002B6272 /* MXRealmCryptoStoreTests.swift */, + ); + path = MXRealmCryptoStore; + sourceTree = ""; + }; ED44F01628180F1300452A5D /* KeySharing */ = { isa = PBXGroup; children = ( @@ -5571,6 +5582,7 @@ ED6DAC1328C78D3700ECDCB6 /* Store */ = { isa = PBXGroup; children = ( + ED4368AF29784CA3002B6272 /* MXRealmCryptoStore */, ED6DAC1428C78D4000ECDCB6 /* MXMemoryCryptoStore.swift */, ); path = Store; @@ -7348,6 +7360,7 @@ EDB4209527DF822B0036AF39 /* MXEventsByTypesEnumeratorOnArrayTests.swift in Sources */, EC40385D28A16EDA0067D5B8 /* MXAes256KeyBackupTests.m in Sources */, ED6DAC0728C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift in Sources */, + ED4368B129784CCE002B6272 /* MXRealmCryptoStoreTests.swift in Sources */, 3A9E2B4328EB3960000DB2A7 /* MXMatrixVersionsUnitTests.swift in Sources */, 3265CB3B1A151C3800E24B2F /* MXRoomStateTests.m in Sources */, ED8F1D302885AB0300F897E7 /* MXTrustLevelSourceUnitTests.swift in Sources */, @@ -8006,6 +8019,7 @@ EDB4209627DF822B0036AF39 /* MXEventsByTypesEnumeratorOnArrayTests.swift in Sources */, EC40385E28A16EDA0067D5B8 /* MXAes256KeyBackupTests.m in Sources */, ED6DAC0828C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift in Sources */, + ED4368B229784CCE002B6272 /* MXRealmCryptoStoreTests.swift in Sources */, 3A9E2B4428EB3960000DB2A7 /* MXMatrixVersionsUnitTests.swift in Sources */, 32B477AA2638186000EA5800 /* MXHTTPAdditionalHeadersUnitTests.m in Sources */, B135066A27EA100100BD3276 /* MXBeaconInfoUnitTests.swift in Sources */, diff --git a/MatrixSDK/Background/MXBackgroundCryptoStore.m b/MatrixSDK/Background/MXBackgroundCryptoStore.m index cdeb1324f3..f2cdccf962 100644 --- a/MatrixSDK/Background/MXBackgroundCryptoStore.m +++ b/MatrixSDK/Background/MXBackgroundCryptoStore.m @@ -209,18 +209,6 @@ - (MXOlmSession*)sessionWithDevice:(NSString*)deviceKey andSessionId:(NSString*) return sessions; } -- (NSArray *)sessions -{ - NSArray *bgSessions = [bgCryptoStore sessions] ?: @[]; - NSArray *appSessions = [cryptoStore sessions] ?: @[]; - - NSMutableArray *sessions = [NSMutableArray array]; - [sessions addObjectsFromArray:bgSessions]; - [sessions addObjectsFromArray:appSessions]; - - return sessions; -} - - (void)storeSession:(MXOlmSession*)session { [bgCryptoStore storeSession:session]; @@ -356,6 +344,22 @@ - (void)storeDeviceSyncToken:(NSString*)deviceSyncToken NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore"); } +- (void)enumerateSessionsBy:(NSInteger)batchSize block:(void (^)(NSArray *, double))block +{ + NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore"); +} + +- (void)enumerateInboundGroupSessionsBy:(NSInteger)batchSize block:(void (^)(NSArray *, NSSet *, double))block +{ + NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore"); +} + +- (NSUInteger)sessionsCount +{ + NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore"); + return 0; +} + - (NSArray *)inboundGroupSessions { NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore"); diff --git a/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h b/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h index 5741516cc1..ce789e5e50 100644 --- a/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h +++ b/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h @@ -296,11 +296,22 @@ - (NSArray*)sessionsWithDevice:(NSString*)deviceKey; /** - Retrieve all end-to-end sessions between this device and all other devices + Enumerate all end-to-end sessions in batches of `batchSize` + + Each block is internally wrapped in `@autoreleasepool` so that memory footprint remains constant + regardless of the number of stored sessions. + + @param batchSize the max number of sessions in a single batch + @param block function that will be executed with each batch, incl. list of sessions and current progress of batching + */ +- (void)enumerateSessionsBy:(NSInteger)batchSize + block:(void (^)(NSArray *sessions, + double progress))block; - @return a array of end-to-end sessions. +/** + The number of stored end-to-end sessions */ -- (NSArray*)sessions; +- (NSUInteger)sessionsCount; /** Store inbound group sessions. @@ -336,6 +347,19 @@ */ - (NSArray *)inboundGroupSessions; +/** + Enumerate all inbound group sessions in batches of `batchSize` + + Each block is internally wrapped in `@autoreleasepool` so that memory footprint remains constant + regardless of the number of stored sessions. + + @param batchSize the max number of sessions in a single batch + @param block function that will be executed with each batch, incl. list of sessions and current progress of batching + */ +- (void)enumerateInboundGroupSessionsBy:(NSInteger)batchSize + block:(void (^)(NSArray *sessions, + NSSet *backedUp, + double progress))block; /** Store outbound group session. diff --git a/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m b/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m index 0a73e6b6d1..401f545d63 100644 --- a/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m +++ b/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m @@ -862,17 +862,7 @@ - (MXOlmSession*)sessionWithDevice:(NSString*)deviceKey andSessionId:(NSString*) { MXRealmOlmSession *realmOlmSession = [MXRealmOlmSession objectsInRealm:self.realm where:@"sessionId = %@ AND deviceKey = %@", sessionId, deviceKey].firstObject; - - MXOlmSession *mxOlmSession; - if (realmOlmSession.olmSessionData) - { - OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmOlmSession.olmSessionData]; - - mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:realmOlmSession.deviceKey]; - mxOlmSession.lastReceivedMessageTs = realmOlmSession.lastReceivedMessageTs; - } - - return mxOlmSession; + return [self olmSessionForRealmSession:realmOlmSession]; } - (void)performSessionOperationWithDevice:(NSString*)deviceKey andSessionId:(NSString*)sessionId block:(void (^)(MXOlmSession *olmSession))block @@ -880,16 +870,11 @@ - (void)performSessionOperationWithDevice:(NSString*)deviceKey andSessionId:(NSS [self.realm transactionWithName:@"[MXRealmCryptoStore] performSessionOperationWithDevice" block:^{ MXRealmOlmSession *realmOlmSession = [MXRealmOlmSession objectsInRealm:self.realm where:@"sessionId = %@ AND deviceKey = %@", sessionId, deviceKey].firstObject; - if (realmOlmSession.olmSessionData) + MXOlmSession *session = [self olmSessionForRealmSession:realmOlmSession]; + if (session) { - OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmOlmSession.olmSessionData]; - - MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:realmOlmSession.deviceKey]; - mxOlmSession.lastReceivedMessageTs = realmOlmSession.lastReceivedMessageTs; - - block(mxOlmSession); - - realmOlmSession.olmSessionData = [NSKeyedArchiver archivedDataWithRootObject:mxOlmSession.session]; + block(session); + realmOlmSession.olmSessionData = [NSKeyedArchiver archivedDataWithRootObject:session.session]; } else { @@ -915,14 +900,10 @@ - (void)performSessionOperationWithDevice:(NSString*)deviceKey andSessionId:(NSS sessionsWithDevice = [NSMutableArray array]; } - if (realmOlmSession.olmSessionData) + MXOlmSession *session = [self olmSessionForRealmSession:realmOlmSession]; + if (session) { - OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmOlmSession.olmSessionData]; - - MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:realmOlmSession.deviceKey]; - mxOlmSession.lastReceivedMessageTs = realmOlmSession.lastReceivedMessageTs; - - [sessionsWithDevice addObject:mxOlmSession]; + [sessionsWithDevice addObject:session]; } } @@ -936,18 +917,64 @@ - (void)performSessionOperationWithDevice:(NSString*)deviceKey andSessionId:(NSS RLMResults *realmOlmSessions = [MXRealmOlmSession allObjectsInRealm:self.realm]; for (MXRealmOlmSession *realmOlmSession in realmOlmSessions) { - if (realmOlmSession.olmSessionData) + MXOlmSession *session = [self olmSessionForRealmSession:realmOlmSession]; + if (session) { - OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmOlmSession.olmSessionData]; + [sessions addObject:session]; + } + } + + return sessions; +} + +- (void)enumerateSessionsBy:(NSInteger)batchSize + block:(void (^)(NSArray *sessions, + double progress))block +{ + RLMResults *query = [MXRealmOlmSession allObjectsInRealm:self.realm]; + for (NSInteger i = 0; i < query.count; i += batchSize) + { + @autoreleasepool { + NSInteger count = MIN(batchSize, query.count - i); + NSIndexSet *batchSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(i, count)]; + MXLogDebug(@"[MXRealmCryptoStore] enumerateSessionsBy: Batch %@", batchSet); - MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:realmOlmSession.deviceKey]; - mxOlmSession.lastReceivedMessageTs = realmOlmSession.lastReceivedMessageTs; + NSMutableArray *sessions = [NSMutableArray array]; + for (MXRealmOlmSession *realmOlmSession in [query objectsAtIndexes:batchSet]) + { + MXOlmSession *session = [self olmSessionForRealmSession:realmOlmSession]; + if (session) + { + [sessions addObject:session]; + } + } - [sessions addObject:mxOlmSession]; + double progress = (double)(batchSet.lastIndex + 1)/(double)query.count; + block(sessions.copy, progress); } } +} + +- (NSUInteger)sessionsCount +{ + RLMResults *sessions = [MXRealmOlmSession allObjectsInRealm:self.realm]; + return sessions.count; +} + +- (MXOlmSession *)olmSessionForRealmSession:(MXRealmOlmSession *)realmSession +{ + if (!realmSession.olmSessionData) + { + MXLogFailure(@"[MXRealmCryptoStore] olmSessionForRealmSession: Missing olm session data"); + return nil; + } - return sessions; + OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmSession.olmSessionData]; + + MXOlmSession *session = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:realmSession.deviceKey]; + session.lastReceivedMessageTs = realmSession.lastReceivedMessageTs; + + return session; } #pragma mark - MXRealmOlmInboundGroupSession @@ -1061,6 +1088,36 @@ - (void)performSessionOperationWithGroupSessionWithId:(NSString*)sessionId sende return sessions; } +- (void)enumerateInboundGroupSessionsBy:(NSInteger)batchSize + block:(void (^)(NSArray *sessions, + NSSet *backedUp, + double progress))block +{ + RLMResults *query = [MXRealmOlmInboundGroupSession allObjectsInRealm:self.realm]; + for (NSInteger i = 0; i < query.count; i += batchSize) + { + @autoreleasepool { + NSInteger count = MIN(batchSize, query.count - i); + NSIndexSet *batchSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(i, count)]; + MXLogDebug(@"[MXRealmCryptoStore] enumerateInboundGroupSessions: Batch %@", batchSet); + + NSMutableArray *sessions = [NSMutableArray array]; + NSMutableSet *backedUp = [NSMutableSet set]; + for (MXRealmOlmInboundGroupSession *realmSession in [query objectsAtIndexes:batchSet]) + { + [sessions addObject:[NSKeyedUnarchiver unarchiveObjectWithData:realmSession.olmInboundGroupSessionData]]; + if (realmSession.backedUp) + { + [backedUp addObject:realmSession.sessionId]; + } + } + + double progress = (double)(batchSet.lastIndex + 1)/(double)query.count; + block(sessions.copy, backedUp.copy, progress); + } + } +} + - (void)removeInboundGroupSessionWithId:(NSString*)sessionId andSenderKey:(NSString*)senderKey { RLMRealm *realm = self.realm; diff --git a/MatrixSDK/Crypto/MXCrypto.h b/MatrixSDK/Crypto/MXCrypto.h index 6334d6b712..d638850b0d 100644 --- a/MatrixSDK/Crypto/MXCrypto.h +++ b/MatrixSDK/Crypto/MXCrypto.h @@ -403,13 +403,17 @@ MX_ASSUME_MISSING_NULLABILITY_BEGIN error:(NSError **)error; /** - Check if the user has previously enabled crypto. - If yes, init the crypto module. + Initialize the crypto module + + If the user has previously enabled crypto it will be opened, otherwise a new crypto + store will be created. + @param migrationProgress a block called repeatedly with percentage of migration done, if any necessasry @param complete a block called in any case when the operation completes. */ -+ (void)checkCryptoWithMatrixSession:(MXSession*)mxSession - complete:(void (^)(id crypto, NSError *error))complete; ++ (void)initializeCryptoWithMatrixSession:(MXSession*)mxSession + migrationProgress:(void (^)(double progress))migrationProgress + complete:(void (^)(id crypto, NSError *error))complete; /** Stores the exportedOlmDevice related to the credentials into the store. diff --git a/MatrixSDK/Crypto/MXCrypto.m b/MatrixSDK/Crypto/MXCrypto.m index 2eff514c60..fdc3456de6 100644 --- a/MatrixSDK/Crypto/MXCrypto.m +++ b/MatrixSDK/Crypto/MXCrypto.m @@ -150,17 +150,33 @@ @implementation MXLegacyCrypto @synthesize keyVerificationManager = _keyVerificationManager; @synthesize recoveryService = _recoveryService; +#if DEBUG + ++ (MXCryptoV2Factory *)sharedCryptoV2Factory +{ + static MXCryptoV2Factory *factory = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + factory = [[MXCryptoV2Factory alloc] init]; + }); + + return factory; +} + +#endif + + (id)createCryptoWithMatrixSession:(MXSession *)mxSession error:(NSError **)error { __block id crypto; #ifdef MX_CRYPTO - #if DEBUG if (MXSDKOptions.sharedInstance.isCryptoSDKAvailable && MXSDKOptions.sharedInstance.enableCryptoSDK) { - return [self createCryptoV2WithSession:mxSession error:error]; + MXLogFailure(@"[MXCrypto] createCryptoWithMatrixSession: Crypto V2 should not be created directly, use initializeCryptoWithMatrixSession instead"); + return nil; } #endif @@ -177,27 +193,32 @@ @implementation MXLegacyCrypto return crypto; } -+ (void)checkCryptoWithMatrixSession:(MXSession *)mxSession - complete:(void (^)(id crypto, NSError *error))complete ++ (void)initializeCryptoWithMatrixSession:(MXSession *)mxSession + migrationProgress:(void (^)(double))migrationProgress + complete:(void (^)(id crypto, NSError *error))complete { #ifdef MX_CRYPTO #if DEBUG if (MXSDKOptions.sharedInstance.isCryptoSDKAvailable && MXSDKOptions.sharedInstance.enableCryptoSDK) { - NSError *error; - id crypto = [self createCryptoV2WithSession:mxSession error:&error]; - complete(crypto, error); + MXCryptoV2Factory *factory = [MXLegacyCrypto sharedCryptoV2Factory]; + [factory buildCryptoWithSession:mxSession migrationProgress:migrationProgress + success:^(id crypto) { + complete(crypto, nil); + } failure:^(NSError *error) { + complete(nil, error); + }]; return; } #endif - [self checkLegacyCryptoWithMatrixSession:mxSession complete:complete]; + [self initalizeLegacyCryptoWithMatrixSession:mxSession complete:complete]; #else complete(nil); #endif } -+ (void)checkLegacyCryptoWithMatrixSession:(MXSession*)mxSession complete:(void (^)(id crypto, NSError *error))complete ++ (void)initalizeLegacyCryptoWithMatrixSession:(MXSession*)mxSession complete:(void (^)(id crypto, NSError *error))complete { #ifdef MX_CRYPTO diff --git a/MatrixSDK/Crypto/MXCryptoV2.swift b/MatrixSDK/Crypto/MXCryptoV2.swift index be7adaec41..4b58cf64ba 100644 --- a/MatrixSDK/Crypto/MXCryptoV2.swift +++ b/MatrixSDK/Crypto/MXCryptoV2.swift @@ -17,37 +17,122 @@ import Foundation #if DEBUG -public extension MXLegacyCrypto { - enum CryptoError: Swift.Error, LocalizedError { + +import MatrixSDKCrypto + +@objc public class MXCryptoV2Factory: NSObject { + enum Error: Swift.Error { case cryptoNotAvailable + case storeNotAvailable + } + + private let log = MXNamedLog(name: "MXCryptoV2Factory") + + @objc public func buildCrypto( + session: MXSession!, + migrationProgress: ((Double) -> Void)?, + success: @escaping (MXCrypto?) -> Void, + failure: @escaping (Swift.Error) -> Void + ) { + guard + let session = session, + let credentials = session.credentials, + let userId = credentials.userId + else { + log.failure("Missing reuired dependencies") + failure(Error.cryptoNotAvailable) + return + } - public var errorDescription: String? { - return "Encryption not available, please restart the app" + log.debug("Building crypto for \(userId)") + let queue = DispatchQueue(label: "MXCryptoV2-\(userId)") + queue.async { [weak self] in + self?.createOrOpenLegacyStore(credentials: credentials) { legacyStore in + guard let self = self else { return } + + do { + try self.migrateIfNecessary(legacyStore: legacyStore) { + migrationProgress?($0) + } + + let crypto = try MXCryptoV2( + session: session, + cryptoQueue: queue, + legacyStore: legacyStore + ) + + DispatchQueue.main.async { + success(crypto) + } + } catch { + self.log.failure("Cannot create crypto") + DispatchQueue.main.async { + failure(error) + } + } + + } failure: { error in + DispatchQueue.main.async { + failure(error) + } + } } } - /// Create a Rust-based work-in-progress implementation of `MXCrypto` - @objc static func createCryptoV2(session: MXSession!) throws -> MXCrypto { - let log = MXNamedLog(name: "MXCryptoV2") - - guard let session = session else { - log.failure("Cannot create crypto V2, missing session") - throw CryptoError.cryptoNotAvailable + // A few features (e.g. global untrusted users blacklist) are not yet implemented in `MatrixSDKCrypto` + // so they have to be stored in a legacy database. Will be moved to `MatrixSDKCrypto` eventually + private func createOrOpenLegacyStore( + credentials: MXCredentials, + success: @escaping (MXCryptoStore) -> Void, + failure: @escaping (Swift.Error) -> Void + ) { + MXRealmCryptoStore.deleteReadonlyStore(with: credentials) + if MXRealmCryptoStore.hasData(for: credentials) { + log.debug("Legacy crypto store exists") + + guard let legacyStore = MXRealmCryptoStore(credentials: credentials) else { + log.failure("Cannot initialize legacy store") + failure(Error.storeNotAvailable) + return + } + + legacyStore.open { [weak self] in + self?.log.debug("Legacy crypto store opened") + success(legacyStore) + } failure: { [weak self] error in + self?.log.failure("Cannot open legacy crypto store") + failure(error ?? Error.storeNotAvailable) + } + + } else { + log.debug("Creating new legacy crypto store") + + guard let legacyStore = MXRealmCryptoStore.createStore(with: credentials) else { + log.failure("Cannot create legacy store") + failure(Error.storeNotAvailable) + return + } + legacyStore.cryptoVersion = MXCryptoVersion.versionLegacyDeprecated + + log.debug("Legacy crypto store created") + success(legacyStore) } - - do { - return try MXCryptoV2(session: session) - } catch { - log.failure("Error creating crypto V2", context: error) - throw CryptoError.cryptoNotAvailable + } + + private func migrateIfNecessary(legacyStore: MXCryptoStore, updateProgress: @escaping (Double) -> Void) throws { + guard legacyStore.cryptoVersion.rawValue < MXCryptoVersion.versionLegacyDeprecated.rawValue else { + log.debug("Legacy crypto has already been deprecatd, no need to migrate") + return } + + log.debug("Requires migration from legacy crypto") + let migration = MXCryptoMigrationV2(legacyStore: legacyStore) + try migration.migrateCrypto(updateProgress: updateProgress) + + log.debug("Marking legacy crypto as deprecated") + legacyStore.cryptoVersion = MXCryptoVersion.versionLegacyDeprecated } } -#endif - -#if DEBUG - -import MatrixSDKCrypto /// An implementation of `MXCrypto` which uses [matrix-rust-sdk](https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto) /// under the hood. @@ -101,7 +186,7 @@ private class MXCryptoV2: NSObject, MXCrypto { let crossSigning: MXCrossSigning let recoveryService: MXRecoveryService - init(session: MXSession) throws { + init(session: MXSession, cryptoQueue: DispatchQueue, legacyStore: MXCryptoStore) throws { guard let restClient = session.matrixRestClient, let credentials = session.credentials, @@ -112,15 +197,8 @@ private class MXCryptoV2: NSObject, MXCrypto { } self.session = session - self.cryptoQueue = DispatchQueue(label: "MXCryptoV2-\(userId)") - - // A few features (global untrusted users blacklist) are not yet implemented in `MatrixSDKCrypto` - // so they have to be stored locally. Will be moved to `MatrixSDKCrypto` eventually - if MXRealmCryptoStore.hasData(for: credentials) { - self.legacyStore = MXRealmCryptoStore(credentials: credentials) - } else { - self.legacyStore = MXRealmCryptoStore.createStore(with: credentials) - } + self.cryptoQueue = cryptoQueue + self.legacyStore = legacyStore machine = try MXCryptoMachine( userId: userId, @@ -194,6 +272,7 @@ private class MXCryptoV2: NSObject, MXCrypto { _ onComplete: (() -> Void)?, failure: ((Swift.Error) -> Void)? ) { + guard startTask == nil else { log.error("Crypto module has already been started") onComplete?() @@ -203,8 +282,6 @@ private class MXCryptoV2: NSObject, MXCrypto { log.debug("->") startTask = Task { do { - try migrateIfNecessary() - try await machine.uploadKeysIfNecessary() crossSigning.refreshState(success: nil) backup?.checkAndStart() @@ -249,20 +326,6 @@ private class MXCryptoV2: NSObject, MXCrypto { } } - private func migrateIfNecessary() throws { - guard legacyStore.cryptoVersion.rawValue < MXCryptoVersion.versionLegacyDeprecated.rawValue else { - log.debug("Legacy crypto has already been deprecated, no need to migrate") - return - } - - log.debug("Requires migration from legacy crypto") - let migration = MXCryptoMigrationV2(legacyStore: legacyStore) - try migration.migrateCrypto() - - log.debug("Marking legacy crypto as deprecated") - legacyStore.cryptoVersion = MXCryptoVersion.versionLegacyDeprecated - } - // MARK: - Event Encryption public func isRoomEncrypted(_ roomId: String) -> Bool { diff --git a/MatrixSDK/Crypto/Migration/Data/MXCryptoMigrationStore.swift b/MatrixSDK/Crypto/Migration/Data/MXCryptoMigrationStore.swift index b0a2cadc06..25561d1366 100644 --- a/MatrixSDK/Crypto/Migration/Data/MXCryptoMigrationStore.swift +++ b/MatrixSDK/Crypto/Migration/Data/MXCryptoMigrationStore.swift @@ -28,11 +28,19 @@ struct MXCryptoMigrationStore { let legacyStore: MXCryptoStore + var olmSessionCount: UInt { + legacyStore.sessionsCount() + } + + var megolmSessionCount: UInt { + legacyStore.inboundGroupSessionsCount(false) + } + func extractData(with pickleKey: Data) throws -> MigrationData { return .init( account: try pickledAccount(pickleKey: pickleKey), - sessions: olmSessions(pickleKey: pickleKey), - inboundGroupSessions: megolmSessions(pickleKey: pickleKey), + sessions: [], // Sessions are extracted in batches separately + inboundGroupSessions: [], // Group sessions are extracted in batches separately backupVersion: legacyStore.backupVersion, backupRecoveryKey: backupRecoveryKey(), pickleKey: [UInt8](pickleKey), @@ -41,6 +49,46 @@ struct MXCryptoMigrationStore { ) } + func extractSessions( + with pickleKey: Data, + batchSize: Int, + callback: @escaping ([PickledSession], Double) -> Void + ) { + legacyStore.enumerateSessions(by: batchSize) { sessions, progress in + let pickled: [PickledSession] = sessions?.compactMap { + do { + return try PickledSession(session: $0, pickleKey: pickleKey) + } catch { + MXLog.error("[MXCryptoMigrationStore] cannot extract olm session", context: error) + return nil + } + } ?? [] + callback(pickled, progress) + } + } + + func extractGroupSessions( + with pickleKey: Data, + batchSize: Int, + callback: @escaping ([PickledInboundGroupSession], Double) -> Void + ) { + legacyStore.enumerateInboundGroupSessions(by: batchSize) { sessions, backedUp, progress in + let pickled: [PickledInboundGroupSession] = sessions?.compactMap { + do { + return try PickledInboundGroupSession( + session: $0, + pickleKey: pickleKey, + backedUp: backedUp?.contains($0.session.sessionIdentifier()) == true + ) + } catch { + MXLog.error("[MXCryptoMigrationStore] cannot extract megolm session", context: error) + return nil + } + } ?? [] + callback(pickled, progress) + } + } + private func pickledAccount(pickleKey: Data) throws -> PickledAccount { guard let userId = legacyStore.userId(), @@ -57,43 +105,6 @@ struct MXCryptoMigrationStore { ) } - private func olmSessions(pickleKey: Data) -> [PickledSession] { - return legacyStore - .sessions()? - .compactMap { - do { - return try PickledSession(session: $0, pickleKey: pickleKey) - } catch { - MXLog.error("[MXCryptoMigrationStore] cannot extract olm session", context: error) - return nil - } - } ?? [] - } - - private func megolmSessions(pickleKey: Data) -> [PickledInboundGroupSession] { - guard let sessions = legacyStore.inboundGroupSessions() else { - return [] - } - - let sessionsToBackup = Set( - legacyStore.inboundGroupSessions(toBackup: UInt.max) - .compactMap { $0.session?.sessionIdentifier() } - ) - - return sessions.compactMap { - do { - return try PickledInboundGroupSession( - session: $0, - pickleKey: pickleKey, - backedUp: !sessionsToBackup.contains($0.session?.sessionIdentifier() ?? "") - ) - } catch { - MXLog.error("[MXCryptoMigrationStore] cannot extract megolm session", context: error) - return nil - } - } - } - private func backupRecoveryKey() -> String? { guard let privateKey = secret(for: MXSecretId.keyBackup) else { return nil diff --git a/MatrixSDK/Crypto/Migration/MXCryptoMigrationV2.swift b/MatrixSDK/Crypto/Migration/MXCryptoMigrationV2.swift index 8944dc32d9..9ef3e8864f 100644 --- a/MatrixSDK/Crypto/Migration/MXCryptoMigrationV2.swift +++ b/MatrixSDK/Crypto/Migration/MXCryptoMigrationV2.swift @@ -22,6 +22,8 @@ import OLMKit import MatrixSDKCrypto class MXCryptoMigrationV2: NSObject { + private static let SessionBatchSize = 1000 + private let store: MXCryptoMigrationStore private let log = MXNamedLog(name: "MXCryptoMachineMigration") @@ -31,10 +33,12 @@ class MXCryptoMigrationV2: NSObject { OLMKit.sharedInstance().pickleKeyDelegate = self } - func migrateCrypto() throws { + func migrateCrypto(updateProgress: @escaping (Double) -> Void) throws { log.debug("Starting migration") + updateProgress(0) - let data = try store.extractData(with: pickleKey()) + let key = pickleKey() + let data = try store.extractData(with: key) let url = try MXCryptoMachine.storeURL(for: data.account.userId) if FileManager.default.fileExists(atPath: url.path) { @@ -45,8 +49,8 @@ class MXCryptoMigrationV2: NSObject { Migration summary - user id : \(data.account.userId) - device id : \(data.account.deviceId) - - olm_sessions : \(data.sessions.count) - - megolm_sessions : \(data.inboundGroupSessions.count) + - olm_sessions : \(store.olmSessionCount) + - megolm_sessions : \(store.megolmSessionCount) - backup_key : \(data.backupRecoveryKey != nil ? "true" : "false") - cross_signing : \(data.crossSigning.masterKey != nil ? "true" : "false") - tracked_users : \(data.trackedUsers.count) @@ -60,7 +64,72 @@ class MXCryptoMigrationV2: NSObject { progressListener: self ) + log.debug("Migrating olm sessions in batches") + + // How much does migration of olm vs megolm sessions contribute to the overall progress + let olmToMegolmProgressRatio = 0.25 + + store.extractSessions(with: key, batchSize: Self.SessionBatchSize) { [weak self] batch, progress in + updateProgress(progress * olmToMegolmProgressRatio) + + do { + try self?.migrateSessions( + account: data.account, + pickleKey: data.pickleKey, + sessions: batch, + url: url + ) + } catch { + self?.log.error("Error migrating some sessions", context: error) + } + } + + log.debug("Migrating megolm sessions in batches") + + store.extractGroupSessions(with: key, batchSize: Self.SessionBatchSize) { [weak self] batch, progress in + updateProgress(olmToMegolmProgressRatio + progress * (1 - olmToMegolmProgressRatio)) + + do { + try self?.migrateSessions( + account: data.account, + pickleKey: data.pickleKey, + inboundGroupSessions: batch, + url: url + ) + } catch { + self?.log.error("Error migrating some sessions", context: error) + } + } + log.debug("Migration complete") + updateProgress(1) + } + + // To migrate sessions in batches and keep memory under control we are repeatedly calling `migrate` + // function whilst only passing data for sessions and account, keeping the rest empty. + // This API will be improved in `MatrixCryptoSDK` in the future. + private func migrateSessions( + account: PickledAccount, + pickleKey: [UInt8], + sessions: [PickledSession] = [], + inboundGroupSessions: [PickledInboundGroupSession] = [], + url: URL + ) throws { + try migrate( + data: .init( + account: account, + sessions: sessions, + inboundGroupSessions: inboundGroupSessions, + backupVersion: nil, + backupRecoveryKey: nil, + pickleKey: pickleKey, + crossSigning: .init(masterKey: nil, selfSigningKey: nil, userSigningKey: nil), + trackedUsers: [] + ), + path: url.path, + passphrase: nil, + progressListener: self + ) } } @@ -84,7 +153,7 @@ extension MXCryptoMigrationV2: OLMKitPickleKeyDelegate { extension MXCryptoMigrationV2: ProgressListener { func onProgress(progress: Int32, total: Int32) { - log.debug("Migration progress \(progress) out of \(total)") + // Progress loggged manually } } diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m index 82b60b8eff..6366a24cdd 100644 --- a/MatrixSDK/MXSession.m +++ b/MatrixSDK/MXSession.m @@ -398,7 +398,13 @@ -(void)setStore:(id)store success:(void (^)(void))onStoreDataReady fail // Check if the user has enabled crypto MXWeakify(self); - [MXLegacyCrypto checkCryptoWithMatrixSession:self complete:^(id crypto, NSError *error) { + [MXLegacyCrypto initializeCryptoWithMatrixSession:self migrationProgress:^(double progress) { + if (MXSDKOptions.sharedInstance.enableStartupProgress) + { + [self.startupProgress updateMigrationProgress:progress]; + } + + } complete:^(id crypto, NSError *error) { MXStrongifyAndReturnIfNil(self); if (!crypto && error) diff --git a/MatrixSDK/MXSessionStartupProgress.swift b/MatrixSDK/MXSessionStartupProgress.swift index abb98a6f7f..abe702d5cb 100644 --- a/MatrixSDK/MXSessionStartupProgress.swift +++ b/MatrixSDK/MXSessionStartupProgress.swift @@ -21,6 +21,9 @@ import Foundation /// and used to update the user interface during session loading. public enum MXSessionStartupStage { + /// Migrating data to a new store version + case migratingData(progress: Double) + /// Syncing with the server as Nth attempt case serverSyncing(attempt: Int) @@ -71,6 +74,11 @@ public protocol MXSessionStartupProgressDelegate: AnyObject { } } + /// Update the progress of the `migratingData` stage + @objc public func updateMigrationProgress(_ progress: Double) { + stage = .migratingData(progress: progress) + } + /// Increment the total number of sync attempts during the `serverSyncing` stage @objc public func incrementSyncAttempt() { guard stage == nil || stage?.isSyncing == true else { diff --git a/MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift b/MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift index 7febe3a9e3..c09fb0ac79 100644 --- a/MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift +++ b/MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift @@ -209,8 +209,12 @@ public class MXMemoryCryptoStore: NSObject, MXCryptoStore { Array(olmSessions.filter { $0.key.deviceKey == deviceKey }.values) } - public func sessions() -> [MXOlmSession]! { - Array(olmSessions.values) + public func enumerateSessions(by batchSize: Int, block: (([MXOlmSession]?, Double) -> Void)!) { + block(Array(olmSessions.values), 1) + } + + public func sessionsCount() -> UInt { + UInt(olmSessions.count) } // MARK: - Inbound Group Sessions @@ -231,6 +235,11 @@ public class MXMemoryCryptoStore: NSObject, MXCryptoStore { public func inboundGroupSessions() -> [MXOlmInboundGroupSession]! { inboundSessions.map { $0.session } } + + public func enumerateInboundGroupSessions(by batchSize: Int, block: (([MXOlmInboundGroupSession]?, Set?, Double) -> Void)!) { + let backedUp = inboundSessions.filter { $0.backedUp }.map(\.sessionId) + block(inboundGroupSessions(), Set(backedUp), 1) + } public func inboundGroupSessions(withSessionId sessionId: String!) -> [MXOlmInboundGroupSession]! { inboundSessions.filter { $0.sessionId == sessionId }.map { $0.session } diff --git a/MatrixSDKTests/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStoreTests.swift b/MatrixSDKTests/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStoreTests.swift new file mode 100644 index 0000000000..303670f7fe --- /dev/null +++ b/MatrixSDKTests/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStoreTests.swift @@ -0,0 +1,114 @@ +// +// Copyright 2023 The Matrix.org Foundation C.I.C +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +@testable import MatrixSDK + +class MXRealmCryptoStoreTests: XCTestCase { + var store: MXRealmCryptoStore! + override func setUp() { + store = MXRealmCryptoStore() + } + + override func tearDown() { + MXRealmCryptoStore.deleteAllStores() + } + + func makeSession( + deviceKey: String = "XYZ" + ) -> MXOlmSession { + return MXOlmSession(olmSession: OLMSession(), deviceKey: deviceKey) + } + + func makeGroupSession( + roomId: String = "ABC", + senderKey: String? = "Bob", + isUntrusted: Bool = false, + backedUp: Bool = false + ) -> MXOlmInboundGroupSession { + let device = MXOlmDevice(store: store)! + let outbound = device.createOutboundGroupSessionForRoom(withRoomId: roomId) + + let session = MXOlmInboundGroupSession(sessionKey: outbound!.sessionKey)! + session.senderKey = senderKey + session.roomId = roomId + session.keysClaimed = ["A": "1"] + session.isUntrusted = isUntrusted + return session + } + + // MARK: - Olm sessions + + func test_saveAndLoadSession() { + let session = makeSession() + + store.store(session) + XCTAssertEqual(store.sessionsCount(), 1) + + let fetched = store.sessions(withDevice: "XYZ") + XCTAssertEqual(fetched?.count, 1) + } + + func test_enumerateSessions() { + for i in 0 ..< 15 { + let session = makeSession(deviceKey: "\(i)") + store.store(session) + } + + XCTAssertEqual(store.sessionsCount(), 15) + + var count = 0 + var batches = 0 + store.enumerateSessions(by: 4) { sessions, _ in + count += sessions?.count ?? 0 + batches += 1 + } + + XCTAssertEqual(count, 15) + XCTAssertEqual(batches, 4) + } + + // MARK: - Megolm sessions + + func test_saveAndLoadGroupSession() { + let session = makeGroupSession() + + store.store([session]) + XCTAssertEqual(store.inboundGroupSessionsCount(false), 1) + + let fetched = store.inboundGroupSessions() + XCTAssertEqual(fetched?.count, 1) + } + + func test_enumerateGroupSessions() { + for _ in 0 ..< 111 { + let session = makeGroupSession() + store.store([session]) + } + + XCTAssertEqual(store.inboundGroupSessionsCount(false), 111) + + var count = 0 + var batches = 0 + store.enumerateInboundGroupSessions(by: 20) { sessions, backedUp, progress in + count += sessions?.count ?? 0 + batches += 1 + } + + XCTAssertEqual(count, 111) + XCTAssertEqual(batches, 6) + } +} diff --git a/MatrixSDKTests/Crypto/Migration/Data/MXCryptoMigrationStoreUnitTests.swift b/MatrixSDKTests/Crypto/Migration/Data/MXCryptoMigrationStoreUnitTests.swift index 6cae30420a..2861c2e21d 100644 --- a/MatrixSDKTests/Crypto/Migration/Data/MXCryptoMigrationStoreUnitTests.swift +++ b/MatrixSDKTests/Crypto/Migration/Data/MXCryptoMigrationStoreUnitTests.swift @@ -47,6 +47,22 @@ class MXCryptoMigrationStoreUnitTests: XCTestCase { try store.extractData(with: pickleKey ?? self.pickleKey) } + func extractSessions(pickleKey: Data? = nil) throws -> [PickledSession] { + var sessions = [PickledSession]() + store.extractSessions(with: pickleKey ?? self.pickleKey, batchSize: .max) { batch, progress in + sessions += batch + } + return sessions + } + + func extractGroupSessions(pickleKey: Data? = nil) throws -> [PickledInboundGroupSession] { + var sessions = [PickledInboundGroupSession]() + store.extractGroupSessions(with: pickleKey ?? self.pickleKey, batchSize: .max) { batch, progress in + sessions += batch + } + return sessions + } + @discardableResult func storeGroupSession( roomId: String = "ABC", @@ -106,8 +122,11 @@ class MXCryptoMigrationStoreUnitTests: XCTestCase { legacyStore.store(session) let pickle = try session.session.serializeData(withKey: pickleKey) - let sessions = try extractData().sessions + // There are no sessions in the general migration data + XCTAssertTrue(try extractData().sessions.isEmpty) + // But they can be accumulated by batching + let sessions = try extractSessions() XCTAssertEqual(sessions.count, 1) XCTAssertEqual(sessions[0].pickle, pickle) XCTAssertEqual(sessions[0].senderKey, "XYZ") @@ -116,22 +135,27 @@ class MXCryptoMigrationStoreUnitTests: XCTestCase { XCTAssertEqual(sessions[0].lastUseTime, "123") } - func test_extractsMultipleSession() throws { - for i in 0 ..< 3 { + func test_extractsMultipleSessionsInBatches() throws { + for i in 0 ..< 12 { legacyStore.store(MXOlmSession(olmSession: OLMSession(), deviceKey: "\(i)")) } - let sessions = try extractData().sessions - - XCTAssertEqual(sessions.count, 3) + // There are no sessions in the general migration data + XCTAssertTrue(try extractData().sessions.isEmpty) + // But they can be accumulated by batching + let sessions = try extractSessions() + XCTAssertEqual(sessions.count, 12) } func test_extractsGroupSession() throws { let session = storeGroupSession(roomId: "abcd") let pickle = try session.session.serializeData(withKey: pickleKey) - let sessions = try extractData().inboundGroupSessions + // There are no sessions in the general migration data + XCTAssertTrue(try extractData().inboundGroupSessions.isEmpty) + // But they can be accumulated by batching + let sessions = try extractGroupSessions() XCTAssertEqual(sessions.count, 1) XCTAssertEqual(sessions[0].pickle, pickle) XCTAssertEqual(sessions[0].senderKey, "Bob") @@ -146,8 +170,11 @@ class MXCryptoMigrationStoreUnitTests: XCTestCase { storeGroupSession(senderKey: isValid ? "Bob" : nil) } - let sessions = try extractData().inboundGroupSessions + // There are no sessions in the general migration data + XCTAssertTrue(try extractData().inboundGroupSessions.isEmpty) + // But they can be accumulated by batching + let sessions = try extractGroupSessions() XCTAssertEqual(sessions.count, 2) } @@ -156,7 +183,7 @@ class MXCryptoMigrationStoreUnitTests: XCTestCase { storeGroupSession(isUntrusted: false) storeGroupSession(isUntrusted: false) - let sessions = try extractData().inboundGroupSessions + let sessions = try extractGroupSessions() XCTAssertEqual(sessions.count, 3) XCTAssertTrue(sessions[0].imported) @@ -169,7 +196,7 @@ class MXCryptoMigrationStoreUnitTests: XCTestCase { storeGroupSession(backedUp: true) storeGroupSession(backedUp: false) - let sessions = try extractData().inboundGroupSessions + let sessions = try extractGroupSessions() XCTAssertEqual(sessions.count, 3) XCTAssertFalse(sessions[0].backedUp) diff --git a/MatrixSDKTests/Crypto/Migration/MXCryptoMigrationV2Tests.swift b/MatrixSDKTests/Crypto/Migration/MXCryptoMigrationV2Tests.swift index e79c80d1f9..c1c5489dc0 100644 --- a/MatrixSDKTests/Crypto/Migration/MXCryptoMigrationV2Tests.swift +++ b/MatrixSDKTests/Crypto/Migration/MXCryptoMigrationV2Tests.swift @@ -61,7 +61,7 @@ class MXCryptoMigrationV2Tests: XCTestCase { MXKeyProvider.sharedInstance().delegate = KeyProvider() let migration = MXCryptoMigrationV2(legacyStore: store) - try migration.migrateCrypto() + try migration.migrateCrypto { _ in } MXKeyProvider.sharedInstance().delegate = nil return try MXCryptoMachine( diff --git a/changelog.d/pr-1687.change b/changelog.d/pr-1687.change new file mode 100644 index 0000000000..e1fba6c767 --- /dev/null +++ b/changelog.d/pr-1687.change @@ -0,0 +1 @@ +CryptoV2: Batch migrate olm and megolm sessions