diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2022-12-19 11:32:01 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2022-12-19 13:35:58 +0100 |
| commit | bb643b559ed24e582714124d39b9e47513b1173e (patch) | |
| tree | e92ba106f6415a343f184f9d0bea67b292887f46 | |
| parent | cce928896f27319cffc65db0541bd5ec3672fc7a (diff) | |
| download | mullvadvpn-bb643b559ed24e582714124d39b9e47513b1173e.tar.xz mullvadvpn-bb643b559ed24e582714124d39b9e47513b1173e.zip | |
Reconnect tunnel after migration from AppDelegate
| -rw-r--r-- | ios/MullvadVPN/AppDelegate.swift | 131 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsManager/SettingsManager.swift | 72 | ||||
| -rw-r--r-- | ios/MullvadVPN/TunnelManager/TunnelManager.swift | 11 |
3 files changed, 120 insertions, 94 deletions
diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift index dfdee88d7e..f4307d0f28 100644 --- a/ios/MullvadVPN/AppDelegate.swift +++ b/ios/MullvadVPN/AppDelegate.swift @@ -113,61 +113,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD setupNotificationHandler() addApplicationNotifications(application: application) - let loadTunnelsOperation = AsyncBlockOperation(dispatchQueue: .main) { operation in - self.tunnelStore.loadPersistentTunnels { error in - if let error = error { - self.logger.error( - error: error, - message: "Failed to load persistent tunnels." - ) - } - operation.finish() - } - } - - let migrateSettingsOperation = AsyncBlockOperation(dispatchQueue: .main) { operation in - SettingsManager.migrateStore(with: self.proxyFactory) { error in - guard let error = error else { - operation.finish() - return - } - - guard let migrationUIHandler = application.connectedScenes.compactMap({ scene in - return scene.delegate as? SettingsMigrationUIHandler - }).first else { - operation.finish() - return - } - - migrationUIHandler.showMigrationError(error) { - operation.finish() - } - } - } - migrateSettingsOperation.addDependency(loadTunnelsOperation) - - let loadTunnelConfigurationOperation = - AsyncBlockOperation(dispatchQueue: .main) { operation in - self.tunnelManager.loadConfiguration { error in - // TODO: avoid throwing fatal error and show the problem report UI instead. - if let error = error { - fatalError(error.localizedDescription) - } - - self.logger.debug("Finished initialization.") - - NotificationManager.shared.updateNotifications() - self.storePaymentManager.startPaymentQueueMonitoring() - - operation.finish() - } - } - loadTunnelConfigurationOperation.addDependency(migrateSettingsOperation) - - operationQueue.addOperations( - [loadTunnelsOperation, migrateSettingsOperation, loadTunnelConfigurationOperation], - waitUntilFinished: false - ) + startInitialization(application: application) return true } @@ -405,6 +351,81 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD UNUserNotificationCenter.current().delegate = self } + private func startInitialization(application: UIApplication) { + let loadTunnelStoreOperation = AsyncBlockOperation(dispatchQueue: .main) { operation in + self.tunnelStore.loadPersistentTunnels { error in + if let error = error { + self.logger.error( + error: error, + message: "Failed to load persistent tunnels." + ) + } + operation.finish() + } + } + + let migrateSettingsOperation = ResultBlockOperation<SettingsMigrationResult, Error>( + dispatchQueue: .main + ) { operation in + SettingsManager.migrateStore(with: self.proxyFactory) { migrationResult in + let finishHandler = { + operation.finish(completion: .success(migrationResult)) + } + + guard case let .failure(error) = migrationResult, + let migrationUIHandler = application.connectedScenes.compactMap({ scene in + return scene.delegate as? SettingsMigrationUIHandler + }).first + else { + finishHandler() + return + } + + migrationUIHandler.showMigrationError(error, completionHandler: finishHandler) + } + } + migrateSettingsOperation.addDependency(loadTunnelStoreOperation) + + let initTunnelManagerOperation = AsyncBlockOperation(dispatchQueue: .main) { operation in + self.tunnelManager.loadConfiguration { error in + // TODO: avoid throwing fatal error and show the problem report UI instead. + if let error = error { + fatalError(error.localizedDescription) + } + + self.logger.debug("Finished initialization.") + + NotificationManager.shared.updateNotifications() + self.storePaymentManager.startPaymentQueueMonitoring() + + operation.finish() + } + } + initTunnelManagerOperation.addDependency(migrateSettingsOperation) + + let reconnectTunnelOperation = TransformOperation<SettingsMigrationResult, Void, Error>( + dispatchQueue: .main + ) { migrationResult in + if case .success = migrationResult { + self.logger.debug("Reconnect the tunnel after settings migration.") + + self.tunnelManager.reconnectTunnel(selectNewRelay: true) + } + } + reconnectTunnelOperation.inject(from: migrateSettingsOperation) + reconnectTunnelOperation.addDependency(initTunnelManagerOperation) + + operationQueue.addOperations( + [ + loadTunnelStoreOperation, + migrateSettingsOperation, + initTunnelManagerOperation, + reconnectTunnelOperation, + ], + waitUntilFinished: false + ) + } + // MARK: - StorePaymentManagerDelegate func storePaymentManager( diff --git a/ios/MullvadVPN/SettingsManager/SettingsManager.swift b/ios/MullvadVPN/SettingsManager/SettingsManager.swift index 2940937ae4..06ae10f1a6 100644 --- a/ios/MullvadVPN/SettingsManager/SettingsManager.swift +++ b/ios/MullvadVPN/SettingsManager/SettingsManager.swift @@ -15,6 +15,17 @@ private let keychainServiceName = "Mullvad VPN" private let accountTokenKey = "accountToken" private let accountExpiryKey = "accountExpiry" +enum SettingsMigrationResult { + /// Nothing to migrate. + case nothing + + /// Successfully performed migration. + case success + + /// Failure when migrating store. + case failure(Error) +} + enum SettingsManager { private static let logger = Logger(label: "SettingsManager") @@ -110,31 +121,35 @@ enum SettingsManager { /// Migrate settings store if needed. /// - /// The error returned in `completion` handler is set to `nil` upon success or when no - /// migration is not needed. - /// /// The following types of error are expected to be returned by this method: /// `SettingsMigrationError`, `UnsupportedSettingsVersionError`, `ReadSettingsVersionError`. static func migrateStore( with restFactory: REST.ProxyFactory, - completion: @escaping (Error?) -> Void + completion: @escaping (SettingsMigrationResult) -> Void ) { - let handleCompletion = { (error: Error?) in + let handleCompletion = { (result: SettingsMigrationResult) in // Reset store upon failure to migrate settings. - if error != nil { + if case .failure = result { self.resetStore() } - completion(error) + completion(result) } if let legacySettings = readLegacySettings() { migrateLegacySettings( restFactory: restFactory, - legacySettings: legacySettings, - completion: handleCompletion - ) + legacySettings: legacySettings + ) { error in + handleCompletion(error.map { .failure($0) } ?? .success) + } } else { - migrateModernSettings(completion: handleCompletion) + do { + try checkLatestSettingsVersion() + + handleCompletion(.nothing) + } catch { + handleCompletion(.failure(error)) + } } } @@ -171,29 +186,30 @@ enum SettingsManager { } } - private static func migrateModernSettings(completion: @escaping (Error?) -> Void) { + private static func checkLatestSettingsVersion() throws { + let settingsVersion: Int do { let parser = makeParser() let settingsData = try store.read(key: .settings) - let settingsVersion = try parser.parseVersion(data: settingsData) - - if settingsVersion != SchemaVersion.current.rawValue { - let error = UnsupportedSettingsVersionError( - storedVersion: settingsVersion, - currentVersion: SchemaVersion.current - ) - - logger.error(error: error, message: "Encountered an unknown version.") - - completion(error) - } else { - completion(nil) - } + settingsVersion = try parser.parseVersion(data: settingsData) } catch .itemNotFound as KeychainError { - completion(nil) + return } catch { - completion(ReadSettingsVersionError(underlyingError: error)) + throw ReadSettingsVersionError(underlyingError: error) } + + guard settingsVersion != SchemaVersion.current.rawValue else { + return + } + + let error = UnsupportedSettingsVersionError( + storedVersion: settingsVersion, + currentVersion: SchemaVersion.current + ) + + logger.error(error: error, message: "Encountered an unknown version.") + + throw error } /// Removes all legacy settings, device state and tunnel settings but keeps the last used diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift index c117dee394..3854f3ca54 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift @@ -84,9 +84,6 @@ final class TunnelManager: StorePaymentObserver { /// Last processed device check identifier. private var lastDeviceCheckIdentifier: UUID? - /// Flag indicating tunnel reconnected after settings migration. - private var reconnectedTunnelAfterMigration = false - // MARK: - Initialization init( @@ -697,14 +694,6 @@ final class TunnelManager: StorePaymentObserver { // while the tunnel process is trying to connect. startPollingTunnelStatus(interval: establishingTunnelStatusPollInterval) - if newTunnelStatus.packetTunnelStatus.lastErrors.contains(.readConfiguration), - !reconnectedTunnelAfterMigration - { - reconnectTunnel(selectNewRelay: true) - - reconnectedTunnelAfterMigration = true - } - case .connected, .waitingForConnectivity: // Start polling tunnel status to keep connectivity status up to date. startPollingTunnelStatus(interval: establishedTunnelStatusPollInterval) |
